diff --git a/ch.eitchnet.utils/LICENSE b/ch.eitchnet.utils/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/ch.eitchnet.utils/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ch.eitchnet.utils/README.md b/ch.eitchnet.utils/README.md new file mode 100644 index 000000000..46c7a0bcd --- /dev/null +++ b/ch.eitchnet.utils/README.md @@ -0,0 +1,47 @@ +ch.eitchnet.utils +====================== + +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=ch.eitchnet.utils)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.utils/) + +Java Utilites which ease daily work when programming in the Java language + +Dependencies +---------------------- +This utility package is built by Maven3 and has very few external dependencies. The current dependencies are: +* the Java Runtime Environment 7 +* JUnit 4.11 (test scope) +* slf4j 1.7.2 +* slf4j-log4j bindings (test scope) + +Features +---------------------- +* RMI File client/server + * This is a small RMI client server which allows to fetch files from a server which exposes the RmiFileHandler class via RMI +* ObjectFilter + * The ObjectFilter allows to keep track of modifications to objects. The modifications are add/update/remove. + * You register the modification of an object on the filter and when all is done, you query the filter for all the add/update/remove modifications so that you only persist the required changes to your database +* ArraysHelper + * The ArraysHelper contains methods to handling arrays +* BaseEncoding + * The BaseEncoding class implements RFC4648 and thus implements Base64, Base32, Base16 in all their different alphabets and also implementes the D-Base32 encoding +* ByteHelper + * The ByteHelper contains methods to print, convert and manipulate bytes +* FileHelper + * The FileHelper contains methods relevant to files. E.g. recursively deleting directories, copying files, reading/writing files etc. +* ProcessHelper + * The ProcessHelper abstracts away OS specific process tasks +* StringHelper + * The StringHelper contains methods for handling Strings +* SystemHelper + * The SystemHelper contains methods to get system specific information +* XmlHelper + * The XmlHelper contains methods to handle XML files + +Building +------------------------- +* Prerequisites: + * JDK 6 is installed and JAVA_HOME is properly set and ../bin is in path + * Maven 3 is installed and MAVEN_HOME is properly set and ../bin is in path +* Clone repository and change path to root +* Run maven: + * mvn clean install diff --git a/ch.eitchnet.utils/pom.xml b/ch.eitchnet.utils/pom.xml new file mode 100644 index 000000000..9839d0b9a --- /dev/null +++ b/ch.eitchnet.utils/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + + ch.eitchnet + ch.eitchnet.parent + 1.1.0-SNAPSHOT + ../ch.eitchnet.parent/pom.xml + + + ch.eitchnet.utils + jar + ch.eitchnet.utils + These utils contain project independent helper classes and utilities for reuse + https://github.com/eitchnet/ch.eitchnet.utils + 2011 + + + Github Issues + https://github.com/eitchnet/ch.eitchnet.utils/issues + + + + scm:git:https://github.com/eitchnet/ch.eitchnet.utils.git + scm:git:git@github.com:eitchnet/ch.eitchnet.utils.git + https://github.com/eitchnet/ch.eitchnet.utils + HEAD + + + + + org.slf4j + slf4j-log4j12 + runtime + + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-site-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + + + diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommandKey.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommandKey.java new file mode 100644 index 000000000..f34389bf0 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommandKey.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public class CommandKey { + private final String key1; + private final String key2; + private final int hashCode; + + /** + * @param key1 + * @param key2 + */ + public CommandKey(String key1, String key2) { + this.key1 = key1; + this.key2 = key2; + + final int prime = 31; + int result = 1; + result = prime * result + ((this.key1 == null) ? 0 : this.key1.hashCode()); + result = prime * result + ((this.key2 == null) ? 0 : this.key2.hashCode()); + this.hashCode = result; + } + + public static CommandKey key(String key1, String key2) { + return new CommandKey(key1, key2); + } + + @Override + public int hashCode() { + return this.hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CommandKey other = (CommandKey) obj; + return this.key1.equals(other.key1) && this.key2.equals(other.key2); + } + + @Override + public String toString() { + return this.key1 + StringHelper.COLON + this.key2; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java new file mode 100644 index 000000000..46db7c6ba --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -0,0 +1,405 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.IoMessage.State; +import ch.eitchnet.utils.collections.MapOfLists; +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public class CommunicationConnection implements Runnable { + + protected static final Logger logger = LoggerFactory.getLogger(CommunicationConnection.class); + + private String id; + private ConnectionMode mode; + private Map parameters; + + private ConnectionState state; + private String stateMsg; + + private BlockingDeque messageQueue; + private Thread queueThread; + private volatile boolean run; + private MapOfLists connectionObservers; + private List connectionStateObservers; + + private CommunicationEndpoint endpoint; + private IoMessageVisitor messageVisitor; + + private IoMessageArchive archive; + + public CommunicationConnection(String id, ConnectionMode mode, Map parameters, + CommunicationEndpoint endpoint, IoMessageVisitor messageVisitor) { + + DBC.PRE.assertNotEmpty("Id must be set!", id); //$NON-NLS-1$ + DBC.PRE.assertNotNull("ConnectionMode must be set!", mode); //$NON-NLS-1$ + DBC.PRE.assertNotNull("Paramerters must not be null!", parameters); //$NON-NLS-1$ + DBC.PRE.assertNotNull("Endpoint must be set!", endpoint); //$NON-NLS-1$ + DBC.PRE.assertNotNull("IoMessageVisitor must be set!", messageVisitor); //$NON-NLS-1$ + + this.id = id; + this.mode = mode; + this.parameters = parameters; + this.endpoint = endpoint; + this.messageVisitor = messageVisitor; + + this.state = ConnectionState.CREATED; + this.stateMsg = this.state.toString(); + this.messageQueue = new LinkedBlockingDeque<>(); + this.connectionObservers = new MapOfLists<>(); + this.connectionStateObservers = new ArrayList<>(); + } + + public void setArchive(IoMessageArchive archive) { + this.archive = archive; + } + + public IoMessageArchive getArchive() { + return this.archive; + } + + public String getId() { + return this.id; + } + + public int getQueueSize() { + return this.messageQueue.size(); + } + + public ConnectionState getState() { + return this.state; + } + + public String getStateMsg() { + return this.stateMsg; + } + + public ConnectionMode getMode() { + return this.mode; + } + + public Map getParameters() { + return this.parameters; + } + + public void clearQueue() { + this.messageQueue.clear(); + } + + public void addConnectionObserver(CommandKey key, ConnectionObserver observer) { + synchronized (this.connectionObservers) { + this.connectionObservers.addElement(key, observer); + } + } + + public void removeConnectionObserver(CommandKey key, ConnectionObserver observer) { + synchronized (this.connectionObservers) { + this.connectionObservers.removeElement(key, observer); + } + } + + public void addConnectionStateObserver(ConnectionStateObserver observer) { + synchronized (this.connectionStateObservers) { + this.connectionStateObservers.add(observer); + } + } + + public void removeConnectionStateObserver(ConnectionStateObserver observer) { + synchronized (this.connectionStateObservers) { + this.connectionStateObservers.remove(observer); + } + } + + public void notifyStateChange(ConnectionState state, String stateMsg) { + ConnectionState oldState = this.state; + String oldStateMsg = this.stateMsg; + this.state = state; + this.stateMsg = stateMsg; + + List observers; + synchronized (this.connectionStateObservers) { + observers = new ArrayList<>(this.connectionStateObservers); + } + for (ConnectionStateObserver observer : observers) { + observer.notify(oldState, oldStateMsg, state, stateMsg); + } + } + + public void switchMode(ConnectionMode mode) { + ConnectionMessages.assertConfigured(this, "Can not switch modes yet!"); //$NON-NLS-1$ + if (mode == ConnectionMode.OFF) { + stop(); + } else if (mode == ConnectionMode.ON) { + stop(); + start(); + } + + this.mode = mode; + } + + /** + * Configure the underlying {@link CommunicationEndpoint} and {@link IoMessageVisitor} + */ + public void configure() { + this.messageVisitor.configure(this); + this.endpoint.configure(this, this.messageVisitor); + notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.name()); + } + + public void start() { + ConnectionMessages.assertConfigured(this, "Can not start yet!"); //$NON-NLS-1$ + + switch (this.mode) { + case OFF: + logger.info("Not connecting as mode is currently OFF"); //$NON-NLS-1$ + break; + case SIMULATION: + logger.info("Started SIMULATION connection!"); //$NON-NLS-1$ + break; + case ON: + if (this.queueThread != null) { + logger.warn(MessageFormat.format("{0}: Already connected!", this.id)); //$NON-NLS-1$ + } else { + logger.info(MessageFormat.format("Starting Connection {0} to {1}...", this.id, getRemoteUri())); //$NON-NLS-1$ + this.run = true; + this.queueThread = new Thread(this, MessageFormat.format("{0}_OUT", this.id)); //$NON-NLS-1$ + this.queueThread.start(); + + connectEndpoint(); + } + break; + default: + logger.error("Unhandled mode " + this.mode); //$NON-NLS-1$ + break; + } + } + + public void stop() { + ConnectionMessages.assertConfigured(this, "Can not stop yet!"); //$NON-NLS-1$ + + switch (this.mode) { + case OFF: + break; + case SIMULATION: + logger.info("Disconnected SIMULATION connection!"); //$NON-NLS-1$ + break; + case ON: + logger.info("Disconnecting..."); //$NON-NLS-1$ + if (this.queueThread == null) { + logger.warn(MessageFormat.format("{0}: Already disconnected!", this.id)); //$NON-NLS-1$ + } else { + this.run = false; + + try { + disconnectEndpoint(); + } catch (Exception e) { + String msg = "Caught exception while disconnecting endpoint: {0}"; //$NON-NLS-1$ + logger.error(MessageFormat.format(msg, e.getLocalizedMessage()), e); + } + + try { + this.queueThread.interrupt(); + } catch (Exception e) { + String msg = "Caught exception while stopping queue thread: {0}"; //$NON-NLS-1$ + logger.warn(MessageFormat.format(msg, e.getLocalizedMessage())); + } + String msg = "{0} is stopped"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.queueThread.getName())); + this.queueThread = null; + } + break; + default: + logger.error("Unhandled mode " + this.mode); //$NON-NLS-1$ + break; + } + } + + /** + * Called by the underlying endpoint when a new message has been received and parsed + * + * @param message + */ + public void handleNewMessage(IoMessage message) { + ConnectionMessages.assertConfigured(this, "Can not be notified of new message yet!"); //$NON-NLS-1$ + + // if the state of the message is already later than ACCEPTED + // then an underlying component has already set the state, so + // we don't need to set it + if (message.getState().compareTo(State.ACCEPTED) < 0) + message.setState(State.ACCEPTED, StringHelper.DASH); + + notifyObservers(message); + } + + public void notifyObservers(IoMessage message) { + List observers; + synchronized (this.connectionObservers) { + List list = this.connectionObservers.getList(message.getKey()); + if (list == null) + return; + + observers = new ArrayList<>(list); + } + + for (ConnectionObserver observer : observers) { + try { + observer.notify(message.getKey(), message); + } catch (Exception e) { + String msg = "Failed to notify observer for key {0} on message with id {1}"; //$NON-NLS-1$ + logger.error(MessageFormat.format(msg, message.getKey(), message.getId()), e); + } + } + + if (this.archive != null) + this.archive.archive(message); + } + + @Override + public void run() { + while (this.run) { + + IoMessage message = null; + + try { + + message = this.messageQueue.take(); + logger.info(MessageFormat.format("Processing message {0}...", message.getId())); //$NON-NLS-1$ + + if (this.mode == ConnectionMode.ON) + this.endpoint.send(message); + else if (this.mode == ConnectionMode.SIMULATION) + this.endpoint.simulate(message); + + // notify the caller that the message has been processed + if (message.getState().compareTo(State.DONE) < 0) + message.setState(State.DONE, StringHelper.DASH); + + done(message); + + } catch (InterruptedException e) { + logger.warn(MessageFormat.format("{0} connection has been interruped!", this.id)); //$NON-NLS-1$ + + // an interrupted exception means the thread must stop + this.run = false; + + if (message != null) { + logger.error(MessageFormat.format("Can not send message {0}", message.getId())); //$NON-NLS-1$ + message.setState(State.FATAL, e.getLocalizedMessage()); + done(message); + } + + } catch (Exception e) { + logger.error(e.getMessage(), e); + + if (message != null) { + logger.error(MessageFormat.format("Can not send message {0}", message.getId())); //$NON-NLS-1$ + message.setState(State.FATAL, e.getLocalizedMessage()); + done(message); + } + } finally { + if (message != null && this.archive != null) { + this.archive.archive(message); + } + } + } + } + + /** + * Called when an outgoing message has been handled. This method logs the message state and then notifies all + * observers + * + * @param message + */ + public void done(IoMessage message) { + ConnectionMessages.assertConfigured(this, "Can not notify observers yet!"); //$NON-NLS-1$ + + switch (message.getState()) { + case ACCEPTED: + case CREATED: + case DONE: + case PENDING: + logger.info(MessageFormat.format("Sent message {0}", message.toString())); //$NON-NLS-1$ + break; + case FAILED: + case FATAL: + logger.error(MessageFormat.format("Failed to send message {0}", message.toString())); //$NON-NLS-1$ + break; + default: + logger.error(MessageFormat.format("Unhandled state for message {0}", message.toString())); //$NON-NLS-1$ + break; + } + + notifyObservers(message); + } + + public String getRemoteUri() { + return this.endpoint == null ? "0.0.0.0:0" : this.endpoint.getRemoteUri(); //$NON-NLS-1$ + } + + public String getLocalUri() { + return this.endpoint == null ? "0.0.0.0:0" : this.endpoint.getLocalUri(); //$NON-NLS-1$ + } + + public void reset() { + ConnectionMessages.assertConfigured(this, "Can not resest yet!"); //$NON-NLS-1$ + this.endpoint.reset(); + } + + /** + * Called when the connection is connected, thus the underlying endpoint can be started + */ + protected void connectEndpoint() { + this.endpoint.start(); + } + + /** + * Called when the connection is disconnected, thus the underlying endpoint must be stopped + */ + protected void disconnectEndpoint() { + this.endpoint.stop(); + } + + /** + * Send the message using the underlying endpoint. Do not change the state of the message, this will be done by the + * caller + * + * @param message + */ + public void send(IoMessage message) { + ConnectionMessages.assertConfigured(this, "Can not send yet"); //$NON-NLS-1$ + if (this.mode == ConnectionMode.OFF) + throw ConnectionMessages.throwNotConnected(this, message); + + message.setState(State.PENDING, State.PENDING.name()); + + this.messageQueue.add(message); + this.messageQueue.add(message); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java new file mode 100644 index 000000000..54579f7b9 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +/** + * @author Robert von Burg + */ +public interface CommunicationEndpoint { + + public void configure(CommunicationConnection connection, IoMessageVisitor converter); + + public void start(); + + public void stop(); + + public void reset(); + + public String getLocalUri(); + + public String getRemoteUri(); + + public void send(IoMessage message) throws Exception; + + public void simulate(IoMessage message) throws Exception; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java new file mode 100644 index 000000000..465477567 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +/** + * Base Exception for exceptions thrown by the communication classes + * + * @author Robert von Burg + */ +public class ConnectionException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } + + public ConnectionException(String message) { + super(message); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java new file mode 100644 index 000000000..c5bfd51a3 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.communication; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * @author Robert von Burg + */ +@XmlRootElement(name = "ConnectionInfo") +@XmlAccessorType(XmlAccessType.FIELD) +public class ConnectionInfo { + + @XmlAttribute(name = "id") + private String id; + + @XmlAttribute(name = "localUri") + private String localUri; + + @XmlAttribute(name = "remoteUri") + private String remoteUri; + + @XmlAttribute(name = "mode") + private ConnectionMode mode; + + @XmlAttribute(name = "queueSize") + private int queueSize; + + @XmlAttribute(name = "state") + private ConnectionState state; + + @XmlAttribute(name = "stateMsg") + private String stateMsg; + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + /** + * @param id + * the id to set + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the localUri + */ + public String getLocalUri() { + return this.localUri; + } + + /** + * @param localUri + * the localUri to set + */ + public void setLocalUri(String localUri) { + this.localUri = localUri; + } + + /** + * @return the remoteUri + */ + public String getRemoteUri() { + return this.remoteUri; + } + + /** + * @param remoteUri + * the remoteUri to set + */ + public void setRemoteUri(String remoteUri) { + this.remoteUri = remoteUri; + } + + /** + * @return the mode + */ + public ConnectionMode getMode() { + return this.mode; + } + + /** + * @param mode + * the mode to set + */ + public void setMode(ConnectionMode mode) { + this.mode = mode; + } + + /** + * @return the state + */ + public ConnectionState getState() { + return this.state; + } + + /** + * @param state + * the state to set + */ + public void setState(ConnectionState state) { + this.state = state; + } + + /** + * @return the stateMsg + */ + public String getStateMsg() { + return this.stateMsg; + } + + /** + * @param stateMsg + * the stateMsg to set + */ + public void setStateMsg(String stateMsg) { + this.stateMsg = stateMsg; + } + + /** + * @return the queueSize + */ + public int getQueueSize() { + return this.queueSize; + } + + /** + * @param queueSize + * the queueSize to set + */ + public void setQueueSize(int queueSize) { + this.queueSize = queueSize; + } + + public static ConnectionInfo valueOf(CommunicationConnection connection) { + ConnectionInfo info = new ConnectionInfo(); + info.setId(connection.getId()); + info.setLocalUri(connection.getLocalUri()); + info.setRemoteUri(connection.getRemoteUri()); + info.setMode(connection.getMode()); + info.setState(connection.getState()); + info.setStateMsg(connection.getStateMsg()); + info.setQueueSize(connection.getQueueSize()); + return info; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java new file mode 100644 index 000000000..28f6baef1 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java @@ -0,0 +1,165 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * Helper class to thrown connection messages + * + * @author Robert von Burg + */ +public class ConnectionMessages { + + private static Logger logger = LoggerFactory.getLogger(ConnectionMessages.class); + + /** + * Utility class + */ + private ConnectionMessages() { + // + } + + /** + * Convenience method to throw an exception when an illegal {@link ConnectionState} change occurs + * + * @param current + * @param change + * + * @return + */ + public static ConnectionException throwIllegalConnectionState(ConnectionState current, ConnectionState change) { + String msg = "The connection with state {0} cannot be changed to {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, current.name(), change.name()); + ConnectionException e = new ConnectionException(msg); + return e; + } + + /** + * Convenience method to throw an exception when an invalid parameter is set in the configuration + * + * @param clazz + * @param parameterName + * @param parameterValue + * + * @return + */ + public static ConnectionException throwInvalidParameter(Class clazz, String parameterName, String parameterValue) { + String value; + if (parameterValue != null && !parameterValue.isEmpty()) + value = parameterValue; + else + value = StringHelper.NULL; + + String msg = "{0}: parameter ''{1}'' has invalid value ''{2}''"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, clazz.getSimpleName(), parameterName, value); + ConnectionException e = new ConnectionException(msg); + return e; + } + + /** + * Convenience method to throw an exception when an two conflicting parameters are activated + * + * @param clazz + * @param parameter1 + * @param parameter2 + * + * @return + */ + public static ConnectionException throwConflictingParameters(Class clazz, String parameter1, String parameter2) { + String msg = "{0} : The parameters {1} and {2} can not be both activated as they conflict"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, clazz.getSimpleName(), parameter1, parameter1); + ConnectionException e = new ConnectionException(msg); + return e; + } + + /** + * Convenience method to log a warning when a parameter is not set in the configuration + * + * @param clazz + * @param parameterName + * @param defValue + */ + public static void warnUnsetParameter(Class clazz, String parameterName, String defValue) { + if (logger.isDebugEnabled()) { + String msg = "{0}: parameter ''{1}'' is not set, using default value ''{2}''"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, clazz.getSimpleName(), parameterName, defValue); + Map properties = new HashMap<>(); + logger.warn(MessageFormat.format(msg, properties)); + } + } + + /** + * Convenience method to throw an exception when the connection is not yet configured + * + * @param connection + * @param message + */ + public static void assertConfigured(CommunicationConnection connection, String message) { + if (connection.getState() == ConnectionState.CREATED) { + String msg = "{0} : Not yet configured: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connection.getId(), message); + throw new ConnectionException(msg); + } + } + + /** + * Convenience method to throw an exception when the connection is not connected + * + * @param connection + * @param message + * + * @return + */ + public static ConnectionException throwNotConnected(CommunicationConnection connection, String message) { + String msg = "{0} : Not connected: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connection.getId(), message); + ConnectionException e = new ConnectionException(msg); + return e; + } + + /** + * Convenience method to throw an exception when the connection is not connected + * + * @param connection + * @param message + * + * @return + */ + public static ConnectionException throwNotConnected(CommunicationConnection connection, IoMessage message) { + String msg = "{0} : Not connected, can not send message with id {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connection.getId(), message.getId()); + ConnectionException e = new ConnectionException(msg); + return e; + } + + public static void assertLegalMessageVisitor(Class endpoint, + Class expectedVisitor, IoMessageVisitor actualVisitor) { + if (!(expectedVisitor.isAssignableFrom(actualVisitor.getClass()))) { + String msg = "{0} requires {1} but has received illegal type {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, endpoint.getName(), expectedVisitor.getName(), actualVisitor.getClass() + .getName()); + throw new ConnectionException(msg); + } + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java new file mode 100644 index 000000000..8cf2e2640 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java @@ -0,0 +1,75 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.io.IOException; + +/** + *

+ * The mode of an {@link CommunicationConnection} can be one of the following enum values. This makes it possible use + * the connection without starting connection and later starting the connection when required + *

+ * The modes have the following semantics: + *
    + *
  • OFF - the connection can only have states {@link ConnectionState#CREATED} and {@link ConnectionState#INITIALIZED} + * . Trying to use the connection will throw an exception
  • + *
  • ON - the connection can be used normally
  • + *
  • SIMULATION - the same as ON, with the difference that the connection should silently drop any work
  • + *
+ * + * @author Robert von Burg + */ +public enum ConnectionMode { + + /** + * Denotes that the {@link CommunicationConnection} is off. This means it cannot accept messages, process messages + * or do any other kind of work + */ + OFF { + @Override + public boolean isSimulation() { + return false; + } + }, + + /** + * Denotes that the {@link CommunicationConnection} is on. This means that the {@link CommunicationConnection} + * accepts and process messages. Any connections which need to be established will automatically be connected and + * re-established should an {@link IOException} occur + */ + ON { + @Override + public boolean isSimulation() { + return false; + } + }, + + /** + * Denotes that the {@link CommunicationConnection} is in simulation mode. Mostly this means that the + * {@link CommunicationConnection} accepts messages, but silently swallows them, instead of processing them + */ + SIMULATION { + @Override + public boolean isSimulation() { + return true; + } + }; + + /** + * @return true if the current mode is simulation, false otherwise + */ + public abstract boolean isSimulation(); +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java new file mode 100644 index 000000000..8445ca357 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +/** + * @author Robert von Burg + */ +public interface ConnectionObserver { + + public void notify(CommandKey key, IoMessage message); +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java new file mode 100644 index 000000000..13724331e --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +/** + *

+ * a {@link CommunicationConnection} undergoes a serious of state changes. These states can be viewed by a client to + * monitor the state of the connection + *

+ * The states have the following semantics: + *
    + *
  • CREATED - initial state
  • + *
  • INITIALIZED - the appropriate connection parameters are found
  • + *
  • CONNECTING - the connection is trying to build up a connection
  • + *
  • WAITING - the connection is waiting before retrying to connect
  • + *
  • CONNECTED - the connection has just been established
  • + *
  • IDLE - the connection is connected, but waiting for work
  • + *
  • WORKING - the connection is working
  • + *
  • BROKEN - the connection has lost the connection and is waiting before reconnecting, or another unknown failure + * occurred
  • + *
  • DISCONNECTED - the connection has been disconnected
  • + *
+ * + * @author Robert von Burg + */ +public enum ConnectionState { + + // initial + CREATED, + // configured and ready to connect + INITIALIZED, + // working + CONNECTING, + WAITING, + CONNECTED, + IDLE, + WORKING, + BROKEN, + // disconnected due to connection error or manual disconnect/stop + DISCONNECTED; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java new file mode 100644 index 000000000..3b98f1f17 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java @@ -0,0 +1,6 @@ +package ch.eitchnet.communication; + +public interface ConnectionStateObserver { + + public void notify(ConnectionState oldState, String oldStateMsg, ConnectionState newState, String newStateMsg); +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessage.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessage.java new file mode 100644 index 000000000..45d2ba77e --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessage.java @@ -0,0 +1,215 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; + +/** + *

+ * An {@link IoMessage} is the object containing the data to be transmitted in IO. Implementations of + * {@link CommunicationConnection} should implement sub classes of this method to hold the actual payload. + *

+ * + *

+ * This class also contains a {@link Map} to store transient meta data to the actual payload + *

+ * + * @author Robert von Burg + */ +public class IoMessage { + + private final String id; + private final CommandKey key; + private final String connectionId; + private Date updated; + private State state; + private String stateMsg; + private Map parameters; + + /** + * @param id + * @param key + * @param connectionId + */ + public IoMessage(String id, CommandKey key, String connectionId) { + this.id = id; + this.key = key; + this.connectionId = connectionId; + this.state = State.CREATED; + this.stateMsg = StringHelper.DASH; + this.updated = new Date(); + this.parameters = new HashMap<>(); + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + /** + * @return the key + */ + public CommandKey getKey() { + return this.key; + } + + /** + * @return the connectionId + */ + public String getConnectionId() { + return this.connectionId; + } + + /** + * @return the updated + */ + public Date getUpdated() { + return this.updated; + } + + /** + * Used for testing purposes only! + * + * @param date + */ + void setUpdated(Date date) { + this.updated = date; + } + + /** + * @return the state + */ + public State getState() { + return this.state; + } + + /** + * @return the stateMsg + */ + public String getStateMsg() { + return this.stateMsg; + } + + /** + * @param state + * the state + * @param stateMsg + * the stateMsg + */ + public void setState(State state, String stateMsg) { + this.state = state; + this.stateMsg = stateMsg; + this.updated = new Date(); + } + + @SuppressWarnings("unchecked") + public T getParam(String key) { + return (T) this.parameters.get(key); + } + + /** + * Add a transient parameter to this message + * + * @param key + * the key under which the parameter is to be stored + * @param value + * the value to store under the given key + */ + public void addParam(String key, Object value) { + this.parameters.put(key, value); + } + + /** + * Removes the parameter with the given key + * + * @param key + * The give of the parameter to be removed + * @return the removed value, or null if the object didn't exist + */ + @SuppressWarnings("unchecked") + public T removeParam(String key) { + return (T) this.parameters.remove(key); + } + + /** + * @return the set of parameter keys + */ + public Set getParamKeys() { + return this.parameters.keySet(); + } + + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(this.getClass().getSimpleName() + " [id="); + builder.append(this.id); + builder.append(", key="); + builder.append(this.key); + builder.append(", updated="); + builder.append(ISO8601FormatFactory.getInstance().formatDate(this.updated)); + builder.append(", state="); + builder.append(this.state); + if (StringHelper.isNotEmpty(this.stateMsg)) { + builder.append(", stateMsg="); + builder.append(this.stateMsg); + } + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + IoMessage other = (IoMessage) obj; + if (this.id == null) { + if (other.id != null) + return false; + } else if (!this.id.equals(other.id)) + return false; + return true; + } + + public enum State { + CREATED, // new + PENDING, // outbound + ACCEPTED, // inbound + DONE, // completed + FAILED, // completed + FATAL; // not sendable + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java new file mode 100644 index 000000000..ee0af8077 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.util.List; + +public interface IoMessageArchive { + + public int getMaxSize(); + + public void setMaxSize(int maxSize); + + public int getTrimSize(); + + public void setTrimSize(int trimSize); + + public int size(); + + public List getAll(); + + public List getBy(String connectionId); + + public List getBy(String connectionId, CommandKey key); + + public void clearArchive(); + + public void archive(IoMessage message); +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java new file mode 100644 index 000000000..cf7d11a54 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import ch.eitchnet.communication.IoMessage.State; + +/** + * @author Robert von Burg + */ +public class IoMessageStateObserver implements ConnectionObserver { + + private CommandKey key; + private String messageId; + private State state; + + public IoMessageStateObserver(CommandKey key, String messageId) { + this.key = key; + this.messageId = messageId; + } + + @Override + public void notify(CommandKey key, IoMessage message) { + if (this.key.equals(key) && message.getId().equals(this.messageId)) { + this.state = message.getState(); + + synchronized (this) { + notifyAll(); + } + } + } + + public State getState() { + return this.state; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java new file mode 100644 index 000000000..b5a08f9da --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.console.ConsoleMessageVisitor; + +/** + *

+ * Visitors to read and write {@link IoMessage} using different kind of endpoints. Different endpoints will require + * different ways of writing or reading message, thus this is not defined here. Known extensions are + * {@link ConsoleMessageVisitor}, {@link StreamMessageVisitor}. + *

+ * + *

+ * Concrete implementations must be thread safe! + *

+ * + * @author Robert von Burg + */ +public abstract class IoMessageVisitor { + + protected static final Logger logger = LoggerFactory.getLogger(IoMessageVisitor.class); + + protected CommunicationConnection connection; + + public void configure(CommunicationConnection connection) { + this.connection = connection; + } + + public void simulate(IoMessage ioMessage) { + // allow for subclasses to implement + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java new file mode 100644 index 000000000..00ad303b6 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java @@ -0,0 +1,121 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; + +public class SimpleMessageArchive implements IoMessageArchive { + + private int maxSize; + private int trimSize; + private TreeSet messageArchive; + + public SimpleMessageArchive() { + this(1000, 100); + } + + public SimpleMessageArchive(int maxSize, int trimSize) { + this.maxSize = maxSize; + this.trimSize = trimSize; + + this.messageArchive = new TreeSet<>(new Comparator() { + @Override + public int compare(IoMessage o1, IoMessage o2) { + return o1.getUpdated().compareTo(o2.getUpdated()); + } + }); + } + + @Override + public synchronized int getMaxSize() { + return this.maxSize; + } + + @Override + public synchronized void setMaxSize(int maxSize) { + this.maxSize = maxSize; + trim(); + } + + @Override + public synchronized int getTrimSize() { + return this.trimSize; + } + + @Override + public synchronized void setTrimSize(int trimSize) { + this.trimSize = trimSize; + } + + @Override + public synchronized int size() { + return this.messageArchive.size(); + } + + @Override + public synchronized List getAll() { + List all = new ArrayList<>(this.messageArchive); + return all; + } + + @Override + public synchronized List getBy(String connectionId) { + List all = new ArrayList<>(); + for (IoMessage msg : this.messageArchive) { + if (msg.getConnectionId().equals(connectionId)) + all.add(msg); + } + return all; + } + + @Override + public synchronized List getBy(String connectionId, CommandKey key) { + List all = new ArrayList<>(); + for (IoMessage msg : this.messageArchive) { + if (msg.getConnectionId().equals(connectionId) && msg.getKey().equals(key)) + all.add(msg); + } + return all; + } + + @Override + public synchronized void clearArchive() { + this.messageArchive.clear(); + } + + @Override + public synchronized void archive(IoMessage message) { + this.messageArchive.add(message); + trim(); + } + + protected void trim() { + if (this.messageArchive.size() <= this.maxSize) + return; + + Iterator iter = this.messageArchive.iterator(); + for (int i = 0; i <= this.trimSize; i++) { + if (iter.hasNext()) { + iter.next(); + iter.remove(); + } + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java new file mode 100644 index 000000000..894ebf883 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + *

+ * {@link IoMessageVisitor} to read or write using IO Streams. + *

+ * + *

+ * Concrete implementations must be thread safe! + *

+ * + * @author Robert von Burg + */ +public abstract class StreamMessageVisitor extends IoMessageVisitor { + + public abstract void visit(OutputStream outputStream, IoMessage message) throws Exception; + + public abstract IoMessage visit(InputStream inputStream) throws Exception; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java new file mode 100644 index 000000000..5dd90d451 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java @@ -0,0 +1,133 @@ +package ch.eitchnet.communication.chat; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.text.MessageFormat; +import java.util.Enumeration; + +import ch.eitchnet.utils.helper.StringHelper; + +public class Chat { + + public static void main(String[] args) { + + if (args.length < 4) + printIllegalArgsAndExit(args); + + if (args[0].equals("server")) { //$NON-NLS-1$ + startServer(args); + } else if (args[0].equals("client")) { //$NON-NLS-1$ + startClient(args); + } + } + + private static void startServer(String[] args) { + + // server + InetAddress host; + String hostS = args[1]; + try { + host = InetAddress.getByName(hostS); + } catch (UnknownHostException e1) { + System.err.println(MessageFormat.format("Illegal server address: {0}", hostS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + boolean isHostAddress = false; + try { + for (Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); interfaces + .hasMoreElements();) { + NetworkInterface iface = interfaces.nextElement(); + for (Enumeration addresses = iface.getInetAddresses(); addresses.hasMoreElements();) { + InetAddress inetAddress = addresses.nextElement(); + if (inetAddress.getHostAddress().equals(host.getHostAddress())) + isHostAddress = true; + } + } + } catch (SocketException e) { + System.err.println("Oops: " + e.getMessage()); //$NON-NLS-1$ + } + + if (!isHostAddress) { + System.err.println(MessageFormat.format("The address {0} is not an address of this host!", hostS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + } + + // port + int port; + String portS = args[2]; + try { + port = Integer.parseInt(portS); + } catch (NumberFormatException e) { + System.err.println(MessageFormat.format("Illegal port: {0}", portS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + if (port < 1 || port > 65535) { + System.err.println(MessageFormat.format("Illegal port: {0}", port)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + + // username + String username = args[3]; + + // start + ChatServer chatServer = new ChatServer(host, port, username); + chatServer.start(); + } + + private static void startClient(String[] args) { + + // server + InetAddress host; + String hostS = args[1]; + try { + host = InetAddress.getByName(hostS); + } catch (UnknownHostException e1) { + System.err.println(MessageFormat.format("Illegal server address: {0}", hostS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + + // port + int port; + String portS = args[2]; + try { + port = Integer.parseInt(portS); + } catch (NumberFormatException e) { + System.err.println(MessageFormat.format("Illegal port: {0}", portS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + if (port < 1 || port > 65535) { + System.err.println(MessageFormat.format("Illegal port: {0}", port)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + + // username + String username = args[3]; + + // start + ChatClient chatClient = new ChatClient(host, port, username); + chatClient.start(); + } + + private static void printIllegalArgsAndExit(String[] args) { + System.err.print("Illegal arguments: "); //$NON-NLS-1$ + if (args.length == 0) { + System.err.print("(none)"); //$NON-NLS-1$ + } else { + for (String arg : args) { + System.err.print(arg + StringHelper.SPACE); + } + } + System.err.println(); + System.err.println("Usage: java ...Chat server "); //$NON-NLS-1$ + System.err.println("Usage: java ...Chat client "); //$NON-NLS-1$ + System.exit(1); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java new file mode 100644 index 000000000..5f0807281 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java @@ -0,0 +1,92 @@ +package ch.eitchnet.communication.chat; + +import static ch.eitchnet.communication.chat.ChatMessageVisitor.inboundKey; +import static ch.eitchnet.communication.chat.ChatMessageVisitor.outboundKey; + +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +import ch.eitchnet.communication.CommandKey; +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.ConnectionMode; +import ch.eitchnet.communication.ConnectionObserver; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.ConnectionStateObserver; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.tcpip.ClientSocketEndpoint; +import ch.eitchnet.communication.tcpip.SocketEndpointConstants; +import ch.eitchnet.communication.tcpip.SocketMessageVisitor; + +public class ChatClient implements ConnectionObserver, ConnectionStateObserver { + + private static final String id = "ChatClient"; //$NON-NLS-1$ + private InetAddress host; + private int port; + private String username; + private boolean connected; + + public ChatClient(InetAddress host, int port, String username) { + this.host = host; + this.port = port; + this.username = username; + } + + public void start() { + ConnectionMode mode = ConnectionMode.ON; + + Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_RETRY, "10000"); //$NON-NLS-1$ + parameters.put(SocketEndpointConstants.PARAMETER_USE_TIMEOUT, Boolean.FALSE.toString()); + parameters.put(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_ADDRESS, this.host.getHostAddress()); + parameters.put(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_PORT, Integer.toString(this.port)); + parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.FALSE.toString()); + + SocketMessageVisitor messageVisitor = new ChatMessageVisitor(id); + ClientSocketEndpoint endpoint = new ClientSocketEndpoint(); + + CommunicationConnection connection = new CommunicationConnection(id, mode, parameters, endpoint, messageVisitor); + connection.addConnectionObserver(outboundKey, this); + connection.addConnectionObserver(inboundKey, this); + connection.addConnectionStateObserver(this); + connection.configure(); + connection.start(); + + while (!this.connected) { + synchronized (this) { + try { + this.wait(2000l); + } catch (InterruptedException e) { + System.err.println("oops: " + e.getMessage()); //$NON-NLS-1$ + } + } + } + + System.out.println("Connected. Send messages to user:"); //$NON-NLS-1$ + while (true) { + @SuppressWarnings("resource") + Scanner in = new Scanner(System.in); + //System.out.print(this.username + ": "); + String line = in.nextLine(); + connection.send(ChatIoMessage.msg(outboundKey, id, this.username, line)); + } + } + + @Override + public void notify(CommandKey key, IoMessage message) { + if (key.equals(inboundKey)) { + ChatIoMessage chatIoMessage = (ChatIoMessage) message; + System.out.println(chatIoMessage.getChatMsg()); + } + } + + @Override + public void notify(ConnectionState oldState, String oldStateMsg, ConnectionState newState, String newStateMsg) { + this.connected = newState == ConnectionState.CONNECTED || newState == ConnectionState.IDLE + || newState == ConnectionState.WORKING; + synchronized (this) { + notifyAll(); + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java new file mode 100644 index 000000000..a9bf851eb --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java @@ -0,0 +1,31 @@ +package ch.eitchnet.communication.chat; + +import ch.eitchnet.communication.CommandKey; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.utils.helper.StringHelper; + +public class ChatIoMessage extends IoMessage { + + private String chatMsg; + + public ChatIoMessage(String id, CommandKey key, String connectionId) { + super(id, key, connectionId); + } + + public String getChatMsg() { + return this.chatMsg; + } + + public void setChatMsg(String chagMsg) { + this.chatMsg = chagMsg; + } + + public static ChatIoMessage msg(CommandKey key, String connId, String username, String msg) { + + String line = username + StringHelper.COLON + StringHelper.SPACE + msg; + + ChatIoMessage chatIoMessage = new ChatIoMessage(StringHelper.getUniqueId(), key, connId); + chatIoMessage.setChatMsg(line); + return chatIoMessage; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java new file mode 100644 index 000000000..29667480f --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java @@ -0,0 +1,45 @@ +package ch.eitchnet.communication.chat; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.InputStreamReader; + +import ch.eitchnet.communication.CommandKey; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.tcpip.SocketMessageVisitor; +import ch.eitchnet.utils.helper.StringHelper; + +public class ChatMessageVisitor extends SocketMessageVisitor { + + public static final CommandKey inboundKey = CommandKey.key("server", "msg"); //$NON-NLS-1$//$NON-NLS-2$ + public static final CommandKey outboundKey = CommandKey.key("client", "msg"); //$NON-NLS-1$ //$NON-NLS-2$ + + public ChatMessageVisitor(String connectionId) { + super(connectionId); + } + + @Override + public IoMessage visit(DataInputStream inputStream, DataOutputStream outputStream) throws Exception { + + @SuppressWarnings("resource") + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String line = bufferedReader.readLine(); + if (line == null) { + bufferedReader.close(); + return null; + } + + ChatIoMessage chatIoMessage = new ChatIoMessage(StringHelper.getUniqueId(), inboundKey, this.connectionId); + chatIoMessage.setChatMsg(line); + return chatIoMessage; + } + + @Override + public void visit(DataInputStream inputStream, DataOutputStream outputStream, IoMessage message) throws Exception { + ChatIoMessage chatIoMessage = (ChatIoMessage) message; + outputStream.writeChars(chatIoMessage.getChatMsg()); + outputStream.writeChar('\n'); + outputStream.flush(); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java new file mode 100644 index 000000000..fbd9b3bde --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java @@ -0,0 +1,93 @@ +package ch.eitchnet.communication.chat; + +import static ch.eitchnet.communication.chat.ChatMessageVisitor.inboundKey; +import static ch.eitchnet.communication.chat.ChatMessageVisitor.outboundKey; + +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +import ch.eitchnet.communication.CommandKey; +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.ConnectionMode; +import ch.eitchnet.communication.ConnectionObserver; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.ConnectionStateObserver; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.tcpip.ServerSocketEndpoint; +import ch.eitchnet.communication.tcpip.SocketEndpointConstants; +import ch.eitchnet.communication.tcpip.SocketMessageVisitor; + +public class ChatServer implements ConnectionObserver, ConnectionStateObserver { + + private static final String id = "ChatServer"; //$NON-NLS-1$ + private InetAddress address; + private int port; + private String username; + private boolean connected; + + public ChatServer(InetAddress address, int port, String username) { + this.address = address; + this.port = port; + this.username = username; + } + + public void start() { + ConnectionMode mode = ConnectionMode.ON; + + Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_RETRY, "10000"); //$NON-NLS-1$ + parameters.put(SocketEndpointConstants.PARAMETER_USE_TIMEOUT, Boolean.FALSE.toString()); + parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_ADDRESS, this.address.getHostAddress()); + parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT, Integer.toString(this.port)); + parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.FALSE.toString()); + parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.FALSE.toString()); + + SocketMessageVisitor messageVisitor = new ChatMessageVisitor(id); + ServerSocketEndpoint endpoint = new ServerSocketEndpoint(); + + CommunicationConnection connection = new CommunicationConnection(id, mode, parameters, endpoint, messageVisitor); + connection.addConnectionObserver(outboundKey, this); + connection.addConnectionObserver(inboundKey, this); + connection.addConnectionStateObserver(this); + connection.configure(); + connection.start(); + + while (!this.connected) { + synchronized (this) { + try { + this.wait(2000l); + } catch (InterruptedException e) { + System.err.println("oops: " + e.getMessage()); //$NON-NLS-1$ + } + } + } + + System.out.println("Connected. Send messages to user:"); //$NON-NLS-1$ + while (true) { + @SuppressWarnings("resource") + Scanner in = new Scanner(System.in); + //System.out.print(this.username + ": "); + String line = in.nextLine(); + connection.send(ChatIoMessage.msg(outboundKey, id, this.username, line)); + } + } + + @Override + public void notify(CommandKey key, IoMessage message) { + if (key.equals(inboundKey)) { + ChatIoMessage chatIoMessage = (ChatIoMessage) message; + System.out.println(chatIoMessage.getChatMsg()); + } + } + + @Override + public void notify(ConnectionState oldState, String oldStateMsg, ConnectionState newState, String newStateMsg) { + this.connected = newState == ConnectionState.CONNECTED || newState == ConnectionState.IDLE + || newState == ConnectionState.WORKING; + synchronized (this) { + notifyAll(); + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java new file mode 100644 index 000000000..26bcf14e8 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java @@ -0,0 +1,83 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication.console; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; + +/** + * @author Robert von Burg + */ +public class ConsoleEndpoint implements CommunicationEndpoint { + + private static final Logger logger = LoggerFactory.getLogger(ConsoleEndpoint.class); + private CommunicationConnection connection; + private ConsoleMessageVisitor messageVisitor; + + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + this.connection = connection; + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), ConsoleMessageVisitor.class, messageVisitor); + this.messageVisitor = (ConsoleMessageVisitor) messageVisitor; + } + + @Override + public void start() { + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + + @Override + public void stop() { + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); + } + + @Override + public void reset() { + this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); + } + + @Override + public String getLocalUri() { + return "console"; //$NON-NLS-1$ + } + + @Override + public String getRemoteUri() { + return "console"; //$NON-NLS-1$ + } + + @Override + public void send(IoMessage message) throws Exception { + this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); + try { + this.messageVisitor.visit(logger, message); + } finally { + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + } + + @Override + public void simulate(IoMessage message) throws Exception { + send(message); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java new file mode 100644 index 000000000..ccad0ef30 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication.console; + +import org.slf4j.Logger; + +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; + +public abstract class ConsoleMessageVisitor extends IoMessageVisitor { + + public abstract void visit(Logger logger, IoMessage message) throws Exception; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java new file mode 100644 index 000000000..459bf36ab --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java @@ -0,0 +1,251 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionException; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; +import ch.eitchnet.communication.StreamMessageVisitor; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * An {@link CommunicationEndpoint} which writes and/or reads from a designated file + * + * @author Robert von Burg + */ +public class FileEndpoint implements CommunicationEndpoint, Runnable { + + public static final String ENDPOINT_MODE = "endpointMode"; //$NON-NLS-1$ + public static final String INBOUND_FILENAME = "inboundFilename"; //$NON-NLS-1$ + public static final String OUTBOUND_FILENAME = "outboundFilename"; //$NON-NLS-1$ + public static final long POLL_TIME = 1000l; + + private static final Logger logger = LoggerFactory.getLogger(FileEndpoint.class); + + private CommunicationConnection connection; + + private FileEndpointMode endpointMode; + private String inboundFilename; + private String outboundFilename; + private Thread thread; + private boolean run = false; + private StreamMessageVisitor messageVisitor; + + /** + * {@link FileEndpoint} needs the following parameters on the configuration to be initialized + *
    + *
  • outboundFilename: the file name where the {@link IoMessage} contents are written to. The value may contain + * {@link System#getProperty(String)} place holders which will be evaluated
  • + *
+ */ + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + this.connection = connection; + + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), StreamMessageVisitor.class, messageVisitor); + this.messageVisitor = (StreamMessageVisitor) messageVisitor; + + configure(); + } + + private void configure() { + Map parameters = this.connection.getParameters(); + + String endpointModeS = parameters.get(ENDPOINT_MODE); + if (StringHelper.isEmpty(endpointModeS)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); + } + try { + this.endpointMode = FileEndpointMode.valueOf(endpointModeS); + } catch (Exception e) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); + } + + if (this.endpointMode.isRead()) { + this.inboundFilename = parameters.get(INBOUND_FILENAME); + if (StringHelper.isEmpty(this.inboundFilename)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, INBOUND_FILENAME, + this.inboundFilename); + } + } + + if (this.endpointMode.isWrite()) { + this.outboundFilename = parameters.get(OUTBOUND_FILENAME); + if (StringHelper.isEmpty(this.outboundFilename)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, OUTBOUND_FILENAME, + this.outboundFilename); + } + } + } + + @Override + public String getLocalUri() { + return new File(this.inboundFilename).getAbsolutePath(); + } + + @Override + public String getRemoteUri() { + return new File(this.outboundFilename).getAbsolutePath(); + } + + @Override + public void start() { + if (this.endpointMode.isRead()) { + this.thread = new Thread(this, new File(this.inboundFilename).getName()); + this.run = true; + this.thread.start(); + } + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + + @Override + public void stop() { + stopThread(); + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); + } + + @Override + public void reset() { + stopThread(); + configure(); + this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); + } + + private void stopThread() { + this.run = false; + if (this.thread != null) { + try { + this.thread.interrupt(); + this.thread.join(2000l); + } catch (Exception e) { + logger.error(MessageFormat.format("Error while interrupting thread: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } + + this.thread = null; + } + } + + @Override + public void send(IoMessage message) throws Exception { + if (!this.endpointMode.isWrite()) { + String msg = "FileEnpoint mode is {0} and thus write is not allowed!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.endpointMode); + throw new ConnectionException(msg); + } + + this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); + + // open the stream + try (FileOutputStream outputStream = new FileOutputStream(this.outboundFilename, false)) { + + // write the message using the visitor + this.messageVisitor.visit(outputStream, message); + + } finally { + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + } + + @Override + public void simulate(IoMessage message) throws Exception { + this.messageVisitor.simulate(message); + } + + @Override + public void run() { + + File file = new File(this.inboundFilename); + + long lastModified = 0l; + + logger.info("Starting..."); //$NON-NLS-1$ + while (this.run) { + + try { + + if (file.canRead()) { + long tmpModified = file.lastModified(); + if (tmpModified > lastModified) { + + logger.info(MessageFormat.format("Handling file {0}", file.getAbsolutePath())); //$NON-NLS-1$ + + this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); + + // file is changed + lastModified = tmpModified; + + // read the file + handleFile(file); + + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + } + + if (this.run) { + this.connection.notifyStateChange(ConnectionState.WAITING, ConnectionState.WAITING.toString()); + try { + synchronized (this) { + this.wait(POLL_TIME); + } + } catch (InterruptedException e) { + this.run = false; + logger.info("Interrupted!"); //$NON-NLS-1$ + } + } + + } catch (Exception e) { + logger.error(MessageFormat.format("Error reading file: {0}", file.getAbsolutePath())); //$NON-NLS-1$ + logger.error(e.getMessage(), e); + + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } + } + } + + /** + * Reads the file and handle using {@link StreamMessageVisitor} + * + * @param file + * the {@link File} to read + */ + protected void handleFile(File file) throws Exception { + + try (InputStream inputStream = new FileInputStream(file)) { + + // convert the object to an integration message + IoMessage message = this.messageVisitor.visit(inputStream); + + // and forward to the connection + if (message != null) { + this.connection.handleNewMessage(message); + } + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java new file mode 100644 index 000000000..842b0b00e --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication.file; + +public enum FileEndpointMode { + READ(true, false), WRITE(false, true), READ_WRITE(true, true); + private boolean read; + private boolean write; + + private FileEndpointMode(boolean read, boolean write) { + this.read = read; + this.write = write; + } + + public boolean isRead() { + return this.read; + } + + public boolean isWrite() { + return this.write; + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java new file mode 100644 index 000000000..7ea688be1 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -0,0 +1,538 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication.tcpip; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.text.MessageFormat; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionException; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessage.State; +import ch.eitchnet.communication.IoMessageVisitor; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

+ * This {@link CommunicationEndpoint} is an abstract implementation with everything needed to connect through a + * {@link Socket} to a remote server which is listening for incoming {@link Socket} connections. This {@link Socket} + * endpoint can send messages to the remote side, as well as receive messages from the remote side + *

+ *

+ * This endpoint is maintained as a client connection. This means that this endpoint opens the {@link Socket} to the + * remote server + *

+ * + * @author Robert von Burg + */ +public class ClientSocketEndpoint implements CommunicationEndpoint { + + protected static final Logger logger = LoggerFactory.getLogger(ClientSocketEndpoint.class); + + // state variables + private boolean connected; + private boolean closed; + private long lastConnect; + private boolean useTimeout; + private int timeout; + private long retry; + private boolean clearOnConnect; + private boolean connectOnStart; + private boolean closeAfterSend; + + // remote address + private String remoteInputAddressS; + private int remoteInputPort; + private String localOutputAddressS; + private int localOutputPort; + + private InetAddress remoteInputAddress; + private InetAddress localOutputAddress; + + // connection + private Socket socket; + + protected DataOutputStream outputStream; + protected DataInputStream inputStream; + + protected CommunicationConnection connection; + + protected SocketMessageVisitor messageVisitor; + + /** + * Default constructor + */ + public ClientSocketEndpoint() { + this.connected = false; + this.closed = true; + } + + /** + * Checks the state of the connection and returns true if {@link Socket} is connected and ready for transmission, + * false otherwise + * + * @return true if {@link Socket} is connected and ready for transmission, false otherwise + */ + protected boolean checkConnection() { + return !this.closed + && this.connected + && (this.socket != null && !this.socket.isClosed() && this.socket.isBound() + && this.socket.isConnected() && !this.socket.isInputShutdown() && !this.socket + .isOutputShutdown()); + } + + /** + * Establishes a {@link Socket} connection to the remote server. This method blocks till a connection is + * established. In the event of a connection failure, the method waits a configured time before retrying + */ + protected void openConnection() { + + ConnectionState state = this.connection.getState(); + + // do not allow connecting if state is + // - CREATED + // - CONNECTING + // - WAITING + // - CLOSED + if (state == ConnectionState.CREATED || state == ConnectionState.CONNECTING || state == ConnectionState.WAITING + || state == ConnectionState.DISCONNECTED) { + + ConnectionMessages.throwIllegalConnectionState(state, ConnectionState.CONNECTING); + } + + // first close the connection + closeConnection(); + + while (!this.connected && !this.closed) { + try { + + this.connection.notifyStateChange(ConnectionState.CONNECTING, ConnectionState.CONNECTING.toString()); + + // only try in proper intervals + long currentTime = System.currentTimeMillis(); + long timeDifference = currentTime - this.lastConnect; + if (timeDifference < this.retry) { + long wait = (this.retry - timeDifference); + logger.info(MessageFormat.format("Waiting: {0}ms", wait)); //$NON-NLS-1$ + + this.connection.notifyStateChange(ConnectionState.WAITING, ConnectionState.WAITING.toString()); + Thread.sleep(wait); + this.connection + .notifyStateChange(ConnectionState.CONNECTING, ConnectionState.CONNECTING.toString()); + } + + // don't try and connect if we are closed! + if (this.closed) { + logger.error("The connection has been closed and can not be connected"); //$NON-NLS-1$ + closeConnection(); + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, null); + return; + } + + // keep track of the time of this connection attempt + this.lastConnect = System.currentTimeMillis(); + + // open the socket + if (this.localOutputAddress != null) { + String msg = "Opening connection to {0}:{1} from {2}:{3}..."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.remoteInputAddress.getHostAddress(), + Integer.toString(this.remoteInputPort), this.localOutputAddress.getHostAddress(), + Integer.toString(this.localOutputPort))); + this.socket = new Socket(this.remoteInputAddress, this.remoteInputPort, this.localOutputAddress, + this.localOutputPort); + } else { + String msg = "Opening connection to {0}:{1}..."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.remoteInputAddress.getHostAddress(), + Integer.toString(this.remoteInputPort))); + this.socket = new Socket(this.remoteInputAddress, this.remoteInputPort); + } + + // configure the socket + if (logger.isDebugEnabled()) { + String msg = "BufferSize (send/read): {0} / {1} SoLinger: {2} TcpNoDelay: {3}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.socket.getSendBufferSize(), + this.socket.getReceiveBufferSize(), this.socket.getSoLinger(), this.socket.getTcpNoDelay())); + } + //outputSocket.setSendBufferSize(1); + //outputSocket.setSoLinger(true, 0); + //outputSocket.setTcpNoDelay(true); + + // activate connection timeout + if (this.useTimeout) { + this.socket.setSoTimeout(this.timeout); + } + + // get the streams + this.outputStream = new DataOutputStream(this.socket.getOutputStream()); + this.inputStream = new DataInputStream(this.socket.getInputStream()); + + if (this.clearOnConnect) { + // clear the input stream + int available = this.inputStream.available(); + logger.info(MessageFormat.format("clearOnConnect: skipping {0} bytes.", available)); //$NON-NLS-1$ + this.inputStream.skip(available); + } + + String msg = "Connected {0}: {1}:{2} with local side {3}:{4}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.connection.getId(), this.remoteInputAddressS, + Integer.toString(this.remoteInputPort), this.socket.getLocalAddress().getHostAddress(), + Integer.toString(this.socket.getLocalPort()))); + + // we are connected! + this.connection.notifyStateChange(ConnectionState.CONNECTED, ConnectionState.CONNECTED.toString()); + this.connected = true; + + } catch (InterruptedException e) { + logger.info("Interrupted!"); //$NON-NLS-1$ + this.closed = true; + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, null); + } catch (Exception e) { + String msg = "Error while connecting to {0}:{1}: {2}"; //$NON-NLS-1$ + logger.error( + MessageFormat.format(msg, this.remoteInputAddressS, Integer.toString(this.remoteInputPort)), + e.getMessage()); + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } + } + } + + /** + * closes the connection HARD by calling close() on the streams and socket. All Exceptions are caught to make sure + * that the connections are cleaned up + */ + protected void closeConnection() { + + this.connected = false; + this.connection.notifyStateChange(ConnectionState.BROKEN, null); + + if (this.outputStream != null) { + try { + this.outputStream.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing OutputStream: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.outputStream = null; + } + } + + if (this.inputStream != null) { + try { + this.inputStream.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing InputStream: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.inputStream = null; + } + } + + if (this.socket != null) { + try { + this.socket.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing OutputSocket: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.socket = null; + } + + String msg = "Socket closed for connection {0} at remote input address {1}:{2}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.connection.getId(), this.remoteInputAddressS, + Integer.toString(this.remoteInputPort))); + } + } + + /** + *

+ * Configures this {@link ClientSocketEndpoint} + *

+ * gets the parameter map from the connection and reads the following parameters from the map: + *
    + *
  • remoteInputAddress - the IP or Hostname of the remote server
  • + *
  • remoteInputPort - the port to which the socket should be established
  • + *
  • localOutputAddress - the IP or Hostname of the local server (if null, then the network layer will decide)
  • + *
  • localOutputPort - the local port from which the socket should go out of (if null, then the network layer will + * decide)
  • + *
  • retry - a configured retry wait time. Default is {@link SocketEndpointConstants#RETRY}
  • + *
  • timeout - the timeout after which an idle socket is deemed dead. Default is + * {@link SocketEndpointConstants#TIMEOUT}
  • + *
  • useTimeout - if true, then the timeout is activated, otherwise it is. default is + * {@link SocketEndpointConstants#USE_TIMEOUT}
  • + *
  • clearOnConnect - if true, then the after a successful connect the input is cleared by discarding all + * available bytes. This can be useful in cases where the channel is clogged with stale data. default is + * {@link SocketEndpointConstants#CLEAR_ON_CONNECT}
  • + *
  • connectOnStart - if true, then when the connection is started, the connection to the remote address is + * attempted. default is {@link SocketEndpointConstants#CONNECT_ON_START} + *
+ * + * @see CommunicationEndpoint#configure(CommunicationConnection, IoMessageVisitor) + */ + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + if (this.connection != null && connection.getState().compareTo(ConnectionState.INITIALIZED) > 0) { + String msg = "{0}:{1} already configured."; //$NON-NLS-1$ + logger.warn(MessageFormat.format(msg, this.getClass().getSimpleName(), connection.getId())); + return; + } + + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), SocketMessageVisitor.class, messageVisitor); + this.messageVisitor = (SocketMessageVisitor) messageVisitor; + this.connection = connection; + configure(); + } + + private void configure() { + Map parameters = this.connection.getParameters(); + + this.remoteInputAddressS = parameters.get(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_ADDRESS); + String remoteInputPortS = parameters.get(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_PORT); + this.localOutputAddressS = parameters.get(SocketEndpointConstants.PARAMETER_LOCAL_OUTPUT_ADDRESS); + String localOutputPortS = parameters.get(SocketEndpointConstants.PARAMETER_LOCAL_OUTPUT_PORT); + + // parse remote input Address to InetAddress object + try { + this.remoteInputAddress = InetAddress.getByName(this.remoteInputAddressS); + } catch (UnknownHostException e) { + throw ConnectionMessages.throwInvalidParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_REMOTE_INPUT_ADDRESS, this.remoteInputAddressS); + } + + // parse remote input address port to integer + try { + this.remoteInputPort = Integer.parseInt(remoteInputPortS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_REMOTE_INPUT_PORT, remoteInputPortS); + } + + // if local output address is not set, then we will use the localhost InetAddress + if (this.localOutputAddressS == null || this.localOutputAddressS.length() == 0) { + logger.debug("No localOutputAddress set. Using localhost"); //$NON-NLS-1$ + } else { + + // parse local output address name to InetAddress object + try { + this.localOutputAddress = InetAddress.getByName(this.localOutputAddressS); + } catch (UnknownHostException e) { + String msg = "The host name ''{0}'' can not be evaluated to an internet address"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.localOutputAddressS); + throw new ConnectionException(msg, e); + } + + // parse local output address port to integer + try { + this.localOutputPort = Integer.parseInt(localOutputPortS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_LOCAL_OUTPUT_PORT, localOutputPortS); + } + } + + // configure retry wait time + String retryS = parameters.get(SocketEndpointConstants.PARAMETER_RETRY); + if (retryS == null || retryS.length() == 0) { + ConnectionMessages.warnUnsetParameter(ClientSocketEndpoint.class, SocketEndpointConstants.PARAMETER_RETRY, + String.valueOf(SocketEndpointConstants.RETRY)); + this.retry = SocketEndpointConstants.RETRY; + } else { + try { + this.retry = Long.parseLong(retryS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_RETRY, retryS); + } + } + + // configure connect on start + String connectOnStartS = parameters.get(SocketEndpointConstants.PARAMETER_CONNECT_ON_START); + if (StringHelper.isNotEmpty(connectOnStartS)) { + this.connectOnStart = StringHelper.parseBoolean(connectOnStartS); + } else { + ConnectionMessages.warnUnsetParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_CONNECT_ON_START, + String.valueOf(SocketEndpointConstants.CONNECT_ON_START)); + this.connectOnStart = SocketEndpointConstants.CONNECT_ON_START; + } + + // configure closeAfterSend + String closeAfterSendS = parameters.get(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND); + if (StringHelper.isNotEmpty(closeAfterSendS)) { + this.closeAfterSend = StringHelper.parseBoolean(closeAfterSendS); + } else { + ConnectionMessages.warnUnsetParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, + String.valueOf(SocketEndpointConstants.CLOSE_AFTER_SEND)); + this.closeAfterSend = SocketEndpointConstants.CLOSE_AFTER_SEND; + } + + // configure if timeout on connection should be activated + String useTimeoutS = parameters.get(SocketEndpointConstants.PARAMETER_USE_TIMEOUT); + if (useTimeoutS == null || useTimeoutS.length() == 0) { + ConnectionMessages.warnUnsetParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_USE_TIMEOUT, String.valueOf(SocketEndpointConstants.USE_TIMEOUT)); + this.useTimeout = SocketEndpointConstants.USE_TIMEOUT; + } else { + this.useTimeout = Boolean.parseBoolean(useTimeoutS); + } + + if (this.useTimeout) { + // configure timeout on connection + String timeoutS = parameters.get(SocketEndpointConstants.PARAMETER_TIMEOUT); + if (timeoutS == null || timeoutS.length() == 0) { + ConnectionMessages.warnUnsetParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_TIMEOUT, String.valueOf(SocketEndpointConstants.TIMEOUT)); + this.timeout = SocketEndpointConstants.TIMEOUT; + } else { + try { + this.timeout = Integer.parseInt(timeoutS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_TIMEOUT, timeoutS); + } + } + } + + // configure if the connection should be cleared on connect + String clearOnConnectS = parameters.get(SocketEndpointConstants.PARAMETER_CLEAR_ON_CONNECT); + if (clearOnConnectS == null || clearOnConnectS.length() == 0) { + ConnectionMessages.warnUnsetParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_CLEAR_ON_CONNECT, + String.valueOf(SocketEndpointConstants.CLEAR_ON_CONNECT)); + this.clearOnConnect = SocketEndpointConstants.CLEAR_ON_CONNECT; + } else { + this.clearOnConnect = Boolean.parseBoolean(clearOnConnectS); + } + } + + /** + * @return the uri as String to which this {@link ClientSocketEndpoint} is locally bound to + */ + @Override + public String getLocalUri() { + if (this.socket != null) { + InetAddress localAddress = this.socket.getLocalAddress(); + return localAddress.getHostAddress() + StringHelper.COLON + this.socket.getLocalPort(); + } else if (this.localOutputAddress != null) { + return this.localOutputAddress.getHostAddress() + StringHelper.COLON + this.localOutputPort; + } + + return "0.0.0.0:0"; //$NON-NLS-1$ + } + + /** + * @return the uri as String to which this {@link ClientSocketEndpoint} is connecting to + */ + @Override + public String getRemoteUri() { + if (this.socket != null) { + InetAddress remoteAddress = this.socket.getInetAddress(); + return remoteAddress.getHostAddress() + StringHelper.COLON + this.socket.getPort(); + } else if (this.remoteInputAddress != null) { + return this.remoteInputAddress.getHostAddress() + StringHelper.COLON + this.remoteInputPort; + } + + return this.remoteInputAddressS + StringHelper.COLON + this.remoteInputPort; + } + + /** + * Allows this end point to connect and then opens the connection to the defined remote server + * + * @see CommunicationEndpoint#start() + */ + @Override + public void start() { + if (!this.closed) { + logger.warn(MessageFormat.format("CommunicationConnection {0} already started.", this.connection.getId())); //$NON-NLS-1$ + } else { + // logger.info(MessageFormat.format("Enabling connection {0}...", this.connection.getId())); //$NON-NLS-1$ + this.closed = false; + this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); + if (this.connectOnStart) { + openConnection(); + } + } + } + + /** + * Closes this connection and disallows this end point to reconnect + * + * @see CommunicationEndpoint#stop() + */ + @Override + public void stop() { + this.closed = true; + + closeConnection(); + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); + + logger.info(MessageFormat.format("Disabled connection {0}.", this.connection.getId())); //$NON-NLS-1$ + } + + @Override + public void reset() { + this.closed = true; + closeConnection(); + this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); + } + + @Override + public void simulate(IoMessage message) throws Exception { + this.messageVisitor.simulate(message); + } + + @Override + public void send(IoMessage message) throws Exception { + + while (!this.closed && message.getState() == State.PENDING) { + try { + + // open the connection + if (!checkConnection()) + openConnection(); + + // read and write to the client socket + this.messageVisitor.visit(this.inputStream, this.outputStream, message); + + message.setState(State.DONE, State.DONE.name()); + + } catch (Exception e) { + if (this.closed) { + logger.warn("Socket has been closed!"); //$NON-NLS-1$ + message.setState(State.FATAL, "Socket has been closed!"); //$NON-NLS-1$ + } else { + closeConnection(); + logger.error(e.getMessage(), e); + message.setState(State.FATAL, e.getLocalizedMessage()); + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } + } finally { + if (this.closeAfterSend && !this.closed) { + closeConnection(); + } + } + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java new file mode 100644 index 000000000..57f6399ab --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -0,0 +1,594 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication.tcpip; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.BindException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.text.MessageFormat; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionException; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

+ * This {@link CommunicationEndpoint} is an abstract implementation with everything needed to start a {@link Socket} + * server which waits for a request from a single client + *

+ *

+ * This end point only allows a single connection at a time and implements all exception handling for opening and + * closing a {@link Socket} connection + *

+ * + * @see ServerSocketEndpoint#configure(CommunicationConnection, IoMessageVisitor) for details on configuring the end + * point + * + * @author Robert von Burg + */ +public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { + + protected static final Logger logger = LoggerFactory.getLogger(ServerSocketEndpoint.class); + + private Thread serverThread; + + // state variables + private boolean connected; + private boolean closed; + private boolean fatal; + private long lastConnect; + private boolean useTimeout; + private int timeout; + private long retry; + private boolean clearOnConnect; + + // address + private String localInputAddressS; + private int localInputPort; + private String remoteOutputAddressS; + private int remoteOutputPort; + + private InetAddress localInputAddress; + private InetAddress remoteOutputAddress; + + // connection + private ServerSocket serverSocket; + private Socket socket; + + protected DataOutputStream outputStream; + protected DataInputStream inputStream; + + protected CommunicationConnection connection; + + protected SocketMessageVisitor messageVisitor; + + /** + * Default constructor + */ + public ServerSocketEndpoint() { + this.connected = false; + this.closed = true; + this.fatal = false; + } + + /** + * Checks the state of the connection and returns true if {@link Socket} is connected and ready for transmission, + * false otherwise + * + * @return true if {@link Socket} is connected and ready for transmission, false otherwise + */ + protected boolean checkConnection() { + return !this.closed + && this.connected + && (this.socket != null && !this.socket.isClosed() && this.socket.isBound() + && this.socket.isConnected() && !this.socket.isInputShutdown() && !this.socket + .isOutputShutdown()); + } + + /** + * Listens on the {@link ServerSocket} for an incoming connection. Prepares the connection then for use. If the + * remote address has been defined, then the remote connection is validated to come from this appropriate host. + * CommunicationConnection attempts are always separated by a configured amount of time + */ + protected void openConnection() { + + ConnectionState state = this.connection.getState(); + + // do not open the connection if state is + // - CREATED + // - CONNECTING + // - WAITING + // - CLOSED + if (state == ConnectionState.CREATED || state == ConnectionState.CONNECTING || state == ConnectionState.WAITING + || state == ConnectionState.DISCONNECTED) { + + ConnectionMessages.throwIllegalConnectionState(state, ConnectionState.CONNECTING); + } + + // first close the connection + closeConnection(); + + while (!this.connected && !this.closed) { + try { + + this.connection.notifyStateChange(ConnectionState.CONNECTING, ConnectionState.CONNECTING.toString()); + + // only try in proper intervals + long currentTime = System.currentTimeMillis(); + long timeDifference = currentTime - this.lastConnect; + if (timeDifference < this.retry) { + long wait = this.retry - timeDifference; + logger.info(MessageFormat.format("Waiting: {0}ms", wait)); //$NON-NLS-1$ + + this.connection.notifyStateChange(ConnectionState.WAITING, ConnectionState.WAITING.toString()); + Thread.sleep(wait); + this.connection + .notifyStateChange(ConnectionState.CONNECTING, ConnectionState.CONNECTING.toString()); + } + + // don't try and connect if we are closed! + if (this.closed) { + logger.error("The connection has been closed and can not be connected"); //$NON-NLS-1$ + closeConnection(); + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, null); + return; + } + + // keep track of the time of this connection attempt + this.lastConnect = System.currentTimeMillis(); + + // open the socket + String msg = "Waiting for connections on: {0}:{1}..."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.localInputAddress.getHostAddress(), + Integer.toString(this.localInputPort))); + this.socket = this.serverSocket.accept(); + + // validate that the remote side of the socket is really the client we want + if (this.remoteOutputAddress != null) { + + String remoteAddr = this.socket.getInetAddress().getHostAddress(); + if (!remoteAddr.equals(this.remoteOutputAddress.getHostAddress())) { + msg = "Illegal remote client at address {0}. Expected is {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, remoteAddr, this.remoteOutputAddress.getHostAddress()); + logger.error(msg); + + closeConnection(); + + throw new ConnectionException(msg); + } + } + + // configure the socket + if (logger.isDebugEnabled()) { + msg = "BufferSize (send/read): {0} / {1} SoLinger: {2} TcpNoDelay: {3}"; //$NON-NLS-1$ + logger.debug(MessageFormat.format(msg, this.socket.getSendBufferSize(), + this.socket.getReceiveBufferSize(), this.socket.getSoLinger(), this.socket.getTcpNoDelay())); + } + //inputSocket.setSendBufferSize(1); + //inputSocket.setSoLinger(true, 0); + //inputSocket.setTcpNoDelay(true); + + // activate connection timeout + if (this.useTimeout) { + this.socket.setSoTimeout(this.timeout); + } + + // get the streams + this.outputStream = new DataOutputStream(this.socket.getOutputStream()); + this.inputStream = new DataInputStream(this.socket.getInputStream()); + + if (this.clearOnConnect) { + // clear the input stream + int available = this.inputStream.available(); + logger.info(MessageFormat.format("clearOnConnect: skipping {0} bytes.", available)); //$NON-NLS-1$ + this.inputStream.skip(available); + } + + msg = "Connected {0}{1}: {2}:{3} with local side {4}:{5}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.getClass().getSimpleName(), this.connection.getId(), + this.socket.getInetAddress().getHostName(), Integer.toString(this.socket.getPort()), + this.socket.getLocalAddress().getHostAddress(), Integer.toString(this.socket.getLocalPort()))); + + // we are connected! + this.connection.notifyStateChange(ConnectionState.CONNECTED, ConnectionState.CONNECTED.toString()); + this.connected = true; + + } catch (InterruptedException e) { + logger.warn("Interrupted!"); //$NON-NLS-1$ + this.closed = true; + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, null); + } catch (Exception e) { + if (this.closed && e instanceof SocketException) { + logger.warn("Socket closed!"); //$NON-NLS-1$ + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, null); + } else { + String msg = "Error while opening socket for inbound connection {0}: {1}"; //$NON-NLS-1$ + logger.error(MessageFormat.format(msg, this.connection.getId()), e.getMessage()); + this.connected = false; + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } + } + } + } + + /** + * closes the connection HARD by calling close() on the streams and socket. All Exceptions are caught to make sure + * that the connections are cleaned up + */ + protected void closeConnection() { + + this.connected = false; + this.connection.notifyStateChange(ConnectionState.BROKEN, null); + + if (this.outputStream != null) { + try { + this.outputStream.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing OutputStream: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.outputStream = null; + } + } + + if (this.inputStream != null) { + try { + this.inputStream.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing InputStream: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.inputStream = null; + } + } + + if (this.socket != null) { + try { + this.socket.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing InputSocket: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.socket = null; + } + + String msg = "Socket closed for inbound connection {0} at local input address {1}:{2}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.connection.getId(), this.localInputAddressS, + Integer.toString(this.localInputPort))); + } + } + + /** + *

+ * Configures this {@link ServerSocketEndpoint} + *

+ * gets the parameter map from the connection and reads the following parameters from the map: + *
    + *
  • localInputAddress - the local IP or Hostname to bind to for incoming connections
  • + *
  • localInputPort - the local port on which to listen for incoming connections
  • + *
  • remoteOutputAddress - the IP or Hostname of the remote client. If this value is not null, then it will be + * verified that the connecting client is connecting from this address
  • + *
  • remoteOutputPort - the port from which the remote client must connect. If this value is not null, then it + * will be verified that the connecting client is connecting from this port
  • + *
  • retry - a configured retry wait time. Default is {@link SocketEndpointConstants#RETRY}
  • + *
  • timeout - the timeout after which an idle socket is deemed dead. Default is + * {@link SocketEndpointConstants#TIMEOUT}
  • + *
  • useTimeout - if true, then the timeout is activated. default is {@link SocketEndpointConstants#USE_TIMEOUT}
  • + *
  • clearOnConnect - if true, then the after a successful connect the input is cleared by discarding all + * available bytes. This can be useful in cases where the channel is clogged with stale data. default is + * {@link SocketEndpointConstants#CLEAR_ON_CONNECT}
  • + *
+ * + * @see CommunicationEndpoint#configure(CommunicationConnection, IoMessageVisitor) + */ + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + if (this.connection != null && connection.getState().compareTo(ConnectionState.INITIALIZED) > 0) { + logger.warn(MessageFormat.format("Inbound connection {0} already configured.", connection.getId())); //$NON-NLS-1$ + return; + } + + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), SocketMessageVisitor.class, messageVisitor); + this.messageVisitor = (SocketMessageVisitor) messageVisitor; + this.connection = connection; + configure(); + } + + private void configure() { + Map parameters = this.connection.getParameters(); + + this.localInputAddressS = parameters.get(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_ADDRESS); + String localInputPortS = parameters.get(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT); + this.remoteOutputAddressS = parameters.get(SocketEndpointConstants.PARAMETER_REMOTE_OUTPUT_ADDRESS); + String remoteOutputPortS = parameters.get(SocketEndpointConstants.PARAMETER_REMOTE_OUTPUT_PORT); + + // parse local Address to InetAddress object + try { + this.localInputAddress = InetAddress.getByName(this.localInputAddressS); + } catch (UnknownHostException e) { + throw ConnectionMessages.throwInvalidParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_LOCAL_INPUT_ADDRESS, this.localInputAddressS); + } + + // parse local address port to integer + try { + this.localInputPort = Integer.parseInt(localInputPortS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT, localInputPortS); + } + + // if remote address is not set, then we will use the localhost InetAddress + if (this.remoteOutputAddressS == null || this.remoteOutputAddressS.length() == 0) { + logger.debug("No remoteOutputAddress set. Allowing connection from any remote address"); //$NON-NLS-1$ + } else { + + // parse remote output address name to InetAddress object + try { + this.remoteOutputAddress = InetAddress.getByName(this.remoteOutputAddressS); + } catch (UnknownHostException e) { + throw ConnectionMessages.throwInvalidParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_REMOTE_OUTPUT_ADDRESS, this.remoteOutputAddressS); + } + + // parse remote output address port to integer + try { + this.remoteOutputPort = Integer.parseInt(remoteOutputPortS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_REMOTE_OUTPUT_PORT, remoteOutputPortS); + } + } + + // configure retry wait time + String retryS = parameters.get(SocketEndpointConstants.PARAMETER_RETRY); + if (retryS == null || retryS.length() == 0) { + ConnectionMessages.warnUnsetParameter(ServerSocketEndpoint.class, SocketEndpointConstants.PARAMETER_RETRY, + String.valueOf(SocketEndpointConstants.RETRY)); + this.retry = SocketEndpointConstants.RETRY; + } else { + try { + this.retry = Long.parseLong(retryS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_RETRY, retryS); + } + } + + // configure if timeout on connection should be activated + String useTimeoutS = parameters.get(SocketEndpointConstants.PARAMETER_USE_TIMEOUT); + if (useTimeoutS == null || useTimeoutS.length() == 0) { + ConnectionMessages.warnUnsetParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_USE_TIMEOUT, String.valueOf(SocketEndpointConstants.USE_TIMEOUT)); + this.useTimeout = SocketEndpointConstants.USE_TIMEOUT; + } else { + this.useTimeout = Boolean.parseBoolean(useTimeoutS); + } + + if (this.useTimeout) { + // configure timeout on connection + String timeoutS = parameters.get(SocketEndpointConstants.PARAMETER_TIMEOUT); + if (timeoutS == null || timeoutS.length() == 0) { + ConnectionMessages.warnUnsetParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_TIMEOUT, String.valueOf(SocketEndpointConstants.TIMEOUT)); + this.timeout = SocketEndpointConstants.TIMEOUT; + } else { + try { + this.timeout = Integer.parseInt(timeoutS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_TIMEOUT, timeoutS); + } + } + } + + // configure if the connection should be cleared on connect + String clearOnConnectS = parameters.get(SocketEndpointConstants.PARAMETER_CLEAR_ON_CONNECT); + if (clearOnConnectS == null || clearOnConnectS.length() == 0) { + ConnectionMessages.warnUnsetParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_CLEAR_ON_CONNECT, + String.valueOf(SocketEndpointConstants.CLEAR_ON_CONNECT)); + this.clearOnConnect = SocketEndpointConstants.CLEAR_ON_CONNECT; + } else { + this.clearOnConnect = Boolean.parseBoolean(clearOnConnectS); + } + } + + /** + * @return the uri as String to which this {@link ServerSocketEndpoint} is locally bound to + */ + @Override + public String getLocalUri() { + if (this.socket != null) { + InetAddress localAddress = this.socket.getLocalAddress(); + return localAddress.getHostAddress() + StringHelper.COLON + this.socket.getLocalPort(); + } else if (this.localInputAddress != null) { + return this.localInputAddress.getHostAddress() + StringHelper.COLON + this.localInputPort; + } + + return "0.0.0.0:0"; //$NON-NLS-1$ + } + + /** + * @return the uri as String from which this {@link ServerSocketEndpoint} is receiving data from + */ + @Override + public String getRemoteUri() { + if (this.socket != null) { + InetAddress remoteAddress = this.socket.getInetAddress(); + return remoteAddress.getHostAddress() + StringHelper.COLON + this.socket.getPort(); + } else if (this.remoteOutputAddressS != null) { + return this.remoteOutputAddress.getHostAddress() + StringHelper.COLON + this.remoteOutputPort; + } + + return "0.0.0.0:0"; //$NON-NLS-1$ + } + + /** + * Starts the {@link Thread} to allow incoming connections + * + * @see CommunicationEndpoint#start() + */ + @Override + public void start() { + + if (this.fatal) { + String msg = "CommunicationConnection had a fatal exception and can not yet be started. Please check log file for further information!"; //$NON-NLS-1$ + throw new ConnectionException(msg); + } + + if (this.serverThread != null) { + logger.warn(MessageFormat.format("CommunicationConnection {0} already started.", this.connection.getId())); //$NON-NLS-1$ + } else { + // logger.info(MessageFormat.format("Enabling connection {0}...", this.connection.getId())); //$NON-NLS-1$ + this.closed = false; + + this.serverThread = new Thread(this, this.connection.getId()); + this.serverThread.start(); + } + } + + /** + * Closes any open connection and then stops the {@link Thread} disallowing incoming connections + * + * @see CommunicationEndpoint#stop() + */ + @Override + public void stop() { + closeThread(); + closeConnection(); + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); + + logger.info(MessageFormat.format("Disabled connection {0}.", this.connection.getId())); //$NON-NLS-1$ + } + + @Override + public void reset() { + closeThread(); + closeConnection(); + configure(); + this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); + } + + private void closeThread() { + this.closed = true; + this.fatal = false; + + if (this.serverThread != null) { + try { + this.serverThread.interrupt(); + if (this.serverSocket != null) + this.serverSocket.close(); + this.serverThread.join(2000l); + } catch (Exception e) { + logger.error(MessageFormat.format( + "Exception while interrupting server thread: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } + + this.serverThread = null; + } + } + + /** + * Thread is listening on the ServerSocket and opens a new connection if necessary + */ + @Override + public void run() { + + while (!this.closed) { + + // bomb-proof, catches all exceptions! + try { + + // if serverSocket is null or closed, open a new server socket + if (this.serverSocket == null || this.serverSocket.isClosed()) { + + try { + // String msg = "Opening socket on {0}:{1}..."; //$NON-NLS-1$ + // logger.info(MessageFormat.format(msg, this.localInputAddress.getHostAddress(), + // Integer.toString(this.localInputPort))); + this.serverSocket = new ServerSocket(this.localInputPort, 1, this.localInputAddress); + this.serverSocket.setReuseAddress(true); + } catch (BindException e) { + logger.error("Fatal BindException occurred! Port is already in use, or address is illegal!"); //$NON-NLS-1$ + logger.error(e.getMessage(), e); + this.closed = true; + this.fatal = true; + + String msg = "Fatal error while binding to server socket. ServerSocket endpoint is dead"; //$NON-NLS-1$ + throw new ConnectionException(msg); + } + } + + // open the connection + openConnection(); + + // as long as connection is connected + while (checkConnection()) { + + // read and write from the connected server socket + IoMessage message = this.messageVisitor.visit(this.inputStream, this.outputStream); + if (message != null) { + this.connection.handleNewMessage(message); + } + } + + } catch (Exception e) { + if (e instanceof InterruptedException) { + logger.error("Interrupted!"); //$NON-NLS-1$ + } else { + logger.error(e.getMessage(), e); + } + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } finally { + closeConnection(); + } + } + + if (!this.fatal) { + logger.warn(MessageFormat.format( + "CommunicationConnection {0} is not running anymore!", this.connection.getId())); //$NON-NLS-1$ + this.connection.notifyStateChange(ConnectionState.BROKEN, null); + } else { + String msg = "CommunicationConnection {0} is broken due to a fatal exception!"; //$NON-NLS-1$ + logger.error(MessageFormat.format(msg, this.connection.getId())); + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, null); + } + } + + @Override + public void simulate(IoMessage message) throws Exception { + send(message); + } + + @Override + public void send(IoMessage message) throws Exception { + String msg = "The Server Socket can not send messages, use the {0} implementation instead!"; //$NON-NLS-1$ + throw new UnsupportedOperationException(MessageFormat.format(msg, ClientSocketEndpoint.class.getName())); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java new file mode 100644 index 000000000..df7173e46 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java @@ -0,0 +1,82 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication.tcpip; + +/** + * Constants used in the communication classes + * + * @author Robert von Burg + */ +public class SocketEndpointConstants { + + public static final String PARAMETER_USE_TIMEOUT = "useTimeout"; //$NON-NLS-1$ + public static final String PARAMETER_TIMEOUT = "timeout"; //$NON-NLS-1$ + public static final String PARAMETER_RETRY = "retry"; //$NON-NLS-1$ + public static final String PARAMETER_CLEAR_ON_CONNECT = "clearOnConnect"; //$NON-NLS-1$ + public static final String PARAMETER_CONNECT_ON_START = "connectOnStart"; //$NON-NLS-1$ + public static final String PARAMETER_CLOSE_AFTER_SEND = "closeAfterSend"; //$NON-NLS-1$ + + public static final String PARAMETER_REMOTE_OUTPUT_PORT = "remoteOutputPort"; //$NON-NLS-1$ + public static final String PARAMETER_REMOTE_OUTPUT_ADDRESS = "remoteOutputAddress"; //$NON-NLS-1$ + public static final String PARAMETER_LOCAL_INPUT_PORT = "localInputPort"; //$NON-NLS-1$ + public static final String PARAMETER_LOCAL_INPUT_ADDRESS = "localInputAddress"; //$NON-NLS-1$ + + public static final String PARAMETER_LOCAL_OUTPUT_ADDRESS = "localOutputAddress"; //$NON-NLS-1$ + public static final String PARAMETER_LOCAL_OUTPUT_PORT = "localOutputPort"; //$NON-NLS-1$ + public static final String PARAMETER_REMOTE_INPUT_ADDRESS = "remoteInputAddress"; //$NON-NLS-1$ + public static final String PARAMETER_REMOTE_INPUT_PORT = "remoteInputPort"; //$NON-NLS-1$ + + /** + * Time to wait in milliseconds before reestablishing a connection. Default is 60000ms + */ + public static final long RETRY = 60000l; + + /** + * The time after which a connection is deemed dead. Value is 60000ms + */ + public static final int TIMEOUT = 60000; + + /** + * Default is to use a timeout on socket connections, thus this value is true + */ + public static final boolean USE_TIMEOUT = true; + + /** + * Default is to not clear the input socket on connect, thus this value is false + */ + public static final boolean CLEAR_ON_CONNECT = false; + + /** + * Default is to connect on start of the connection + */ + public static final boolean CONNECT_ON_START = true; + + /** + * Default is to not close after sending + */ + public static final boolean CLOSE_AFTER_SEND = false; + + /** + * Default is to disconnect after a null message is received when reading from a TCP socket, thus this value is true + */ + public static final boolean DISCONNECT_ON_NULL_MSG = true; + + /** + * If {@link #DISCONNECT_ON_NULL_MSG} is activated, then this is the default time used to wait before reading again, + * which is 10000ms + */ + public static final long WAIT_TIME_ON_NULL_MSG = 10000l; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java new file mode 100644 index 000000000..52f535300 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication.tcpip; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.net.Socket; + +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; + +/** + * This {@link IoMessageVisitor} implements and endpoint connecting to a {@link Socket}. + * + * @author Robert von Burg + */ +public abstract class SocketMessageVisitor extends IoMessageVisitor { + + protected final String connectionId; + + public SocketMessageVisitor(String connectionId) { + this.connectionId = connectionId; + } + + public String getConnectionId() { + return this.connectionId; + } + + /** + * This method is called when a message is read from the underlying {@link Socket} + * + * @param inputStream + * @param outputStream + * @return + * @throws Exception + */ + public abstract IoMessage visit(DataInputStream inputStream, DataOutputStream outputStream) throws Exception; + + /** + * This method is called when a message is to be sent to the underlying connected endpoint + * + * @param inputStream + * @param outputStream + * @param message + * @throws Exception + */ + public abstract void visit(DataInputStream inputStream, DataOutputStream outputStream, IoMessage message) + throws Exception; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java new file mode 100644 index 000000000..967cdacf3 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java @@ -0,0 +1,74 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Map; + +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + */ +public class DbConnectionCheck { + + private static final Logger logger = LoggerFactory.getLogger(DbConnectionCheck.class); + private Map dsMap; + + /** + * @param dsMap + */ + public DbConnectionCheck(Map dsMap) { + this.dsMap = dsMap; + } + + /** + * Checks the connectivity to each of the configured {@link DbConnectionInfo} + * + * @throws DbException + */ + public void checkConnections() throws DbException { + Collection values = this.dsMap.values(); + for (DataSource ds : values) { + + logger.info("Checking connection " + ds); + + try (Connection con = ds.getConnection(); Statement st = con.createStatement();) { + + try (ResultSet rs = st.executeQuery("select version()")) { //$NON-NLS-1$ + if (rs.next()) { + logger.info(MessageFormat.format("Connected to: {0}", rs.getString(1))); //$NON-NLS-1$ + } + } + + } catch (SQLException e) { + String msg = "Failed to open DB connection to {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, ds, e.getMessage()); + throw new DbException(msg, e); + } + } + + logger.info("All connections OK"); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConstants.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConstants.java new file mode 100644 index 000000000..edec0cf3a --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.db; + +/** + * @author Robert von Burg + */ +public class DbConstants { + + public static final String PROP_DB_URL = "db.url"; //$NON-NLS-1$ + public static final String PROP_DB_IGNORE_REALM = "db.ignore.realm"; //$NON-NLS-1$ + public static final String PROP_DB_USERNAME = "db.username"; //$NON-NLS-1$ + public static final String PROP_DB_PASSWORD = "db.password"; //$NON-NLS-1$ + public static final String PROP_ALLOW_SCHEMA_CREATION = "allowSchemaCreation"; + public static final String PROP_ALLOW_SCHEMA_MIGRATION = "allowSchemaMigration"; + public static final String PROP_ALLOW_SCHEMA_DROP = "allowSchemaDrop"; + public static final String PROP_ALLOW_DATA_INIT_ON_SCHEMA_CREATE = "allowDataInitOnSchemaCreate"; + public static final String PROP_DB_VERSION = "db_version"; + public static final String RESOURCE_DB_VERSION = "/{0}_db_version.properties"; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java new file mode 100644 index 000000000..eb33af6f1 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.db; + +import java.util.Properties; + +import javax.sql.DataSource; + +/** + * @author Robert von Burg + */ +public interface DbDataSourceBuilder { + + public DataSource build(String realm, String url, String username, String password, Properties properties); +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbException.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbException.java new file mode 100644 index 000000000..f1c6bb942 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.db; + +/** + * @author Robert von Burg + */ +public class DbException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * @param message + */ + public DbException(String message) { + super(message); + } + + /** + * @param message + * @param cause + */ + public DbException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java new file mode 100644 index 000000000..f937ef344 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java @@ -0,0 +1,23 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.db; + +/** + * @author Robert von Burg + */ +public enum DbMigrationState { + NOTHING, CREATED, MIGRATED, DROPPED_CREATED; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java new file mode 100644 index 000000000..8f870fec0 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java @@ -0,0 +1,363 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.db; + +import static ch.eitchnet.db.DbConstants.PROP_DB_VERSION; +import static ch.eitchnet.db.DbConstants.RESOURCE_DB_VERSION; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.Version; +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +@SuppressWarnings(value = "nls") +public class DbSchemaVersionCheck { + + private static final Logger logger = LoggerFactory.getLogger(DbSchemaVersionCheck.class); + private String app; + private Class ctxClass; + private boolean allowSchemaCreation; + private boolean allowSchemaMigration; + private boolean allowSchemaDrop; + private Map dbMigrationStates; + + /** + * @param app + * @param ctxClass + * @param allowSchemaCreation + * @param allowSchemaDrop + */ + public DbSchemaVersionCheck(String app, Class ctxClass, boolean allowSchemaCreation, + boolean allowSchemaMigration, boolean allowSchemaDrop) { + + DBC.PRE.assertNotEmpty("app may not be empty!", app); + DBC.PRE.assertNotNull("ctxClass may not be null!", ctxClass); + + this.app = app; + this.ctxClass = ctxClass; + this.allowSchemaCreation = allowSchemaCreation; + this.allowSchemaMigration = allowSchemaMigration; + this.allowSchemaDrop = allowSchemaDrop; + this.dbMigrationStates = new HashMap<>(); + } + + /** + * @return the dbMigrationStates + */ + public Map getDbMigrationStates() { + return this.dbMigrationStates; + } + + public void checkSchemaVersion(Map dsMap) throws DbException { + for (Entry entry : dsMap.entrySet()) { + String realm = entry.getKey(); + DataSource ds = entry.getValue(); + DbMigrationState dbMigrationState = checkSchemaVersion(realm, ds); + this.dbMigrationStates.put(realm, dbMigrationState); + } + } + + /** + * Returns true if the schema existed or was only migrated, false if the schema was created + * + * @param ds + * @param realm2 + * + * @param connectionInfo + * + * @return true if the schema existed or was only migrated, false if the schema was created + * + * @throws DbException + */ + public DbMigrationState checkSchemaVersion(String realm, DataSource ds) throws DbException { + + logger.info(MessageFormat.format("[{0}:{1}] Checking Schema version for: {2}", this.app, realm, ds)); + + Version expectedDbVersion = getExpectedDbVersion(this.app, this.ctxClass); + + try (Connection con = ds.getConnection()) { + + // get current version + Version currentVersion = getCurrentVersion(con, this.app); + DbMigrationState migrationType = detectMigrationState(realm, expectedDbVersion, currentVersion); + + switch (migrationType) { + case CREATED: + createSchema(con, realm, expectedDbVersion); + break; + case MIGRATED: + migrateSchema(con, realm, expectedDbVersion); + break; + case DROPPED_CREATED: + throw new DbException("Migration type " + migrationType + " not handled!"); + case NOTHING: + // do nothing + default: + break; + } + + con.commit(); + return migrationType; + + } catch (SQLException e) { + String msg = "Failed to open DB connection to {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, ds, e.getMessage()); + throw new DbException(msg, e); + } + } + + /** + * @param con + * @param app + * + * @return + * + * @throws SQLException + */ + public static Version getCurrentVersion(Connection con, String app) throws SQLException { + + // first see if we have any schema + String sql = "select table_schema, table_name, table_type from information_schema.tables where table_name = ?"; + try (PreparedStatement st = con.prepareStatement(sql)) { + st.setString(1, PROP_DB_VERSION); + if (!st.executeQuery().next()) + return null; + } + + // first find current version + sql = "select id, version from db_version where app = ? order by id desc;"; + Version currentVersion = null; + try (PreparedStatement st = con.prepareStatement(sql)) { + st.setString(1, app); + try (ResultSet rs = st.executeQuery()) { + if (rs.next()) + currentVersion = Version.valueOf(rs.getString(2)); + } + } + + return currentVersion; + } + + /** + * @param realm + * @param expectedDbVersion + * + * @return + * + * @throws SQLException + * @throws DbException + */ + public DbMigrationState detectMigrationState(String realm, Version expectedDbVersion, Version currentVersion) + throws SQLException, DbException { + + // no version, then we need to create it + if (currentVersion == null) + return DbMigrationState.CREATED; + + // otherwise parse the version + int compare = expectedDbVersion.compareTo(currentVersion); + if (compare == 0) { + String msg = "[{0}:{1}] Schema version {2} is the current version. No changes needed."; + msg = MessageFormat.format(msg, this.app, realm, currentVersion); + logger.info(msg); + return DbMigrationState.NOTHING; + } else if (compare > 0) { + String msg = "[{0}:{1}] Schema version is not current. Need to upgrade from {2} to {3}"; + msg = MessageFormat.format(msg, this.app, realm, currentVersion, expectedDbVersion); + logger.warn(msg); + return DbMigrationState.MIGRATED; + } + + throw new DbException(MessageFormat.format("[{0}:{1}]Current version {2} is later than expected version {3}", + this.app, realm, currentVersion, expectedDbVersion)); + } + + /** + * @param app + * @param ctxClass + * + * @return + * + * @throws DbException + */ + public static Version getExpectedDbVersion(String app, Class ctxClass) throws DbException { + Properties dbVersionProps = new Properties(); + + String dbVersionPropFile = MessageFormat.format(RESOURCE_DB_VERSION, app); + + try (InputStream stream = ctxClass.getResourceAsStream(dbVersionPropFile);) { + DBC.PRE.assertNotNull( + MessageFormat.format("Resource file with name {0} does not exist!", dbVersionPropFile), stream); + dbVersionProps.load(stream); + } catch (IOException e) { + String msg = "Expected resource file {0} does not exist or is not a valid properties file: {1}"; + msg = MessageFormat.format(msg, dbVersionPropFile, e.getMessage()); + throw new DbException(msg, e); + } + String dbVersion = dbVersionProps.getProperty(PROP_DB_VERSION); + String msg = "Missing property {0} in resource file {1}"; + DBC.PRE.assertNotEmpty(MessageFormat.format(msg, PROP_DB_VERSION, dbVersionPropFile), dbVersion); + + return Version.valueOf(dbVersion); + } + + /** + * @param scriptPrefix + * @param classLoader + * @param dbVersion + * @param type + * + * @return + * + * @throws DbException + */ + public static String getSql(String scriptPrefix, Class ctxClass, Version dbVersion, String type) + throws DbException { + String schemaResourceS = MessageFormat.format("/{0}_db_schema_{1}_{2}.sql", scriptPrefix, dbVersion, type); + try (InputStream stream = ctxClass.getResourceAsStream(schemaResourceS);) { + DBC.PRE.assertNotNull( + MessageFormat.format("Schema Resource file with name {0} does not exist!", schemaResourceS), + stream); + return FileHelper.readStreamToString(stream); + } catch (IOException e) { + throw new DbException("Schema creation resource file is missing or could not be read: " + schemaResourceS, + e); + } + } + + /** + * + * @param realm + * the realm to create the schema for (a {@link DbConnectionInfo} must exist for it) + * @param dbVersion + * the version to upgrade to + * @param st + * the open database {@link Statement} to which the SQL statements will be written + * + * @return true if the schema was created, false if it was not + * + * @throws DbException + */ + public void createSchema(Connection con, String realm, Version dbVersion) throws DbException { + + if (!this.allowSchemaCreation) { + String msg = "[{0}:{1}] No schema exists, or is not valid. Schema generation is disabled, thus can not continue!"; + msg = MessageFormat.format(msg, this.app, realm); + throw new DbException(msg); + } + + logger.info(MessageFormat.format("[{0}:{1}] Creating initial schema...", this.app, realm)); + + String sql = getSql(this.app, this.ctxClass, dbVersion, "initial"); + + try (Statement st = con.createStatement()) { + st.execute(sql); + } catch (SQLException e) { + logger.error("Failed to execute schema creation SQL: \n" + sql); + throw new DbException("Failed to execute schema generation SQL: " + e.getMessage(), e); + } + + logger.info(MessageFormat.format("[{0}:{1}] Successfully created schema with version {2}", this.app, realm, + dbVersion)); + } + + /** + * Upgrades the schema to the given version. If the current version is below the given version, then currently this + * method drops the schema and recreates it. Real migration must still be implemented + * + * @param realm + * the realm to migrate (a {@link DbConnectionInfo} must exist for it) + * @param dbVersion + * the version to upgrade to + * + * @return true if the schema was recreated, false if it was simply migrated + * + * @throws DbException + */ + public void migrateSchema(Connection con, String realm, Version dbVersion) throws DbException { + + if (!this.allowSchemaMigration) { + String msg = "[{0}:{1}] Schema is not valid. Schema migration is disabled, thus can not continue!"; + msg = MessageFormat.format(msg, this.app, realm); + throw new DbException(msg); + } + + logger.info(MessageFormat.format("[{0}:{1}] Migrating schema...", this.app, realm)); + + String sql = getSql(this.app, this.ctxClass, dbVersion, "migration"); + try (Statement st = con.createStatement()) { + st.execute(sql); + } catch (SQLException e) { + logger.error("Failed to execute schema migration SQL: \n" + sql); + throw new DbException("Failed to execute schema migration SQL: " + e.getMessage(), e); + } + + logger.info(MessageFormat.format("[{0}:{1}] Successfully migrated schema to version {2}", this.app, realm, + dbVersion)); + } + + /** + * @param realm + * the realm for which the schema must be dropped (a {@link DbConnectionInfo} must exist for it) + * @param dbVersion + * the version with which to to drop the schema + * @param st + * the open database {@link Statement} to which the SQL statements will be written + * + * @throws DbException + */ + public void dropSchema(Connection con, String realm, Version dbVersion) throws DbException { + + if (!this.allowSchemaDrop) { + String msg = "[{0}:{1}] Dropping Schema is disabled, but is required to upgrade current schema..."; + msg = MessageFormat.format(msg, this.app, realm); + throw new DbException(msg); + } + + logger.info(MessageFormat.format("[{0}:{1}] Dropping existing schema...", this.app, realm)); + + String sql = getSql(this.app, this.ctxClass, dbVersion, "drop"); + try (Statement st = con.createStatement()) { + st.execute(sql); + } catch (SQLException e) { + logger.error("Failed to execute schema drop SQL: \n" + sql); + throw new DbException("Failed to execute schema drop SQL: " + e.getMessage(), e); + } + + logger.info(MessageFormat.format("[{0}:{1}] Successfully dropped schema with version {2}", this.app, realm, + dbVersion)); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java new file mode 100644 index 000000000..0924c8689 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.fileserver; + +import java.rmi.RemoteException; + +/** + * @author Robert von Burg + * + */ +public interface FileClient { + + /** + * Remote method with which a client can push parts of files to the server. It is up to the client to send as many + * parts as needed, the server will write the parts to the associated file + * + * @param filePart + * the part of the file + * @throws RemoteException + * if something goes wrong with the remote call + */ + public void uploadFilePart(FilePart filePart) throws RemoteException; + + /** + * Remote method with which a client can delete files from the server. It only deletes single files if they exist + * + * @param fileDeletion + * the {@link FileDeletion} defining the deletion request + * + * @return true if the file was deleted, false if the file did not exist + * + * @throws RemoteException + * if something goes wrong with the remote call + */ + public boolean deleteFile(FileDeletion fileDeletion) throws RemoteException; + + /** + * Remote method which a client can request part of a file. The server will fill the given {@link FilePart} with a + * byte array of the file, with bytes from the file, respecting the desired offset. It is up to the client to call + * this method multiple times for the entire file. It is a decision of the concrete implementation how much data is + * returned in each part, the client may pass a request, but this is not definitive + * + * @param filePart + * the part of the file + * + * @return the same file part, yet with the part of the file requested as a byte array + * + * @throws RemoteException + * if something goes wrong with the remote call + */ + public FilePart requestFile(FilePart filePart) throws RemoteException; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java new file mode 100644 index 000000000..102f205d6 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java @@ -0,0 +1,238 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.fileserver; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.rmi.RemoteException; +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + * + */ +public class FileClientUtil { + + private static final Logger logger = LoggerFactory.getLogger(FileClientUtil.class); + + /** + * @param rmiFileClient + * @param origFilePart + * @param dstFile + */ + public static void downloadFile(FileClient rmiFileClient, FilePart origFilePart, File dstFile) { + + // here we don't overwrite, the caller must make sure the destination file does not exist + if (dstFile.exists()) { + String msg = "The destination file {0} already exists. Delete it first, if you want to overwrite it!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, dstFile.getAbsolutePath()); + throw new RuntimeException(msg); + } + + try { + + FilePart tmpPart = origFilePart; + + int loops = 0; + int startLength = tmpPart.getPartLength(); + while (true) { + loops += 1; + + // get the next part + tmpPart = rmiFileClient.requestFile(tmpPart); + + // validate length of data + if (tmpPart.getPartLength() != tmpPart.getPartBytes().length) { + String msg = "Invalid tmpPart. Part length is not as long as the bytes passed {0} / {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, tmpPart.getPartLength(), tmpPart.getPartBytes().length); + throw new RuntimeException(msg); + } + + // validate offset is size of file + if (tmpPart.getPartOffset() != dstFile.length()) { + String msg = "The part offset $offset is not at the end of the file {0} / {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, tmpPart.getPartOffset(), dstFile.length()); + throw new RuntimeException(msg); + } + + // append the part + FileHelper.appendFilePart(dstFile, tmpPart.getPartBytes()); + + // update the offset + tmpPart.setPartOffset(tmpPart.getPartOffset() + tmpPart.getPartBytes().length); + + // break if the offset is past the length of the file + if (tmpPart.getPartOffset() >= tmpPart.getFileLength()) + break; + } + + String msg = "{0}: {1}: Requested {2} parts. StartSize: {3} EndSize: {4}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, tmpPart.getFileType(), tmpPart.getFileName(), loops, startLength, + tmpPart.getPartLength()); + logger.info(msg); + + // validate that the offset is at the end of the file + if (tmpPart.getPartOffset() != origFilePart.getFileLength()) { + msg = "Offset {0} is not at file length {1} after reading all the file parts!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, tmpPart.getPartOffset(), origFilePart.getFileLength()); + throw new RuntimeException(msg); + } + + // now validate hashes + String dstFileHash = StringHelper.getHexString(FileHelper.hashFileSha256(dstFile)); + if (!dstFileHash.equals(origFilePart.getFileHash())) { + msg = "Downloading the file {0} failed because the hashes don''t match. Expected: {1} / Actual: {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, origFilePart.getFileName(), origFilePart.getFileHash(), dstFileHash); + throw new RuntimeException(msg); + } + + } catch (Exception e) { + if (e instanceof RuntimeException) + throw (RuntimeException) e; + String msg = "Downloading the file {0} failed because of an underlying exception {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, origFilePart.getFileName(), e.getLocalizedMessage()); + throw new RuntimeException(msg); + } + } + + /** + * @param rmiFileClient + * @param srcFile + * @param fileType + */ + public static void uploadFile(FileClient rmiFileClient, File srcFile, String fileType) { + + // make sure the source file exists + if (!srcFile.canRead()) { + String msg = MessageFormat.format("The source file does not exist at {0}", srcFile.getAbsolutePath()); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + + try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(srcFile));) { + + // get the size of the file + long fileLength = srcFile.length(); + String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(srcFile)); + + // create the file part to send + FilePart filePart = new FilePart(srcFile.getName(), fileType); + filePart.setFileLength(fileLength); + filePart.setFileHash(fileHash); + + // define the normal size of the parts we're sending. The last part will naturally have a different size + int partLength; + if (fileLength > FileHandler.MAX_PART_SIZE) + partLength = FileHandler.MAX_PART_SIZE; + else + partLength = (int) fileLength; + + // this is the byte array of data we're sending each time + byte[] bytes = new byte[partLength]; + + // open the stream to the file + int read = 0; + int offset = 0; + + // loop by reading the number of bytes needed for each part + int loops = 0; + int startLength = partLength; + while (true) { + loops += 1; + + // read the bytes into the array + read = inputStream.read(bytes); + + // validate we read the expected number of bytes + if (read == -1) + throw new IOException("Something went wrong while reading the bytes as -1 was returned!"); //$NON-NLS-1$ + if (read != bytes.length) { + String msg = "Something went wrong while reading the bytes as the wrong number of bytes were read. Expected {0} Actual: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, bytes.length, read); + throw new IOException(msg); + } + + // set the fields on the FilePart + filePart.setPartBytes(bytes); + filePart.setPartOffset(offset); + filePart.setPartLength(bytes.length); + + // and if this is the last part, then also set that + int nextOffset = offset + bytes.length; + if (nextOffset == fileLength) + filePart.setLastPart(true); + + // now send the part to the server + rmiFileClient.uploadFilePart(filePart); + + // if this was the last part, then break + if (filePart.isLastPart()) + break; + + // otherwise update the offset for the next part by also making sure the next part is not larger than + // the last part of the file + if (nextOffset + bytes.length > fileLength) { + long remaining = fileLength - nextOffset; + if (remaining > FileHandler.MAX_PART_SIZE) { + String msg = "Something went wrong as the remaining part {0} is larger than MAX_PART_SIZE {1}!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, remaining, FileHandler.MAX_PART_SIZE); + throw new RuntimeException(msg); + } + partLength = (int) remaining; + bytes = new byte[partLength]; + } + + // and save the offset for the next loop + offset = nextOffset; + } + + String msg = "{0}: {1}: Sent {2} parts. StartSize: {3} EndSize: {4}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, filePart.getFileType(), filePart.getFileName(), loops, startLength, + filePart.getPartLength()); + logger.info(msg); + + } catch (Exception e) { + if (e instanceof RuntimeException) + throw (RuntimeException) e; + String msg = "Uploading the file {0} failed because of an underlying exception {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, srcFile.getAbsolutePath(), e.getLocalizedMessage()); + throw new RuntimeException(msg); + } + } + + /** + * @param rmiFileClient + * @param fileDeletion + * @param dstFile + */ + public static void deleteFile(FileClient rmiFileClient, FileDeletion fileDeletion, File dstFile) { + + try { + rmiFileClient.deleteFile(fileDeletion); + } catch (RemoteException e) { + String msg = "Deleting the file {0} failed because of an underlying exception {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, fileDeletion.getFileName(), e.getLocalizedMessage()); + throw new RuntimeException(msg); + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java new file mode 100644 index 000000000..b669aa39e --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.fileserver; + +import java.io.Serializable; + +/** + * @author Robert von Burg + */ +public class FileDeletion implements Serializable { + + private static final long serialVersionUID = 1L; + + private String fileName; + private String fileType; + + /** + * @param fileName + * the name of the file to be deleted. This is either just the name, or a path relative to the type + * @param fileType + * the type of file to delete. This defines in which path the file resides + */ + public FileDeletion(String fileName, String fileType) { + this.fileName = fileName; + this.fileType = fileType; + } + + /** + * @return the fileType + */ + public String getFileType() { + return this.fileType; + } + + /** + * @return the fileName + */ + public String getFileName() { + return this.fileName; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java new file mode 100644 index 000000000..bee06ab65 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java @@ -0,0 +1,307 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.fileserver; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * This class handles remote requests of clients to upload or download a file. Uploading a file is done by calling + * {@link #handleFilePart(FilePart)} and the downloading a file is done by calling {@link #requestFile(FilePart)} + * + * @author Robert von Burg + */ +public class FileHandler { + + private static final Logger logger = LoggerFactory.getLogger(FileHandler.class); + + /** + * DEF_PART_SIZE = default part size which is set to 1048576 bytes (1 MiB) + */ + public static final int MAX_PART_SIZE = 1048576; + + private String basePath; + private boolean verbose; + + /** + * + */ + public FileHandler(String basePath, boolean verbose) { + + File basePathF = new File(basePath); + if (!basePathF.exists()) { + String msg = MessageFormat.format("Base Path does not exist {0}", basePathF.getAbsolutePath()); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + if (!basePathF.canWrite()) { + String msg = MessageFormat.format("Can not write to base path {0}", basePathF.getAbsolutePath()); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + + this.verbose = verbose; + this.basePath = basePath; + } + + /** + * Method which a client can request part of a file. The server will fill the given {@link FilePart} with a byte + * array of the file, with bytes from the file, respecting the desired offset. It is up to the client to call this + * method multiple times for the entire file. It is a decision of the concrete implementation how much data is + * returned in each part, the client may pass a request, but this is not definitive + * + * @param filePart + * the part of the file + */ + public FilePart requestFile(FilePart filePart) { + + // validate file name is legal + String fileName = filePart.getFileName(); + validateFileName(fileName); + + // validate type is legal + String fileType = filePart.getFileType(); + validateFileType(fileType); + + // evaluate the path where the file should reside + String fileTypePath = this.basePath + "/" + fileType; //$NON-NLS-1$ + File file = new File(fileTypePath, filePart.getFileName()); + + // now evaluate the file exists + String fileNotFoundMsg = "The file {0} could not be found in the location for files of type {1}"; //$NON-NLS-1$ + if (!file.canRead()) { + String msg = fileNotFoundMsg; + msg = MessageFormat.format(msg, fileName, fileType); + throw new RuntimeException(msg); + } + + // if this is the start of the file, then prepare the file part + long fileSize = file.length(); + if (filePart.getPartOffset() == 0) { + + // set the file length + filePart.setFileLength(fileSize); + + // set the SHA256 of the file + filePart.setFileHash(StringHelper.getHexString(FileHelper.hashFileSha256(file))); + } + + // variables defining the part of the file we're going to return + long requestOffset = filePart.getPartOffset(); + int requestSize = filePart.getPartLength(); + if (requestSize > FileHandler.MAX_PART_SIZE) { + String msg = "The requested part size {0} is greater than the allowed {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, requestSize, MAX_PART_SIZE); + throw new RuntimeException(msg); + } + + // validate lengths and offsets + if (filePart.getFileLength() != fileSize) { + String msg = "The part request has a file size {0}, but the file is actually {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, filePart.getFileLength(), fileSize); + throw new RuntimeException(msg); + } else if (requestOffset > fileSize) { + String msg = "The requested file part offset {0} is greater than the size of the file {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, requestOffset, fileSize); + throw new RuntimeException(msg); + } + // Otherwise make sure the offset + request length is not larger than the actual file size. + // If it is then this is the end part + else if (requestOffset + requestSize >= fileSize) { + + long remaining = fileSize - requestOffset; + + // update request size to last part of file + long l = Math.min(requestSize, remaining); + + // this is a fail safe + if (l > MAX_PART_SIZE) { + String msg = "Something went wrong. Min of requestSize and remaining is > MAX_PART_SIZE of {0}!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, MAX_PART_SIZE); + throw new RuntimeException(msg); + } + + // this is the size of the array we want to return + requestSize = (int) l; + filePart.setPartLength(requestSize); + filePart.setLastPart(true); + } + + // now read the part of the file and set it as bytes for the file part + try (FileInputStream fin = new FileInputStream(file);) { + + // position the stream + long skip = fin.skip(requestOffset); + if (skip != requestOffset) { + String msg = MessageFormat.format("Asked to skip {0} but only skipped {1}", requestOffset, skip); //$NON-NLS-1$ + throw new IOException(msg); + } + + // read the data + byte[] bytes = new byte[requestSize]; + int read = fin.read(bytes); + if (read != requestSize) { + String msg = MessageFormat.format("Asked to read {0} but only read {1}", requestSize, read); //$NON-NLS-1$ + throw new IOException(msg); + } + + // set the return result + filePart.setPartBytes(bytes); + + } catch (FileNotFoundException e) { + String msg = MessageFormat.format(fileNotFoundMsg, fileName, fileType); + throw new RuntimeException(msg); + } catch (IOException e) { + String msg = "There was an error while reading from the file {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, fileName); + throw new RuntimeException(msg); + } + + // we are returning the same object as the user gave us, just modified + if (this.verbose) { + String msg = "Read {0} for file {1}/{2}"; //$NON-NLS-1$ + String fileSizeS = FileHelper.humanizeFileSize(filePart.getPartBytes().length); + msg = MessageFormat.format(msg, fileSizeS, fileType, fileName); + logger.info(msg); + } + return filePart; + } + + /** + * Method with which a client can push parts of files to the server. It is up to the client to send as many parts as + * needed, the server will write the parts to the associated file + * + * @param filePart + * the part of the file + */ + public void handleFilePart(FilePart filePart) { + + // validate file name is legal + String fileName = filePart.getFileName(); + validateFileName(fileName); + + // validate type is legal + String fileType = filePart.getFileType(); + validateFileType(fileType); + + // evaluate the path where the file should reside + String fileTypePath = this.basePath + "/" + fileType; //$NON-NLS-1$ + File dstFile = new File(fileTypePath, filePart.getFileName()); + + // if the file already exists, then this may not be a start part + if (filePart.getPartOffset() == 0 && dstFile.exists()) { + String msg = "The file {0} already exist for type {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, fileName, fileType); + throw new RuntimeException(msg); + } + + // write the part + FileHelper.appendFilePart(dstFile, filePart.getPartBytes()); + + // if this is the last part, then validate the hashes + if (filePart.isLastPart()) { + String dstFileHash = StringHelper.getHexString(FileHelper.hashFileSha256(dstFile)); + if (!dstFileHash.equals(filePart.getFileHash())) { + String msg = "Uploading the file {0} failed because the hashes don''t match. Expected: {1} / Actual: {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, filePart.getFileName(), filePart.getFileHash(), dstFileHash); + throw new RuntimeException(msg); + } + } + + if (this.verbose) { + String msg; + if (filePart.isLastPart()) + msg = "Wrote {0} for part of file {1}/{2}"; //$NON-NLS-1$ + else + msg = "Wrote {0} for last part of file {1}/{2}"; //$NON-NLS-1$ + String fileSizeS = FileHelper.humanizeFileSize(filePart.getPartBytes().length); + msg = MessageFormat.format(msg, fileSizeS, fileType, fileName); + logger.info(msg); + } + } + + /** + * Method with which a client can delete files from the server. It only deletes single files if they exist + * + * @param fileDeletion + * the {@link FileDeletion} defining the deletion request + * + * @return true if the file was deleted, false if the file did not exist + * + */ + public boolean deleteFile(FileDeletion fileDeletion) { + + // validate file name is legal + String fileName = fileDeletion.getFileName(); + validateFileName(fileName); + + // validate type is legal + String fileType = fileDeletion.getFileType(); + validateFileType(fileType); + + // evaluate the path where the file should reside + String fileTypePath = this.basePath + "/" + fileType; //$NON-NLS-1$ + File fileToDelete = new File(fileTypePath, fileDeletion.getFileName()); + + // delete the file + boolean deletedFile = FileHelper.deleteFiles(new File[] { fileToDelete }, true); + + String msg; + if (deletedFile) + msg = "Deleted file {1}/{2}"; //$NON-NLS-1$ + else + msg = "Failed to delete file {1}/{2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, fileType, fileName); + logger.info(msg); + return deletedFile; + } + + /** + * Validates that the file name is legal, i.e. not empty or contains references up the tree + * + * @param fileName + */ + private void validateFileName(String fileName) { + + if (fileName == null || fileName.isEmpty()) { + throw new RuntimeException("The file name was not given! Can not find a file without a name!"); //$NON-NLS-1$ + } else if (fileName.contains("/")) { //$NON-NLS-1$ + String msg = "The given file name contains illegal characters. The file name may not contain slashes!"; //$NON-NLS-1$ + throw new RuntimeException(msg); + } + } + + /** + * Validates that the file type is legal, i.e. not empty or contains references up the tree + * + * @param fileType + */ + private void validateFileType(String fileType) { + if (fileType == null || fileType.isEmpty()) { + throw new RuntimeException("The file type was not given! Can not find a file without a type!"); //$NON-NLS-1$ + } else if (fileType.contains("/")) { //$NON-NLS-1$ + String msg = "The given file type contains illegal characters. The file type may not contain slashes!"; //$NON-NLS-1$ + throw new RuntimeException(msg); + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java new file mode 100644 index 000000000..cfd2f5ce3 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java @@ -0,0 +1,161 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.fileserver; + +import java.io.Serializable; + +/** + * @author Robert von Burg + */ +public class FilePart implements Serializable { + + private static final long serialVersionUID = 1L; + + private String fileName; + private long fileLength; + private String fileHash; + private String fileType; + + private long partOffset; + private int partLength; + private byte[] partBytes; + private boolean lastPart; + + /** + * @param fileName + * the name of the file being referenced. This is either just the name, or a path relative to the type + * @param fileType + * defines the type of file being uploaded or retrieved. This defines in which path the file resides + */ + public FilePart(String fileName, String fileType) { + + if (fileName == null || fileName.isEmpty()) + throw new RuntimeException("fileName may not be empty!"); //$NON-NLS-1$ + if (fileType == null || fileType.isEmpty()) + throw new RuntimeException("fileType may not be empty!"); //$NON-NLS-1$ + + this.fileName = fileName; + this.fileType = fileType; + + this.partOffset = 0; + this.partLength = FileHandler.MAX_PART_SIZE; + this.partBytes = null; + } + + /** + * @return the fileLength + */ + public long getFileLength() { + return this.fileLength; + } + + /** + * @param fileLength + * the fileLength to set + */ + public void setFileLength(long fileLength) { + this.fileLength = fileLength; + } + + /** + * @return the fileHash + */ + public String getFileHash() { + return this.fileHash; + } + + /** + * @param fileHash + * the fileHash to set + */ + public void setFileHash(String fileHash) { + this.fileHash = fileHash; + } + + /** + * @return the fileType + */ + public String getFileType() { + return this.fileType; + } + + /** + * @return the partOffset + */ + public long getPartOffset() { + return this.partOffset; + } + + /** + * @param partOffset + * the partOffset to set + */ + public void setPartOffset(long partOffset) { + this.partOffset = partOffset; + } + + /** + * @return the partLength + */ + public int getPartLength() { + return this.partLength; + } + + /** + * @param partLength + * the partLength to set + */ + public void setPartLength(int partLength) { + this.partLength = partLength; + } + + /** + * @return the partBytes + */ + public byte[] getPartBytes() { + return this.partBytes; + } + + /** + * @param partBytes + * the partBytes to set + */ + public void setPartBytes(byte[] partBytes) { + this.partBytes = partBytes; + } + + /** + * @return the lastPart + */ + public boolean isLastPart() { + return this.lastPart; + } + + /** + * @param lastPart + * the lastPart to set + */ + public void setLastPart(boolean lastPart) { + this.lastPart = lastPart; + } + + /** + * @return the fileName + */ + public String getFileName() { + return this.fileName; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java new file mode 100644 index 000000000..aec70ad28 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java @@ -0,0 +1,125 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils; + +import ch.eitchnet.utils.dbc.DBC; + +/** + * @author Robert von Burg + */ +public enum StringMatchMode { + EQUALS_CASE_SENSITIVE(true, true), + EQUALS_CASE_INSENSITIVE(true, false), + CONTAINS_CASE_SENSITIVE(false, true), + CONTAINS_CASE_INSENSITIVE(false, false); + + private final boolean equals; + private final boolean caseSensitve; + + private StringMatchMode(boolean equals, boolean caseSensitive) { + this.equals = equals; + this.caseSensitve = caseSensitive; + } + + /** + * @return the caseSensitve + */ + public boolean isCaseSensitve() { + return this.caseSensitve; + } + + /** + * @return the equals + */ + public boolean isEquals() { + return this.equals; + } + + public boolean matches(String value1, String value2) { + DBC.PRE.assertNotNull("value1 must be set!", value1); //$NON-NLS-1$ + DBC.PRE.assertNotNull("value2 must be set!", value2); //$NON-NLS-1$ + if (!isEquals() && !isCaseSensitve()) + return value1.toLowerCase().contains(value2.toLowerCase()); + + if (!isCaseSensitve()) + return value1.toLowerCase().equals(value2.toLowerCase()); + + if (!isEquals()) + return value1.contains(value2); + + return value1.equals(value2); + } + + /** + *

+ * Returns {@link StringMatchMode#EQUALS_CASE_SENSITIVE} + *

+ * + *

+ * Short method, useful for static imports, or simply for shorter code + *

+ * + * @return {@link StringMatchMode#EQUALS_CASE_SENSITIVE} + */ + public static StringMatchMode es() { + return EQUALS_CASE_SENSITIVE; + } + + /** + *

+ * Returns {@link #EQUALS_CASE_INSENSITIVE} + *

+ * + *

+ * Short method, useful for static imports, or simply for shorter code + *

+ * + * @return {@link #EQUALS_CASE_INSENSITIVE} + */ + public static StringMatchMode ei() { + return EQUALS_CASE_INSENSITIVE; + } + + /** + *

+ * Returns {@link #CONTAINS_CASE_SENSITIVE} + *

+ * + *

+ * Short method, useful for static imports, or simply for shorter code + *

+ * + * @return {@link #CONTAINS_CASE_SENSITIVE} + */ + public static StringMatchMode cs() { + return CONTAINS_CASE_SENSITIVE; + } + + /** + *

+ * Returns {@link #CONTAINS_CASE_INSENSITIVE} + *

+ * + *

+ * Short method, useful for static imports, or simply for shorter code + *

+ * + * @return {@link #CONTAINS_CASE_INSENSITIVE} + */ + public static StringMatchMode ci() { + return CONTAINS_CASE_INSENSITIVE; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/Version.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/Version.java new file mode 100644 index 000000000..7b6b93dd6 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/Version.java @@ -0,0 +1,507 @@ +package ch.eitchnet.utils; + +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * This class has been adapted from org.osgi.framework.Version + * + * Version identifier. + * + *

+ * Version identifiers have four components. + *

    + *
  1. Major version. A non-negative integer.
  2. + *
  3. Minor version. A non-negative integer.
  4. + *
  5. Micro version. A non-negative integer.
  6. + *
  7. Qualifier. A text string. See {@code Version(String)} for the format of the qualifier string.
  8. + *
+ * + * Note: The qualifier can be separated by two different styles: {@link #OSGI_QUALIFIER_SEPARATOR} or + * {@link #MAVEN_QUALIFIER_SEPARATOR}. Thus the qualifier my also have two special values: + * {@link #OSGI_SNAPSHOT_QUALIFIER} or {@value #MAVEN_SNAPSHOT_QUALIFIER}. + * + *

+ * The grammar for parsing version strings is as follows: + * + *

+ * version ::= major('.'minor('.'micro('.'qualifier)?)?)?
+ * major ::= digit+
+ * minor ::= digit+
+ * micro ::= digit+
+ * qualifier ::= (alpha|digit|'_'|'-')+
+ * digit ::= [0..9]
+ * alpha ::= [a..zA..Z]
+ * 
+ * + * Note: There must be no whitespace in version. + *

+ * + *

+ * Note: {@code Version} objects are immutable and thus thread safe + *

+ */ +public class Version implements Comparable { + + public static final String SEPARATOR = "."; + public static final String OSGI_QUALIFIER_SEPARATOR = "."; + public static final String MAVEN_QUALIFIER_SEPARATOR = "-"; + + public static final String MAVEN_SNAPSHOT_QUALIFIER = "SNAPSHOT"; + public static final String OSGI_SNAPSHOT_QUALIFIER = "qualifier"; + + private final int major; + private final int minor; + private final int micro; + private final String qualifier; + + private transient String versionString; + private boolean osgiStyle; + + /** + * The empty version "0.0.0". + */ + public static final Version emptyVersion = new Version(0, 0, 0); + + /** + * Creates a version identifier from the specified numerical components. This instance will have + * {@link #isOsgiStyle()} return false + * + *

+ * The qualifier is set to the empty string. + * + * @param major + * Major component of the version identifier. + * @param minor + * Minor component of the version identifier. + * @param micro + * Micro component of the version identifier. + * @throws IllegalArgumentException + * If the numerical components are negative. + */ + public Version(final int major, final int minor, final int micro) { + this(major, minor, micro, null); + } + + /** + * Creates a version identifier from the specified components. This instance will have {@link #isOsgiStyle()} return + * false + * + * @param major + * Major component of the version identifier. + * @param minor + * Minor component of the version identifier. + * @param micro + * Micro component of the version identifier. + * @param qualifier + * Qualifier component of the version identifier. If {@code null} is specified, then the qualifier will + * be set to the empty string. + * + * @throws IllegalArgumentException + * If the numerical components are negative or the qualifier string is invalid. + */ + public Version(final int major, final int minor, final int micro, String qualifier) { + this(major, minor, micro, null, false); + } + + /** + * Creates a version identifier from the specified components. + * + * @param major + * Major component of the version identifier. + * @param minor + * Minor component of the version identifier. + * @param micro + * Micro component of the version identifier. + * @param qualifier + * Qualifier component of the version identifier. If {@code null} is specified, then the qualifier will + * be set to the empty string. + * @param osgiStyle + * if true, then this is an osgi style version, otherwise not + * + * @throws IllegalArgumentException + * If the numerical components are negative or the qualifier string is invalid. + */ + public Version(final int major, final int minor, final int micro, String qualifier, boolean osgiStyle) { + this.major = major; + this.minor = minor; + this.micro = micro; + this.qualifier = qualifier == null ? "" : qualifier; + this.versionString = null; + validate(); + } + + /** + *

+ * Creates a version identifier from the specified string. + *

+ * + * @param version + * String representation of the version identifier. + * + * @throws IllegalArgumentException + * If {@code version} is improperly formatted. + */ + private Version(final String version) { + int maj = 0; + int min = 0; + int mic = 0; + String qual = StringHelper.EMPTY; + + try { + StringTokenizer st = new StringTokenizer(version, + SEPARATOR + MAVEN_QUALIFIER_SEPARATOR + OSGI_QUALIFIER_SEPARATOR, true); + maj = Integer.parseInt(st.nextToken()); + + if (st.hasMoreTokens()) { // minor + st.nextToken(); // consume delimiter + min = Integer.parseInt(st.nextToken()); + + if (st.hasMoreTokens()) { // micro + st.nextToken(); // consume delimiter + mic = Integer.parseInt(st.nextToken()); + + if (st.hasMoreTokens()) { // qualifier + + String qualifierSeparator = st.nextToken(); // consume delimiter + this.osgiStyle = qualifierSeparator.equals(OSGI_QUALIFIER_SEPARATOR); + + qual = st.nextToken(StringHelper.EMPTY); // remaining string + + if (st.hasMoreTokens()) { // fail safe + throw new IllegalArgumentException("invalid format: " + version); + } + } + } + } + } catch (NoSuchElementException e) { + IllegalArgumentException iae = new IllegalArgumentException("invalid format: " + version); + iae.initCause(e); + throw iae; + } + + this.major = maj; + this.minor = min; + this.micro = mic; + this.qualifier = qual; + this.versionString = null; + validate(); + } + + /** + * Called by the Version constructors to validate the version components. + * + * @throws IllegalArgumentException + * If the numerical components are negative or the qualifier string is invalid. + */ + private void validate() { + if (this.major < 0) { + throw new IllegalArgumentException("negative major"); + } + if (this.minor < 0) { + throw new IllegalArgumentException("negative minor"); + } + if (this.micro < 0) { + throw new IllegalArgumentException("negative micro"); + } + char[] chars = this.qualifier.toCharArray(); + for (char ch : chars) { + if (('A' <= ch) && (ch <= 'Z')) { + continue; + } + if (('a' <= ch) && (ch <= 'z')) { + continue; + } + if (('0' <= ch) && (ch <= '9')) { + continue; + } + if ((ch == '_') || (ch == '-')) { + continue; + } + throw new IllegalArgumentException("invalid qualifier: " + this.qualifier); + } + } + + public Boolean isFullyQualified() { + return !this.qualifier.isEmpty(); + } + + /** + * Parses a version identifier from the specified string. + * + *

+ * See {@code Version(String)} for the format of the version string. + * + * @param version + * String representation of the version identifier. Leading and trailing whitespace will be ignored. + * + * @return A {@code Version} object representing the version identifier. If {@code version} is {@code null} or the + * empty string then {@code emptyVersion} will be returned. + * + * @throws IllegalArgumentException + * If {@code version} is improperly formatted. + */ + public static Version valueOf(String version) { + if (version == null) + return emptyVersion; + + String trimmedVersion = version.trim(); + if (trimmedVersion.length() == 0) + return emptyVersion; + + return new Version(trimmedVersion); + } + + /** + * Returns true if the given version string can be parsed, meaning a {@link Version} instance can be instantiated + * with it + * + * @param version + * String representation of the version identifier. Leading and trailing whitespace will be ignored. + * + * @return true if no parse errors occurr + */ + public static boolean isParseable(String version) { + try { + valueOf(version); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Returns the major component of this version identifier. + * + * @return The major component. + */ + public int getMajor() { + return this.major; + } + + /** + * Returns the minor component of this version identifier. + * + * @return The minor component. + */ + public int getMinor() { + return this.minor; + } + + /** + * Returns the micro component of this version identifier. + * + * @return The micro component. + */ + public int getMicro() { + return this.micro; + } + + /** + * Returns the qualifier component of this version identifier. + * + * @return The qualifier component. + */ + public String getQualifier() { + return this.qualifier; + } + + /** + * Returns a new {@link Version} where each version number is incremented or decreased by the given parameters + * + * @param major + * the value to increase or decrease the major part of the version + * @param minor + * the value to increase or decrease the minor part of the version + * @param micro + * the value to increase or decrease the micro part of the version + * + * @return the new Version with the version parts modified as passed in by the parameters + */ + public Version add(int major, int minor, int micro) { + return new Version(this.major + major, this.minor + minor, this.micro + micro, this.qualifier, this.osgiStyle); + } + + /** + * @return true if this is an OSGI style version, i.e. if has a qualifier, then osgi defines how the qualifier is + * appended to the version + */ + public boolean isOsgiStyle() { + return this.osgiStyle; + } + + /** + * @return true if this version is for a snapshot version, i.e. ends with {@link #MAVEN_SNAPSHOT_QUALIFIER} or + * {@link #OSGI_SNAPSHOT_QUALIFIER} + */ + public boolean isSnapshot() { + return MAVEN_SNAPSHOT_QUALIFIER.equals(this.qualifier) || OSGI_SNAPSHOT_QUALIFIER.equals(this.qualifier); + } + + /** + * Returns a hash code value for the object. + * + * @return An integer which is a hash code value for this object. + */ + @Override + public int hashCode() { + return (this.major << 24) + (this.minor << 16) + (this.micro << 8) + this.qualifier.hashCode(); + } + + /** + * Compares this {@code Version} object to another object. + * + *

+ * A version is considered to be equal to another version if the major, minor and micro components are equal + * and the qualifier component is equal (using {@code String.equals}). + * + * @param object + * The {@code Version} object to be compared. + * @return {@code true} if {@code object} is a {@code Version} and is equal to this object; {@code false} otherwise. + */ + @Override + public boolean equals(final Object object) { + if (object == this) + return true; + if (!(object instanceof Version)) + return false; + + Version other = (Version) object; + return (this.major == other.major) && (this.minor == other.minor) && (this.micro == other.micro) + && this.qualifier.equals(other.qualifier); + } + + /** + * Compares this {@code Version} object to another object ignoring the qualifier part. + * + *

+ * A version is considered to be equal to another version if the major, minor and micro components are + * equal. + * + * @param object + * The {@code Version} object to be compared. + * @return {@code true} if {@code object} is a {@code Version} and is equal to this object; {@code false} otherwise. + */ + public boolean equalsIgnoreQualifier(final Object object) { + if (object == this) + return true; + if (!(object instanceof Version)) + return false; + + Version other = (Version) object; + return (this.major == other.major) && (this.minor == other.minor) && (this.micro == other.micro); + } + + /** + * Compares this {@code Version} object to another {@code Version}. + * + *

+ * A version is considered to be less than another version if its major component is less than the other + * version's major component, or the major components are equal and its minor component is less than the other + * version's minor component, or the major and minor components are equal and its micro component is less than the + * other version's micro component, or the major, minor and micro components are equal and it's qualifier component + * is less than the other version's qualifier component (using {@code String.compareTo}). + * + *

+ * A version is considered to be equal to another version if the major, minor and micro components are equal + * and the qualifier component is equal (using {@code String.compareTo}). + * + * @param other + * The {@code Version} object to be compared. + * @return A negative integer, zero, or a positive integer if this version is less than, equal to, or greater than + * the specified {@code Version} object. + * @throws ClassCastException + * If the specified object is not a {@code Version} object. + */ + @Override + public int compareTo(final Version other) { + if (other == this) + return 0; + + int result = this.major - other.major; + if (result != 0) + return result; + + result = this.minor - other.minor; + if (result != 0) + return result; + + result = this.micro - other.micro; + if (result != 0) + return result; + + return this.qualifier.compareTo(other.qualifier); + } + + /** + * Returns the string representation of this version identifier. + * + *

+ * The format of the version string will be {@code major.minor.micro} if qualifier is the empty string or + * {@code major.minor.micro.qualifier} otherwise. + * + * @return The string representation of this version identifier. + */ + @Override + public String toString() { + if (this.versionString == null) + this.versionString = toString(this.osgiStyle); + return this.versionString; + } + + private String toString(final boolean withOsgiStyle) { + int q = this.qualifier.length(); + StringBuilder result = new StringBuilder(20 + q); + result.append(this.major); + result.append(SEPARATOR); + result.append(this.minor); + result.append(SEPARATOR); + result.append(this.micro); + if (q > 0) { + if (withOsgiStyle) { + result.append(OSGI_QUALIFIER_SEPARATOR); + } else { + result.append(MAVEN_QUALIFIER_SEPARATOR); + } + result.append(createQualifier(withOsgiStyle)); + } + return result.toString(); + } + + private String createQualifier(boolean withOsgiStyle) { + if (this.qualifier.equals(MAVEN_SNAPSHOT_QUALIFIER) || this.qualifier.equals(OSGI_SNAPSHOT_QUALIFIER)) { + if (withOsgiStyle) + return OSGI_SNAPSHOT_QUALIFIER; + return MAVEN_SNAPSHOT_QUALIFIER; + } + + return this.qualifier; + } + + /** + * @return This version represented in a maven compatible form. + */ + public String toMavenStyleString() { + return toString(false); + } + + /** + * @return This version represented in an OSGi compatible form. + */ + public String toOsgiStyleString() { + return toString(true); + } + + /** + * @return This only the major and minor version in a string + */ + public String toMajorAndMinorString() { + StringBuilder result = new StringBuilder(20); + result.append(this.major); + result.append(SEPARATOR); + result.append(this.minor); + return result.toString(); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java new file mode 100644 index 000000000..b4410b8a9 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java @@ -0,0 +1,133 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +import java.util.Date; + +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; + +/** + * @author Robert von Burg + */ +public class DateRange { + + private boolean fromInclusive; + private boolean toInclusive; + private Date fromDate; + private Date toDate; + + public DateRange from(Date from, boolean inclusive) { + this.fromDate = from; + this.fromInclusive = inclusive; + validate(); + return this; + } + + public DateRange to(Date to, boolean inclusive) { + this.toDate = to; + this.toInclusive = inclusive; + validate(); + return this; + } + + private void validate() { + if (this.toDate != null && this.fromDate != null) + DBC.INTERIM.assertTrue("From must be before to!", this.toDate.compareTo(this.fromDate) >= 0); //$NON-NLS-1$ + } + + /** + * @return from date + */ + public Date getFromDate() { + return this.fromDate; + } + + /** + * @return to date + */ + public Date getToDate() { + return this.toDate; + } + + /** + * @return true if from is set + */ + public boolean isFromBounded() { + return this.fromDate != null; + } + + /** + * @return true if to is set + */ + public boolean isToBounded() { + return this.toDate != null; + } + + /** + * @return true if both from and to are null + */ + public boolean isUnbounded() { + return this.fromDate == null && this.toDate == null; + } + + /** + * @return true if both from and to date are set + */ + public boolean isBounded() { + return this.fromDate != null && this.toDate != null; + } + + /** + * @return true if both from and to date are set and they are both equal + */ + public boolean isDate() { + return isBounded() && this.fromDate.equals(this.toDate); + } + + public boolean contains(Date date) { + DBC.PRE.assertNotNull("Date must be given!", date); //$NON-NLS-1$ + if (this.fromDate == null && this.toDate == null) + return true; + + boolean fromContains = true; + boolean toContains = true; + + if (this.toDate != null) { + int compare = this.toDate.compareTo(date); + if (this.toInclusive) + toContains = compare >= 0; + else + toContains = compare > 0; + } + + if (this.fromDate != null) { + int compare = this.fromDate.compareTo(date); + if (this.fromInclusive) + fromContains = compare <= 0; + else + fromContains = compare < 0; + } + return toContains && fromContains; + } + + @Override + public String toString() { + ISO8601FormatFactory df = ISO8601FormatFactory.getInstance(); + return df.formatDate(this.fromDate) + (this.fromInclusive ? " (inc)" : " (exc)") + " - " + + df.formatDate(this.toDate) + (this.toInclusive ? " (inc)" : " (exc)"); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java new file mode 100644 index 000000000..10ef2d75b --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Robert von Burg + */ +public class DefaultedHashMap extends HashMap { + + private static final long serialVersionUID = 1L; + private V defaultValue; + + /** + * Constructs this {@link Map} instance to have a default value on inexistent keys + * + * @param defaultValue + * the default to return if a key is not mapped + */ + public DefaultedHashMap(V defaultValue) { + this.defaultValue = defaultValue; + } + + @Override + public V get(Object key) { + return getOrDefault(key, this.defaultValue); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java new file mode 100644 index 000000000..e40c9a849 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * @author Robert von Burg + */ +public class MapOfLists { + + private Map> mapOfLists; + + public MapOfLists() { + this.mapOfLists = new HashMap<>(); + } + + public Set keySet() { + return this.mapOfLists.keySet(); + } + + public List getList(T t) { + return this.mapOfLists.get(t); + } + + public boolean addElement(T t, U u) { + List list = this.mapOfLists.get(t); + if (list == null) { + list = new ArrayList<>(); + this.mapOfLists.put(t, list); + } + return list.add(u); + } + + public boolean addList(T t, List u) { + List list = this.mapOfLists.get(t); + if (list == null) { + list = new ArrayList<>(); + this.mapOfLists.put(t, list); + } + return list.addAll(u); + } + + public boolean removeElement(T t, U u) { + List list = this.mapOfLists.get(t); + if (list == null) { + return false; + } + boolean removed = list.remove(u); + if (list.isEmpty()) { + this.mapOfLists.remove(t); + } + + return removed; + } + + public List removeList(T t) { + return this.mapOfLists.remove(t); + } + + public void clear() { + Set>> entrySet = this.mapOfLists.entrySet(); + Iterator>> iter = entrySet.iterator(); + while (iter.hasNext()) { + iter.next().getValue().clear(); + iter.remove(); + } + } + + public boolean containsList(T t) { + return this.mapOfLists.containsKey(t); + } + + public boolean containsElement(T t, U u) { + List list = this.mapOfLists.get(t); + if (list == null) + return false; + return list.contains(u); + } + + public int sizeKeys() { + return this.mapOfLists.size(); + } + + public int size() { + int size = 0; + Set>> entrySet = this.mapOfLists.entrySet(); + Iterator>> iter = entrySet.iterator(); + while (iter.hasNext()) { + size += iter.next().getValue().size(); + } + return size; + } + + public int size(T t) { + List list = this.mapOfLists.get(t); + if (list.size() == 0) + return 0; + return list.size(); + } + + public boolean isEmpty() { + return this.mapOfLists.isEmpty(); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java new file mode 100644 index 000000000..724caa53d --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java @@ -0,0 +1,172 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + *

+ * Collection to store a tree with a depth of 3 elements. This solves having to always write the declaration: + *

+ * + *
+ * Map<String, Map&lgt;String, MyObject>> mapOfMaps = new HashMap<>;
+ * 
+ * + *

+ * As an example to persist a map of MyObject where the branches are String one would write it as follows: + *

+ * + *
+ * MapOfMaps<String, String, MyObject> mapOfMaps = new MapOfMaps<>();
+ * 
+ * + * + * @author Robert von Burg + * + * @param + * The key to a map with U as the key and V as the value + * @param + * The key to get a value (leaf) + * @param + * The value stored in the tree (leaf) + */ +public class MapOfMaps { + + private Map> mapOfMaps; + + public MapOfMaps() { + this.mapOfMaps = new HashMap<>(); + } + + public Set keySet() { + return this.mapOfMaps.keySet(); + } + + public Map getMap(T t) { + return this.mapOfMaps.get(t); + } + + public V getElement(T t, U u) { + Map map = this.mapOfMaps.get(t); + if (map == null) + return null; + return map.get(u); + } + + public V addElement(T t, U u, V v) { + Map map = this.mapOfMaps.get(t); + if (map == null) { + map = new HashMap<>(); + this.mapOfMaps.put(t, map); + } + return map.put(u, v); + } + + public List getAllElements() { + List all = new ArrayList<>(); + for (Map u : this.mapOfMaps.values()) { + all.addAll(u.values()); + } + return all; + } + + public List getAllElements(T t) { + List all = new ArrayList<>(); + Map map = this.mapOfMaps.get(t); + if (map != null) { + all.addAll(map.values()); + } + return all; + } + + public void addMap(T t, Map u) { + Map map = this.mapOfMaps.get(t); + if (map == null) { + map = new HashMap<>(); + this.mapOfMaps.put(t, map); + } + map.putAll(u); + } + + public V removeElement(T t, U u) { + Map map = this.mapOfMaps.get(t); + if (map == null) { + return null; + } + V v = map.remove(u); + if (map.isEmpty()) { + this.mapOfMaps.remove(t); + } + + return v; + } + + public Map removeMap(T t) { + return this.mapOfMaps.remove(t); + } + + public void clear() { + Set>> entrySet = this.mapOfMaps.entrySet(); + Iterator>> iter = entrySet.iterator(); + while (iter.hasNext()) { + iter.next().getValue().clear(); + iter.remove(); + } + } + + public boolean containsMap(T t) { + return this.mapOfMaps.containsKey(t); + } + + public boolean containsElement(T t, U u) { + Map map = this.mapOfMaps.get(t); + if (map == null) + return false; + return map.containsKey(u); + } + + public int sizeKeys() { + return this.mapOfMaps.size(); + } + + public int size() { + int size = 0; + Set>> entrySet = this.mapOfMaps.entrySet(); + Iterator>> iter = entrySet.iterator(); + while (iter.hasNext()) { + size += iter.next().getValue().size(); + } + return size; + } + + public int size(T t) { + Map map = this.mapOfMaps.get(t); + if (map == null) + return 0; + return map.size(); + } + + public boolean isEmpty() { + return this.mapOfMaps.isEmpty(); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java new file mode 100644 index 000000000..dde14cd4f --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java @@ -0,0 +1,127 @@ +/* + * Copyright 2015 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +import java.util.List; + +/** + * @author Robert von Burg + */ +public class Paging { + + private int pageSize; + private int pageToReturn; + private int nrOfPages; + private int nrOfElements; + + private List input; + private List page; + + private Paging(int pageSize, int indexOfPageToReturn) { + this.pageSize = pageSize; + this.pageToReturn = indexOfPageToReturn; + } + + public int getPageSize() { + return this.pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + public int getPageToReturn() { + return this.pageToReturn; + } + + public void setPageToReturn(int pageToReturn) { + this.pageToReturn = pageToReturn; + } + + public int getNrOfPages() { + return this.nrOfPages; + } + + public void setNrOfPages(int nrOfPages) { + this.nrOfPages = nrOfPages; + } + + public int getNrOfElements() { + return this.nrOfElements; + } + + public void setNrOfElements(int nrOfElements) { + this.nrOfElements = nrOfElements; + } + + public List getInput() { + return this.input; + } + + public List getPage() { + return this.page; + } + + /** + * Creates a sub list of the given list by creating defining start and end from the requested page of the form + * + * @param list + * the list to paginate + * @param pageSize + * The number of items to return in each page + * @param page + * the page to return - start index is 1 + * + * @return a {@link Paging} instance from which the selected page (list) can be retrieved + */ + public static Paging asPage(List list, int pageSize, int page) { + + Paging paging = new Paging<>(pageSize, page); + paging.nrOfElements = list.size(); + + if (paging.pageSize <= 0 || paging.pageToReturn <= 0) { + paging.nrOfPages = 0; + paging.pageSize = list.size(); + paging.pageToReturn = 0; + paging.input = list; + paging.page = list; + return paging; + } + + int size = list.size(); + + // calculate maximum number of pages + paging.nrOfPages = size / paging.pageSize; + if (size % paging.pageSize != 0) + paging.nrOfPages++; + + // and from this validate requested page + paging.pageToReturn = Math.min(paging.pageToReturn, paging.nrOfPages); + + // now we can calculate the start and end of the page + int start = Math.max(0, paging.pageSize * paging.pageToReturn - paging.pageSize); + int end = Math.min(size, paging.pageSize * paging.pageToReturn); + + // and return the list + paging.page = list.subList(start, end); + + // fix page size + if (paging.page.size() < paging.pageSize) + paging.pageSize = paging.page.size(); + + return paging; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java new file mode 100644 index 000000000..ec61f3338 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +/** + * Simple wrapper for two elements + * + * @author Robert von Burg + */ +public class Tuple { + + private Object first; + private Object second; + + public Tuple() { + // + } + + public Tuple(T first, U second) { + this.first = first; + this.second = second; + } + + @SuppressWarnings("unchecked") + public T getFirst() { + return (T) this.first; + } + + public void setFirst(T first) { + this.first = first; + } + + @SuppressWarnings("unchecked") + public U getSecond() { + return (U) this.second; + } + + public void setSecond(U second) { + this.second = second; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java new file mode 100644 index 000000000..1b43adf76 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -0,0 +1,166 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.dbc; + +import java.io.File; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collection; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public enum DBC { + + PRE, INTERIM, POST; + + public void assertEquals(String msg, T value1, T value2) { + if (value1 == null && value2 == null) + return; + + if (value1 != null && value1.equals(value2)) + return; + + if (value2 != null && value2.equals(value1)) + return; + + String ex = "{0}: {1} != {2}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, value1, value2); + throw new DbcException(ex); + } + + public void assertNotEquals(String msg, T value1, T value2) { + if (value1 != null && !value1.equals(value2)) + return; + + if (value2 != null && !value2.equals(value1)) + return; + + String ex = "{0}: {1} == {2}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, value1, value2); + throw new DbcException(ex); + } + + public void assertTrue(String msg, boolean value) { + if (!value) { + String ex = "Expected true, but was false: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertFalse(String msg, boolean value) { + if (value) { + String ex = "Expected false, but was true: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertEmpty(String msg, String value) { + if (!StringHelper.isEmpty(value)) { + String ex = "{0}: Illegal non-empty value: {1}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, value); + throw new DbcException(ex); + } + } + + public void assertEmpty(String msg, Object[] array) { + assertNotNull(msg, array); + if (array.length != 0) { + String ex = "{0}: Illegal non-empty value: {1}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, Arrays.toString(array)); + throw new DbcException(ex); + } + } + + public void assertEmpty(String msg, Collection collection) { + assertNotNull(msg, collection); + if (!collection.isEmpty()) { + String ex = "{0}: Illegal non-empty value: {1}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, collection.toString()); + throw new DbcException(ex); + } + } + + public void assertNotEmpty(String msg, String value) { + if (StringHelper.isEmpty(value)) { + String ex = "{0}: Illegal empty value"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertNotEmpty(String msg, Object[] array) { + assertNotNull(msg, array); + if (array.length == 0) { + String ex = "{0}: Illegal empty value"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertNotEmpty(String msg, Collection collection) { + assertNotNull(msg, collection); + if (collection.isEmpty()) { + String ex = "{0}: Illegal empty value"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertNotNull(String msg, Object value) { + if (value == null) { + String ex = "{0}: Illegal null value"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertNull(String msg, Object value) { + if (value != null) { + String ex = "{0}: {1} != null"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, value); + throw new DbcException(ex); + } + } + + public void assertNotExists(String msg, File file) { + if (file.exists()) { + String ex = MessageFormat.format("Illegal situation as file ({0}) exists: {1}", file, msg); //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertExists(String msg, File file) { + if (!file.exists()) { + String ex = MessageFormat.format("Illegal situation as file ({0}) does not exist: {1}", file, msg); //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public class DbcException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public DbcException(String message) { + super(message); + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java new file mode 100644 index 000000000..097ac910c --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.exceptions; + +/** + * @author Robert von Burg + */ +public class XmlException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * @param message + */ + public XmlException(String message) { + super(message); + } + + /** + * @param message + * @param cause + */ + public XmlException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java new file mode 100644 index 000000000..1f580405c --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java @@ -0,0 +1,351 @@ +package ch.eitchnet.utils.helper; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.spec.KeySpec; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.dbc.DBC; + +public class AesCryptoHelper { + + private static final String CIPHER = "AES/CBC/PKCS5Padding"; + + private static final Logger logger = LoggerFactory.getLogger(AesCryptoHelper.class); + + public static OutputStream wrapEncrypt(char[] password, byte[] salt, OutputStream outputStream) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return wrapEncrypt(secret, outputStream); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static OutputStream wrapEncrypt(SecretKey secret, OutputStream outputStream) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + + // write the initialization vector, but not through the cipher output stream! + outputStream.write(initVector); + outputStream.flush(); + + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); + return cipherOutputStream; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static InputStream wrapDecrypt(char[] password, byte[] salt, InputStream inputStream) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return wrapDecrypt(secret, inputStream); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static InputStream wrapDecrypt(SecretKey secret, InputStream inputStream) { + + try { + + // read the initialization vector from the normal input stream + byte[] initVector = new byte[16]; + inputStream.read(initVector); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + return cipherInputStream; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void encrypt(char[] password, byte[] salt, String clearTextFileS, String encryptedFileS) { + + try (FileInputStream inFile = new FileInputStream(clearTextFileS); + FileOutputStream outFile = new FileOutputStream(encryptedFileS)) { + + encrypt(password, salt, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException("Failed to encrypt file " + clearTextFileS + " to " + encryptedFileS, e); + } + + logger.info("Encrypted file " + clearTextFileS + " to " + encryptedFileS); + } + + public static void encrypt(SecretKey secret, String clearTextFileS, String encryptedFileS) { + + try (FileInputStream inFile = new FileInputStream(clearTextFileS); + FileOutputStream outFile = new FileOutputStream(encryptedFileS)) { + + encrypt(secret, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException("Failed to encrypt file " + clearTextFileS + " to " + encryptedFileS, e); + } + + logger.info("Encrypted file " + clearTextFileS + " to " + encryptedFileS); + } + + public static void encrypt(char[] password, byte[] salt, InputStream inFile, OutputStream outFile) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + encrypt(secret, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void encrypt(SecretKey secret, InputStream inFile, OutputStream outFile) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + outFile.write(initVector); + + byte[] input = new byte[64]; + int bytesRead; + + while ((bytesRead = inFile.read(input)) != -1) { + byte[] output = cipher.update(input, 0, bytesRead); + if (output != null) + outFile.write(output); + } + + byte[] output = cipher.doFinal(); + if (output != null) + outFile.write(output); + + outFile.flush(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void decrypt(char[] password, byte[] salt, String encryptedFileS, String decryptedFileS) { + + try (FileInputStream fis = new FileInputStream(encryptedFileS); + FileOutputStream fos = new FileOutputStream(decryptedFileS)) { + + decrypt(password, salt, fis, fos); + + } catch (Exception e) { + throw new RuntimeException("Failed to decrypt file " + decryptedFileS + " to " + decryptedFileS, e); + } + + logger.info("Decrypted file " + encryptedFileS + " to " + decryptedFileS); + + } + + public static void decrypt(SecretKey secret, String encryptedFileS, String decryptedFileS) { + + try (FileInputStream fis = new FileInputStream(encryptedFileS); + FileOutputStream fos = new FileOutputStream(decryptedFileS)) { + + decrypt(secret, fis, fos); + + } catch (Exception e) { + throw new RuntimeException("Failed to decrypt file " + decryptedFileS + " to " + decryptedFileS, e); + } + + logger.info("Decrypted file " + encryptedFileS + " to " + decryptedFileS); + + } + + public static void decrypt(char[] password, byte[] salt, InputStream fis, OutputStream fos) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + decrypt(secret, fis, fos); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void decrypt(SecretKey secret, InputStream fis, OutputStream fos) { + + try { + + // read the initialization vector + byte[] initVector = new byte[16]; + fis.read(initVector); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + byte[] in = new byte[64]; + int read; + while ((read = fis.read(in)) != -1) { + byte[] output = cipher.update(in, 0, read); + if (output != null) + fos.write(output); + } + + byte[] output = cipher.doFinal(); + if (output != null) + fos.write(output); + + fos.flush(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] encrypt(char[] password, byte[] salt, String clearText) { + return encrypt(password, salt, clearText.getBytes()); + } + + public static byte[] encrypt(SecretKey secret, byte[] salt, String clearText) { + return encrypt(secret, clearText.getBytes()); + } + + public static byte[] encrypt(char[] password, byte[] salt, byte[] clearTextBytes) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return encrypt(secret, clearTextBytes); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] encrypt(SecretKey secret, byte[] clearTextBytes) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + + // encrypt + byte[] encryptedBytes = cipher.doFinal(clearTextBytes); + + // create result bytes + ByteBuffer byteBuffer = ByteBuffer.allocate(initVector.length + encryptedBytes.length); + byteBuffer.put(initVector); + byteBuffer.put(encryptedBytes); + + return byteBuffer.array(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] decrypt(char[] password, byte[] salt, byte[] encryptedBytes) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return decrypt(secret, encryptedBytes); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] decrypt(SecretKey secret, byte[] encryptedBytes) { + + try { + + // read initialization vector + byte[] initVector = new byte[16]; + System.arraycopy(encryptedBytes, 0, initVector, 0, 16); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + byte[] decryptedBytes = cipher.doFinal(encryptedBytes, 16, encryptedBytes.length - 16); + return decryptedBytes; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static SecretKey buildSecret(char[] password, byte[] salt) { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + KeySpec keySpec = new PBEKeySpec(password, salt, 65536, 256); + SecretKey secretKey = factory.generateSecret(keySpec); + SecretKey secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); + + return secret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java new file mode 100644 index 000000000..5404d9e7f --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.util.Arrays; + +/** + * @author Robert von Burg + */ +public class ArraysHelper { + + /** + * Returns true if the byte array contains the given byte value + * + * @param bytes + * the array to search in + * @param searchByte + * the value to search for + * + * @return true if found, false if not + */ + public static boolean contains(byte[] bytes, byte searchByte) { + for (byte b : bytes) { + if (b == searchByte) + return true; + } + return false; + } + + /** + * Creates a simple copy of the given array + * + * @param bytes + * the array to copy + * + * @return the copy + */ + public static byte[] copyOf(byte[] bytes) { + return Arrays.copyOf(bytes, bytes.length); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java new file mode 100644 index 000000000..7f648de0f --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java @@ -0,0 +1,322 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +/** + * ASCII constants + * + * @author Robert von Burg + */ +public class AsciiHelper { + + /** + * ASCII Value 0, interpretation: NUL
+ * Description: Null character + */ + public static final char NUL = (char) 0; // Null character + + /** + * ASCII Value 1, interpretation: SOH
+ * Description: Start of Header + */ + public static final char SOH = (char) 1; // Start of Header + + /** + * ASCII Value 2, interpretation: STX
+ * Description: Start of Text + */ + public static final char STX = (char) 2; // Start of Text + + /** + * ASCII Value 3, interpretation: ETX
+ * Description: End of Text + */ + public static final char ETX = (char) 3; // End of Text + + /** + * ASCII Value 4, interpretation: EOT
+ * Description: End of Transmission + */ + public static final char EOT = (char) 4; // End of Transmission + + /** + * ASCII Value 5, interpretation: ENQ
+ * Description: Enquiry + */ + public static final char ENQ = (char) 5; // Enquiry + + /** + * ASCII Value 6, interpretation: ACK
+ * Description: Acknowledgement + */ + public static final char ACK = (char) 6; // Acknowledgement + + /** + * ASCII Value 7, interpretation: BEL
+ * Description: Bell + */ + public static final char BEL = (char) 7; // Bell + + /** + * ASCII Value 8, interpretation: BS
+ * Description: Backspace + */ + public static final char BS = (char) 8; // Backspace + + /** + * ASCII Value 9, interpretation: HT
+ * Description: Horizontal Tab + */ + public static final char HT = (char) 9; // Horizontal Tab + + /** + * ASCII Value 10, interpretation: LF
+ * Description: Line Feed + */ + public static final char LF = (char) 10; // Line Feed + + /** + * ASCII Value 11, interpretation: VT
+ * Description: Vertical Tab + */ + public static final char VT = (char) 11; // Vertical Tab + + /** + * ASCII Value 12, interpretation: FF
+ * Description: Form Feed + */ + public static final char FF = (char) 12; // Form Feed + + /** + * ASCII Value 13, interpretation: CR
+ * Description: Carriage Return + */ + public static final char CR = (char) 13; // Carriage Return + + /** + * ASCII Value 14, interpretation: SO
+ * Description: Shift Out + */ + public static final char SO = (char) 14; // Shift Out + + /** + * ASCII Value 15, interpretation: SI
+ * Description: Shift In + */ + public static final char SI = (char) 15; // Shift In + + /** + * ASCII Value 16, interpretation: DLE
+ * Description: Data Link Escape + */ + public static final char DLE = (char) 16; // Data Link Escape + + /** + * ASCII Value 17, interpretation: DC1
+ * Description: (XON) Device Control 1 + */ + public static final char DC1 = (char) 17; // (XON) Device Control 1 + + /** + * ASCII Value 18, interpretation: DC2
+ * Description: Device Control 2 + */ + public static final char DC2 = (char) 18; // Device Control 2 + + /** + * ASCII Value 19 interpretation: DC3
+ * Description: (XOFF) Device Control 3 + */ + public static final char DC3 = (char) 19; // (XOFF) Device Control 3 + + /** + * ASCII Value 20, interpretation: DC4
+ * Description: Device Control 4 + */ + public static final char DC4 = (char) 20; // Device Control 4 + + /** + * ASCII Value 21, interpretation: NAK
+ * Description: Negative Acknowledgment + */ + public static final char NAK = (char) 21; // Negative Acknowledgment + + /** + * ASCII Value 22, interpretation: SYN
+ * Description: Synchronous Idle + */ + public static final char SYN = (char) 22; // Synchronous Idle + + /** + * ASCII Value 23, interpretation: ETB
+ * Description: End of Transmission Block + */ + public static final char ETB = (char) 23; // End of Transmission Block + + /** + * ASCII Value 24, interpretation: CAN
+ * Description: Cancel + */ + public static final char CAN = (char) 24; // Cancel + + /** + * ASCII Value 25, interpretation: EM
+ * Description: End of Medium + */ + public static final char EM = (char) 25; // End of Medium + + /** + * ASCII Value 26, interpretation: SUB
+ * Description: Substitute + */ + public static final char SUB = (char) 26; // Substitute + + /** + * ASCII Value 27, interpretation: ESC
+ * Description: Escape + */ + public static final char ESC = (char) 27; // Escape + + /** + * ASCII Value 28, interpretation: FS
+ * Description: File Separator + */ + public static final char FS = (char) 28; // File Separator + + /** + * ASCII Value 29, interpretation: GS
+ * Description: Group Separator + */ + public static final char GS = (char) 29; // Group Separator + + /** + * ASCII Value 30, interpretation: RS
+ * Description: Request to Send (Record Separator) + */ + public static final char RS = (char) 30; // Request to Send (Record Separator) + + /** + * ASCII Value 31, interpretation: US
+ * Description: Unit Separator + */ + public static final char US = (char) 31; // Unit Separator + + /** + * ASCII Value 32, interpretation: SP
+ * Description: Space + */ + public static final char SP = (char) 32; // Space + + /** + * ASCII Value 127, interpretation: DEL
+ * Description: Delete + */ + public static final char DEL = (char) 127; // Delete + + /** + * Returns the ASCII Text of a certain bye value + * + * @param b + * @return String + */ + public static String getAsciiText(byte b) { + return getAsciiText((char) b); + } + + /** + * Returns the ASCII Text of a certain char value + * + * @param c + * @return String + */ + @SuppressWarnings("nls") + public static String getAsciiText(char c) { + // else if(c == ) { return "";} + if (c == NUL) { + return "NUL"; + } else if (c == SOH) { + return "SOH"; + } else if (c == STX) { + return "STX"; + } else if (c == ETX) { + return "ETX"; + } else if (c == EOT) { + return "EOT"; + } else if (c == ENQ) { + return "ENQ"; + } else if (c == ACK) { + return "ACK"; + } else if (c == BEL) { + return "BEL"; + } else if (c == BS) { + return "BS"; + } else if (c == HT) { + return "HT"; + } else if (c == LF) { + return "LF"; + } else if (c == VT) { + return "VT"; + } else if (c == FF) { + return "FF"; + } else if (c == CR) { + return "CR"; + } else if (c == SO) { + return "SO"; + } else if (c == SI) { + return "SI"; + } else if (c == DLE) { + return "DLE"; + } else if (c == DC1) { + return "DC1"; + } else if (c == DC2) { + return "DC2"; + } else if (c == DC3) { + return "DC3"; + } else if (c == DC4) { + return "DC4"; + } else if (c == NAK) { + return "NAK"; + } else if (c == SYN) { + return "SYN"; + } else if (c == ETB) { + return "ETB"; + } else if (c == CAN) { + return "CAN"; + } else if (c == EM) { + return "EM"; + } else if (c == SUB) { + return "SUB"; + } else if (c == ESC) { + return "ESC"; + } else if (c == FS) { + return "FS"; + } else if (c == GS) { + return "GS"; + } else if (c == RS) { + return "RS"; + } else if (c == US) { + return "US"; + } else if (c == SP) { + return "SP"; + } else if (c == DEL) { + return "DEL"; + } else if ((c) > 32 && (c) < 127) { + return String.valueOf(c); + } else { + return "(null:" + (byte) c + ")"; + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java new file mode 100644 index 000000000..8a3437a49 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java @@ -0,0 +1,1008 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.text.MessageFormat; + +/** + *

+ * This class implements the encoding and decoding of RFC 4648 https://tools.ietf.org/html/rfc4648. + *

+ * + *

+ * The following implementations are supported: + *

    + *
  • Base64
  • + *
  • Base64 URL safe
  • + *
  • Base32
  • + *
  • Base32 HEX
  • + *
  • Base16 / HEX
  • + *
+ *

+ * + *

+ * As a further bonus, it is possible to use the algorithm with a client specified alphabet. In this case the client is + * responsible for generating the alphabet for use in the decoding + *

+ * + *

+ * This class also implements a number of utility methods to check if given data is in a valid encoding + *

+ * + * @author Robert von Burg + */ +public class BaseEncoding { + + // private static final Logger logger = LoggerFactory.getLogger(BaseEncoding.class); + + private static final int PADDING_64 = 2; + private static final int PADDING_32 = 6; + + public static final byte PAD = '='; + + static final byte[] BASE_16 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + static final byte[] BASE_32 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7' }; + + static final byte[] BASE_32_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' }; + + static final byte[] BASE_32_DMEDIA = { '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y' }; + + static final byte[] BASE_32_CROCKFORD = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; + + static final byte[] BASE_64 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', + 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' }; + + static final byte[] BASE_64_SAFE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '-', '_' }; + + // these reverse base encoding alphabets were generated from the actual alphabet + + static final byte[] REV_BASE_16 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_32 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_32_CROCKFORD = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, + -1, 18, 19, -1, 20, 21, -1, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1 }; + + static final byte[] REV_BASE_32_DMEDIA = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, -1, -1, -1, -1, -1, -1, -1, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_32_HEX = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_64 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, + 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_64_SAFE = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; + + public static byte[] toBase64(byte[] bytes) { + return toBase64(BASE_64, bytes); + } + + public static String toBase64(String data) { + return toBase64(BASE_64, data); + } + + public static byte[] toBase64Safe(byte[] bytes) { + return toBase64(BASE_64_SAFE, bytes); + } + + public static String toBase64Safe(String data) { + return toBase64(BASE_64_SAFE, data); + } + + public static String toBase64(byte[] alphabet, String data) { + return new String(toBase64(alphabet, data.getBytes())); + } + + public static byte[] toBase32(byte[] bytes) { + return toBase32(BASE_32, bytes); + } + + public static String toBase32(String data) { + return toBase32(BASE_32, data); + } + + public static byte[] toBase32Hex(byte[] bytes) { + return toBase32(BASE_32_HEX, bytes); + } + + public static String toBase32Hex(String data) { + return toBase32(BASE_32_HEX, data); + } + + public static byte[] toBase32Dmedia(byte[] bytes) { + return toBase32(BASE_32_DMEDIA, bytes); + } + + public static String toBase32Dmedia(String data) { + return toBase32(BASE_32_DMEDIA, data); + } + + public static byte[] toBase32Crockford(byte[] bytes) { + return toBase32(BASE_32_CROCKFORD, bytes); + } + + public static String toBase32Crockford(String data) { + return toBase32(BASE_32_CROCKFORD, data); + } + + public static String toBase32(byte[] alphabet, String data) { + return new String(toBase32(alphabet, data.getBytes())); + } + + public static byte[] toBase16(byte[] bytes) { + return toBase16(BASE_16, bytes); + } + + public static String toBase16(String data) { + return toBase16(BASE_16, data); + } + + public static String toBase16(byte[] alphabet, String data) { + return new String(toBase16(alphabet, data.getBytes())); + } + + public static byte[] fromBase64(byte[] bytes) { + return fromBase64(REV_BASE_64, bytes); + } + + public static String fromBase64(String data) { + return fromBase64(REV_BASE_64, data); + } + + public static String fromBase64Safe(String data) { + return fromBase64(REV_BASE_64_SAFE, data); + } + + public static String fromBase64(byte[] alphabet, String data) { + return new String(fromBase64(alphabet, data.getBytes())); + } + + public static byte[] fromBase32(byte[] bytes) { + return fromBase32(REV_BASE_32, bytes); + } + + public static String fromBase32(String data) { + return fromBase32(REV_BASE_32, data); + } + + public static byte[] fromBase32Hex(byte[] bytes) { + return fromBase32(REV_BASE_32_HEX, bytes); + } + + public static String fromBase32Hex(String data) { + return fromBase32(REV_BASE_32_HEX, data); + } + + public static byte[] fromBase32Dmedia(byte[] bytes) { + return fromBase32(REV_BASE_32_DMEDIA, bytes); + } + + public static String fromBase32Dmedia(String data) { + return fromBase32(REV_BASE_32_DMEDIA, data); + } + + public static byte[] fromBase32Crockford(byte[] bytes) { + return fromBase32(REV_BASE_32_CROCKFORD, bytes); + } + + public static String fromBase32Crockford(String data) { + return fromBase32(REV_BASE_32_CROCKFORD, data); + } + + public static String fromBase32(byte[] alphabet, String data) { + return new String(fromBase32(alphabet, data.getBytes())); + } + + public static byte[] fromBase16(byte[] bytes) { + return fromBase16(REV_BASE_16, bytes); + } + + public static String fromBase16(String data) { + return fromBase16(REV_BASE_16, data); + } + + public static String fromBase16(byte[] alphabet, String data) { + return new String(fromBase16(alphabet, data.getBytes())); + } + + public static boolean isBase64(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_64, bytes, PADDING_64); + } + + public static boolean isBase64(String data) { + return isEncodedByAlphabet(REV_BASE_64, data, PADDING_64); + } + + public static boolean isBase64Safe(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_64_SAFE, bytes, PADDING_64); + } + + public static boolean isBase64Safe(String data) { + return isEncodedByAlphabet(REV_BASE_64_SAFE, data, PADDING_64); + } + + public static boolean isBase32(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_32, bytes, PADDING_32); + } + + public static boolean isBase32(String data) { + return isEncodedByAlphabet(REV_BASE_32, data, PADDING_32); + } + + public static boolean isBase32Hex(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_32_HEX, bytes, PADDING_32); + } + + public static boolean isBase32Hex(String data) { + return isEncodedByAlphabet(REV_BASE_32_HEX, data, PADDING_32); + } + + public static boolean isBase32Crockford(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_32_CROCKFORD, bytes, PADDING_32); + } + + public static boolean isBase32Crockford(String data) { + return isEncodedByAlphabet(REV_BASE_32_CROCKFORD, data, PADDING_32); + } + + public static boolean isBase32Dmedia(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_32_DMEDIA, bytes, PADDING_32); + } + + public static boolean isBase32Dmedia(String data) { + return isEncodedByAlphabet(REV_BASE_32_DMEDIA, data, PADDING_32); + } + + public static boolean isBase16(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_16, bytes, 0); + } + + public static boolean isBase16(String data) { + return isEncodedByAlphabet(REV_BASE_16, data, 0); + } + + public static boolean isEncodedByAlphabet(byte[] alphabet, String data, int padding) { + return isEncodedByAlphabet(alphabet, data.getBytes(), padding); + } + + /** + * @param alphabet + * @param bytes + * @param maxPadding + * + * @return + */ + public static boolean isEncodedByAlphabet(byte[] alphabet, byte[] bytes, int maxPadding) { + if (bytes.length == 0) + return true; + + int paddingStart = 0; + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + if (b < 0 || b > alphabet.length) + return false; + + byte c = alphabet[b]; + if (c == -1) { + + if (b == PAD && maxPadding != 0) { + if (paddingStart == 0) + paddingStart = i; + + continue; + } + + return false; + } + } + + if (paddingStart != 0 && paddingStart < (bytes.length - maxPadding)) + return false; + + return true; + } + + /** + * Encodes the given data to a 64-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet + * + * @param alphabet + * the 64-bit alphabet to use + * @param bytes + * the bytes to encode + * + * @return the encoded data + */ + public static byte[] toBase64(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) + return new byte[0]; + if (alphabet.length != 64) { + String msg = MessageFormat.format("Alphabet does not have expected size 64 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + + // 6 bits input for every 8 bits (1 byte) output + // least common multiple of 6 bits input and 8 bits output = 24 + // and output multiple is then lcm(6, 8) / 6 = 4 + // thus we need to write multiples of 4 bytes of data + int bitsIn = 6; + int outputMultiple = 4; + + // first convert to bits + int nrOfInputBytes = bytes.length; + int nrOfInputBits = nrOfInputBytes * Byte.SIZE; + + // calculate number of bits missing for multiples of bitsIn + int inputPadding = nrOfInputBits % bitsIn; + int nrOfOutputBytes; + if (inputPadding == 0) + nrOfOutputBytes = nrOfInputBits / bitsIn; + else + nrOfOutputBytes = (nrOfInputBits + (bitsIn - (inputPadding))) / bitsIn; + + // calculate number of bits missing for multiple of bitsOut + int nrOfBytesPadding = outputMultiple - (nrOfOutputBytes % outputMultiple); + if (nrOfBytesPadding == outputMultiple) + nrOfBytesPadding = 0; + + // actual result array is multiples of bitsOut/8 thus sum of: + int txtLength = nrOfOutputBytes + nrOfBytesPadding; + +// logger.info(String.format("Input: %d bytes, Output: %d bytes, Padding: %d bytes, TextLength: %d", +// nrOfInputBytes, nrOfOutputBytes, nrOfBytesPadding, txtLength)); + + byte[] txt = new byte[txtLength]; + long bits; + int bytesPos = 0; + int txtPos = 0; + while (bytesPos < bytes.length) { + + int remaining = bytes.length - bytesPos; + // get up to 24 bits of data in 3 bytes + bits = 0; + if (remaining >= 3) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 16) // + | ((long) (bytes[bytesPos++] & 0xff) << 8) // + | ((bytes[bytesPos++] & 0xff)); + + } else if (remaining == 2) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 16) // + | ((long) (bytes[bytesPos++] & 0xff) << 8); + + } else if (remaining == 1) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 16); + } + + // always start at 24. bit + int bitPos = 23; + + // always write 24 bits (6 bits * 4), but this will also write into the padding + // we will fix this by writing the padding as has been calculated previously + while (bitPos >= 0) { + + int index = 0; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + byte character = alphabet[index]; + txt[txtPos] = character; + txtPos++; + } + } + + // write any padding that was calculated + if (nrOfBytesPadding != 0) { + int paddingPos = txtPos - nrOfBytesPadding; + for (; paddingPos < txtLength; paddingPos++) { + txt[paddingPos] = PAD; + } + } + + return txt; + } + + /** + * Encodes the given data to a 32-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet + * + * @param alphabet + * the 32-bit alphabet to use + * @param bytes + * the bytes to encode + * + * @return the encoded data + */ + public static byte[] toBase32(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) + return new byte[0]; + if (alphabet.length != 32) { + String msg = MessageFormat.format("Alphabet does not have expected size 32 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + + // 5 bits input for every 8 bits (1 byte) output + // least common multiple of 5 bits input and 8 bits output = 40 + // and output multiple is then lcm(5, 8) / 5 = 8 + // thus we need to write multiples of 8 bytes of data + int bitsIn = 5; + int outputMultiple = 8; + + // first convert to bits + int nrOfInputBytes = bytes.length; + int nrOfInputBits = nrOfInputBytes * Byte.SIZE; + + // calculate number of bits missing for multiples of bitsIn + int inputPadding = nrOfInputBits % bitsIn; + int nrOfOutputBytes; + if (inputPadding == 0) + nrOfOutputBytes = nrOfInputBits / bitsIn; + else + nrOfOutputBytes = (nrOfInputBits + (bitsIn - (inputPadding))) / bitsIn; + + // calculate number of bits missing for multiple of bitsOut + int nrOfBytesPadding = outputMultiple - (nrOfOutputBytes % outputMultiple); + if (nrOfBytesPadding == outputMultiple) + nrOfBytesPadding = 0; + + // actual result array is multiples of bitsOut/8 thus sum of: + int txtLength = nrOfOutputBytes + nrOfBytesPadding; + +// logger.info(String.format("Input: %d bytes, Output: %d bytes, Padding: %d bytes, TextLength: %d", +// nrOfInputBytes, nrOfOutputBytes, nrOfBytesPadding, txtLength)); + + byte[] txt = new byte[txtLength]; + long bits; + int bytesPos = 0; + int txtPos = 0; + while (bytesPos < bytes.length) { + + int remaining = bytes.length - bytesPos; + // get up to 40 bits of data in 5 bytes + bits = 0; + if (remaining >= 5) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // + | ((long) (bytes[bytesPos++] & 0xff) << 24) // + | ((long) (bytes[bytesPos++] & 0xff) << 16) // + | ((long) (bytes[bytesPos++] & 0xff) << 8) // + | ((bytes[bytesPos++] & 0xff)); + + } else if (remaining == 4) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // + | ((long) (bytes[bytesPos++] & 0xff) << 24) // + | ((long) (bytes[bytesPos++] & 0xff) << 16) // + | ((long) (bytes[bytesPos++] & 0xff) << 8); + + } else if (remaining == 3) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // + | ((long) (bytes[bytesPos++] & 0xff) << 24) // + | ((long) (bytes[bytesPos++] & 0xff) << 16); + + } else if (remaining == 2) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // + | ((long) (bytes[bytesPos++] & 0xff) << 24); + + } else if (remaining == 1) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32); + + } + + // always start at 40. bit + int bitPos = 39; + + // always write 40 bits (5 bytes * 8 multiples), but this will also write into the padding + // we will fix this by writing the padding as has been calculated previously + while (bitPos >= 0) { + + int index = 0; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + byte character = alphabet[index]; + txt[txtPos] = character; + txtPos++; + } + } + + // write any padding that was calculated + if (nrOfBytesPadding != 0) { + int paddingPos = txtPos - nrOfBytesPadding; + for (; paddingPos < txtLength; paddingPos++) { + txt[paddingPos] = PAD; + } + } + + return txt; + } + + /** + * Encodes the given data to a 16-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet + * + * @param alphabet + * the 16-bit alphabet to use + * @param bytes + * the bytes to encode + * + * @return the encoded data + */ + public static byte[] toBase16(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) + return new byte[0]; + if (alphabet.length != 16) { + String msg = MessageFormat.format("Alphabet does not have expected size 16 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + + // calculate output text length + int nrOfInputBytes = bytes.length; + int nrOfOutputBytes = nrOfInputBytes * 2; + int txtLength = nrOfOutputBytes; + +// logger.info(String.format("Input: %d bytes, Output: %d bytes, TextLength: %d", nrOfInputBytes, nrOfOutputBytes, +// txtLength)); + + byte[] txt = new byte[txtLength]; + byte bits; + int bytesPos = 0; + int txtPos = 0; + while (bytesPos < bytes.length) { + + // get 8 bits of data (1 byte) + bits = bytes[bytesPos++]; + + // now write the 8 bits as 2 * 4 bits + + // output byte 1 + int index = (bits >>> 4) & 0xf; + byte character = alphabet[index]; + txt[txtPos] = character; + txtPos++; + + // output byte 2 + index = bits & 0xf; + character = alphabet[index]; + txt[txtPos] = character; + txtPos++; + } + + return txt; + } + + /** + * Decodes the given Base64 encoded data to the original data set + * + * @param alphabet + * the 64-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase64(byte[] alphabet, byte[] bytes) { + int inputLength = bytes.length; + if (inputLength == 0) + return new byte[0]; + if ((inputLength % 4) != 0) { + String msg = MessageFormat.format( + "The input bytes to be decoded must be multiples of 4, but is multiple of {0}", //$NON-NLS-1$ + (inputLength % 4)); + throw new RuntimeException(msg); + } + + if (alphabet.length != 128) { + String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + + if (!isEncodedByAlphabet(alphabet, bytes, PADDING_64)) + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ + + // find how much padding we have + int nrOfBytesPadding = 0; + if (bytes[inputLength - 1] == PAD) { + int end = inputLength - 1; + while (bytes[end] == PAD) + end--; + if (end != inputLength - 1) + nrOfBytesPadding = inputLength - 1 - end; + } + + int inputDataLength = inputLength - nrOfBytesPadding; + int dataLengthBits = inputDataLength * 6; // 6 bits data for every 8 bits inputs + // multiples of 6 required + // truncating is no problem due to the input having padding to have multiples of 32 bits + dataLengthBits = dataLengthBits - (dataLengthBits % 8); + int dataLengthBytes = dataLengthBits / 8; + + // f => Zg== + // fo => Zm8= + // foo => Zm9v + + // we want to write as much as 24 bits in multiples of 6. + // these multiples of 6 are read from multiples of 8 + // i.e. we discard 2 bits from every 8 bits input + // thus we need to read 24 / 6 = 4 bytes + + byte[] data = new byte[dataLengthBytes]; + int dataPos = 0; + + // but we simply ignore the padding + int bytesPos = 0; + while (bytesPos < inputDataLength) { + int remaining = inputDataLength - bytesPos; + + long bits; + if (remaining >= 4) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6) // + | (alphabet[bytes[bytesPos++]] & 63); + + } else if (remaining == 3) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6); + + } else if (remaining == 2) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12); +// +// long b; +// byte a; +// a = bytes[0]; +// logger.info("1 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); +// b = (byte) (alphabet[a] & 63); +// logger.info("1 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); +// a = bytes[1]; +// logger.info("2 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); +// b = (byte) (alphabet[a] & 63); +// logger.info("2 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); + + } else if (remaining == 1) { + + bits = ((alphabet[bytes[bytesPos++]] & 63) << 18); + + } else { + + bits = 0L; + } + + // we can truncate to 8 bits + int toWrite = remaining >= 4 ? 3 : remaining * 6 / 8; + // max is always 3 bytes data from 4 bytes input + +// logger.info("toWrite: " + toWrite + ", remaining: " + remaining); +// logger.info("bits: " + ByteHelper.asBinary(bits)); + + // always start at 24. bit + int bitPos = 23; + // always write 24 bits (8 bits * n bytes) + for (int i = 0; i < toWrite; i++) { + + byte value = 0; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + data[dataPos] = value; + dataPos++; + } + } + + return data; + } + + /** + * Decodes the given Base32 encoded data to the original data set + * + * @param alphabet + * the 32-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase32(byte[] alphabet, byte[] bytes) { + int inputLength = bytes.length; + if (inputLength == 0) + return new byte[0]; + if ((inputLength % 8) != 0) { + String msg = "The input bytes to be decoded must be multiples of 8, but is multiple of {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, (inputLength % 8)); + throw new RuntimeException(msg); + } + + if (alphabet.length != 128) { + String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + + if (!isEncodedByAlphabet(alphabet, bytes, PADDING_32)) + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ + + // find how much padding we have + int nrOfBytesPadding = 0; + if (bytes[inputLength - 1] == PAD) { + int end = inputLength - 1; + while (bytes[end] == PAD) + end--; + if (end != inputLength - 1) + nrOfBytesPadding = inputLength - 1 - end; + } + + int inputDataLength = inputLength - nrOfBytesPadding; + int dataLengthBits = inputDataLength * 5; // 5 bits data for every 8 bits inputs + // multiples of 8 required + // truncating is no problem due to the input having padding to have multiples of 40 bits + dataLengthBits = dataLengthBits - (dataLengthBits % 8); + int dataLengthBytes = dataLengthBits / 8; + +// logger.info("Input " + inputLength + " bytes, InputData " + inputDataLength + " bytes, Padding: " +// + nrOfBytesPadding + " bytes, dataLength: " + dataLengthBits + " bits, dataLengthBytes: " +// + dataLengthBytes + " bytes"); +// logger.info(ByteHelper.asBinary(bytes)); + + // we want to write as much as 40 bits in multiples of 5. + // these multiples of 5 are read from multiples of 8 + // i.e. we discard 3 bits from every 8 bits input + // thus we need to read 40 / 5 = 8 bytes + + byte[] data = new byte[dataLengthBytes]; + int dataPos = 0; + + // but we simply ignore the padding + int bytesPos = 0; + while (bytesPos < inputDataLength) { + int remaining = inputDataLength - bytesPos; + + long bits; + if (remaining >= 8) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5) // + | (alphabet[bytes[bytesPos++]] & 31); + + } else if (remaining >= 7) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5); + + } else if (remaining == 6) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10); + + } else if (remaining == 5) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15); + + } else if (remaining == 4) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20); + + } else if (remaining == 3) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25); + + } else if (remaining == 2) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30); + + } else if (remaining == 1) { + + bits = ((alphabet[bytes[bytesPos++]] & 31) << 35); + + } else { + + bits = 0L; + } + + // we can truncate to 8 bits + int toRead = remaining >= 8 ? 5 : remaining * 5 / 8; + // max is always 5 bytes data from 8 bytes input + + // always start at 40. bit + int bitPos = 39; + // always write 40 bits (5 bytes * 8 bits) + for (int i = 0; i < toRead; i++) { + + byte value = 0; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + data[dataPos] = value; + dataPos++; + } + } + + return data; + } + + /** + * Decodes the given Base16 encoded data to the original data set + * + * @param alphabet + * the 16-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase16(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) + return new byte[0]; + if ((bytes.length % 2) != 0) { + String msg = "The input bytes to be decoded must be multiples of 4, but is multiple of {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, (bytes.length % 4)); + throw new RuntimeException(msg); + } + + if (alphabet.length != 128) { + String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } + + if (!isEncodedByAlphabet(alphabet, bytes, 0)) + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ + + int dataLength = bytes.length / 2; + + byte[] data = new byte[dataLength]; + for (int i = 0; i < bytes.length;) { + + byte b1 = bytes[i++]; + byte b2 = bytes[i++]; + + String msgOutOfRange = "Value at index {0} is not in range of alphabet (0-127){1}"; //$NON-NLS-1$ + if (b1 < 0) { + msgOutOfRange = MessageFormat.format(msgOutOfRange, (i - 2), b1); + throw new IllegalArgumentException(msgOutOfRange); + } + if (b2 < 0) { + msgOutOfRange = MessageFormat.format(msgOutOfRange, (i - 1), b2); + throw new IllegalArgumentException(msgOutOfRange); + } + + byte c1 = alphabet[b1]; + byte c2 = alphabet[b2]; + + String msgIllegalValue = "Value at index {0} is referencing illegal value in alphabet: {1}"; //$NON-NLS-1$ + if (c1 == -1) { + msgIllegalValue = MessageFormat.format(msgIllegalValue, (i - 2), b1); + throw new IllegalArgumentException(msgIllegalValue); + } + if (c2 == -1) { + msgIllegalValue = MessageFormat.format(msgIllegalValue, (i - 2), b2); + throw new IllegalArgumentException(msgIllegalValue); + } + + int dataIndex = (i / 2) - 1; + int value = ((c1 << 4) & 0xff) | c2; + data[dataIndex] = (byte) value; + } + + return data; + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java new file mode 100644 index 000000000..97697ee28 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java @@ -0,0 +1,267 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +/** + * @author Robert von Burg + */ +public class ByteHelper { + + /** + * Creates a long of the given byte array. They byte array must be 8 bytes long. The byte at index 0 is the highest + * byte + * + * @param bytes + * the bytes to convert to a long + * + * @return the long created from the bytes + */ + public static long toLong(byte[] bytes) { + + if (bytes.length != 8) + throw new IllegalArgumentException("The input byte array for a long must have 8 values"); //$NON-NLS-1$ + + return ((long) (bytes[0] & 0xff) << 56) // + | ((long) (bytes[1] & 0xff) << 48) // + | ((long) (bytes[2] & 0xff) << 40) // + | ((long) (bytes[3] & 0xff) << 32) // + | ((long) (bytes[4] & 0xff) << 24) // + | ((long) (bytes[5] & 0xff) << 16) // + | ((long) (bytes[6] & 0xff) << 8) // + | ((bytes[7] & 0xff)); + } + + /** + * Creates an integer of the given byte array. They byte array must be 4 bytes long. The byte at index 0 is the + * highest byte + * + * @param bytes + * the bytes to convert to an integer + * + * @return the integer created from the bytes + */ + public static int toInt(byte[] bytes) { + + if (bytes.length != 4) + throw new IllegalArgumentException("The input byte array for a long must have 4 values"); //$NON-NLS-1$ + + return ((bytes[0] & 0xff) << 24) // + | ((bytes[1] & 0xff) << 16) // + | ((bytes[2] & 0xff) << 8) // + | ((bytes[3] & 0xff)); + } + + /** + * Formats the given byte to a binary string + * + * @param b + * the byte to format to a binary string + * + * @return the binary string + */ + public static String asBinary(byte b) { + + StringBuilder sb = new StringBuilder(); + + sb.append(((b >>> 7) & 1)); + sb.append(((b >>> 6) & 1)); + sb.append(((b >>> 5) & 1)); + sb.append(((b >>> 4) & 1)); + sb.append(((b >>> 3) & 1)); + sb.append(((b >>> 2) & 1)); + sb.append(((b >>> 1) & 1)); + sb.append(((b >>> 0) & 1)); + + return sb.toString(); + } + + /** + * Formats the given byte array to a binary string, separating each byte by a space + * + * @param bytes + * the byte to format to a binary string + * + * @return the binary string + */ + public static String asBinary(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + + for (byte b : bytes) { + sb.append(asBinary(b)); + sb.append(StringHelper.SPACE); + } + + return sb.toString(); + } + + /** + * Formats the given integer to a binary string, each byte is separated by a space + * + * @param i + * the integer to format to a string + * + * @return the binary string + */ + public static String asBinary(int i) { + + StringBuilder sb = new StringBuilder(); + + sb.append(((i >>> 31) & 1)); + sb.append(((i >>> 30) & 1)); + sb.append(((i >>> 29) & 1)); + sb.append(((i >>> 28) & 1)); + sb.append(((i >>> 27) & 1)); + sb.append(((i >>> 26) & 1)); + sb.append(((i >>> 25) & 1)); + sb.append(((i >>> 24) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 23) & 1)); + sb.append(((i >>> 22) & 1)); + sb.append(((i >>> 21) & 1)); + sb.append(((i >>> 20) & 1)); + sb.append(((i >>> 19) & 1)); + sb.append(((i >>> 18) & 1)); + sb.append(((i >>> 17) & 1)); + sb.append(((i >>> 16) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 15) & 1)); + sb.append(((i >>> 14) & 1)); + sb.append(((i >>> 13) & 1)); + sb.append(((i >>> 12) & 1)); + sb.append(((i >>> 11) & 1)); + sb.append(((i >>> 10) & 1)); + sb.append(((i >>> 9) & 1)); + sb.append(((i >>> 8) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 7) & 1)); + sb.append(((i >>> 6) & 1)); + sb.append(((i >>> 5) & 1)); + sb.append(((i >>> 4) & 1)); + sb.append(((i >>> 3) & 1)); + sb.append(((i >>> 2) & 1)); + sb.append(((i >>> 1) & 1)); + sb.append(((i >>> 0) & 1)); + + return sb.toString(); + } + + /** + * Formats the given long to a binary string, each byte is separated by a space + * + * @param i + * the long to format + * + * @return the binary string + */ + public static String asBinary(long i) { + + StringBuilder sb = new StringBuilder(); + + sb.append(((i >>> 63) & 1)); + sb.append(((i >>> 62) & 1)); + sb.append(((i >>> 61) & 1)); + sb.append(((i >>> 60) & 1)); + sb.append(((i >>> 59) & 1)); + sb.append(((i >>> 58) & 1)); + sb.append(((i >>> 57) & 1)); + sb.append(((i >>> 56) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 55) & 1)); + sb.append(((i >>> 54) & 1)); + sb.append(((i >>> 53) & 1)); + sb.append(((i >>> 52) & 1)); + sb.append(((i >>> 51) & 1)); + sb.append(((i >>> 50) & 1)); + sb.append(((i >>> 49) & 1)); + sb.append(((i >>> 48) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 47) & 1)); + sb.append(((i >>> 46) & 1)); + sb.append(((i >>> 45) & 1)); + sb.append(((i >>> 44) & 1)); + sb.append(((i >>> 43) & 1)); + sb.append(((i >>> 42) & 1)); + sb.append(((i >>> 41) & 1)); + sb.append(((i >>> 40) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 39) & 1)); + sb.append(((i >>> 38) & 1)); + sb.append(((i >>> 37) & 1)); + sb.append(((i >>> 36) & 1)); + sb.append(((i >>> 35) & 1)); + sb.append(((i >>> 34) & 1)); + sb.append(((i >>> 33) & 1)); + sb.append(((i >>> 32) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 31) & 1)); + sb.append(((i >>> 30) & 1)); + sb.append(((i >>> 29) & 1)); + sb.append(((i >>> 28) & 1)); + sb.append(((i >>> 27) & 1)); + sb.append(((i >>> 26) & 1)); + sb.append(((i >>> 25) & 1)); + sb.append(((i >>> 24) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 23) & 1)); + sb.append(((i >>> 22) & 1)); + sb.append(((i >>> 21) & 1)); + sb.append(((i >>> 20) & 1)); + sb.append(((i >>> 19) & 1)); + sb.append(((i >>> 18) & 1)); + sb.append(((i >>> 17) & 1)); + sb.append(((i >>> 16) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 15) & 1)); + sb.append(((i >>> 14) & 1)); + sb.append(((i >>> 13) & 1)); + sb.append(((i >>> 12) & 1)); + sb.append(((i >>> 11) & 1)); + sb.append(((i >>> 10) & 1)); + sb.append(((i >>> 9) & 1)); + sb.append(((i >>> 8) & 1)); + + sb.append(StringHelper.SPACE); + + sb.append(((i >>> 7) & 1)); + sb.append(((i >>> 6) & 1)); + sb.append(((i >>> 5) & 1)); + sb.append(((i >>> 4) & 1)); + sb.append(((i >>> 3) & 1)); + sb.append(((i >>> 2) & 1)); + sb.append(((i >>> 1) & 1)); + sb.append(((i >>> 0) & 1)); + + return sb.toString(); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java new file mode 100644 index 000000000..b24d3cd9f --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java @@ -0,0 +1,104 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.text.MessageFormat; + +/** + * Utility class for working with {@link Class Classes} + * + * @author Robert von Burg + */ +public class ClassHelper { + + /** + * Returns an instance of the class' name given by instantiating the class through an empty arguments constructor + * + * @param + * the type of the class to return + * @param className + * the name of a class to instantiate through an empty arguments constructor + * + * @return the newly instantiated object from the given class name + * + * @throws IllegalArgumentException + * if the class could not be instantiated + */ + @SuppressWarnings("unchecked") + public static T instantiateClass(String className) throws IllegalArgumentException { + try { + + Class clazz = (Class) Class.forName(className); + + return clazz.getConstructor().newInstance(); + + } catch (Exception e) { + String msg = MessageFormat.format("The class {0} could not be instantiated: ", className); //$NON-NLS-1$ + throw new IllegalArgumentException(msg, e); + } + } + + /** + * Instantiates an object for the given {@link Class} using an empty arguments constructor + * + * @param + * the type of the class to return + * @param clazz + * the {@link Class} from which a new object is to be instantiated using an empty arguments constructor + * + * @return the newly instantiated object from the given {@link Class} + * + * @throws IllegalArgumentException + * if the {@link Class} could not be instantiated + */ + public static T instantiateClass(Class clazz) throws IllegalArgumentException { + try { + + return clazz.getConstructor().newInstance(); + + } catch (Exception e) { + String msg = MessageFormat.format("The class {0} could not be instantiated: ", clazz.getName()); //$NON-NLS-1$ + throw new IllegalArgumentException(msg, e); + } + } + + /** + * Loads the {@link Class} object for the given class name + * + * @param + * the type of {@link Class} to return + * @param className + * the name of the {@link Class} to load and return + * + * @return the {@link Class} object for the given class name + * + * @throws IllegalArgumentException + * if the class could not be instantiated + */ + @SuppressWarnings("unchecked") + public static Class loadClass(String className) throws IllegalArgumentException { + try { + + Class clazz = (Class) Class.forName(className); + + return clazz; + + } catch (Exception e) { + String msg = MessageFormat.format("The class {0} could not be instantiated: ", className); //$NON-NLS-1$ + throw new IllegalArgumentException(msg, e); + } + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java new file mode 100644 index 000000000..20851de57 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.text.MessageFormat; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** + * @author Robert von Burg + */ +public class DomUtil { + + public static DocumentBuilder createDocumentBuilder() { + try { + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + return docBuilder; + } catch (ParserConfigurationException e) { + String msg = "No Xml Parser could be loaded: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, e.getMessage()); + throw new RuntimeException(msg, e); + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java new file mode 100644 index 000000000..a498c20cb --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * @author Robert von Burg + */ +public class ExceptionHelper { + + /** + *

+ * Returns a message for the given {@link Throwable} + *

+ * + *

+ * A {@link NullPointerException} only has null as the message so this methods returns the class name + * in such a case + *

+ * + * @param t + * @return + */ + public static String getExceptionMessage(Throwable t) { + return StringHelper.isEmpty(t.getMessage()) ? t.getClass().getName() : t.getMessage(); + } + + /** + *

+ * Returns a message for the given {@link Throwable} + *

+ * + *

+ * A {@link NullPointerException} only has null as the message so this methods returns the class name + * in such a case + *

+ * + * @param t + * @return + */ + public static String getExceptionMessageWithCauses(Throwable t) { + if (t.getCause() == null) + return getExceptionMessage(t); + + String root = getExceptionMessageWithCauses(t.getCause()); + return getExceptionMessage(t) + "\n" + root; + } + + /** + * Formats the given {@link Throwable}'s stack trace to a string + * + * @param t + * the throwable for which the stack trace is to be formatted to string + * + * @return a string representation of the given {@link Throwable}'s stack trace + */ + public static String formatException(Throwable t) { + StringWriter stringWriter = new StringWriter(); + PrintWriter writer = new PrintWriter(stringWriter); + t.printStackTrace(writer); + return stringWriter.toString(); + } + + /** + * Formats the given {@link Throwable}'s message including causes to a string + * + * @param t + * the throwable for which the messages are to be formatted to a string + * + * @return a string representation of the given {@link Throwable}'s messages including causes + */ + public static String formatExceptionMessage(Throwable t) { + if (t.getCause() == null) + return getExceptionMessage(t); + + String root = formatExceptionMessage(t.getCause()); + return getExceptionMessage(t) + "\ncause:\n" + root; + } + + /** + * Returns the root cause for the given {@link Throwable} + * + * @param throwable + * the {@link Throwable} for which to get the root cause + * + * @return the root cause of the given {@link Throwable} + */ + public static Throwable getRootCause(Throwable throwable) { + Throwable t = throwable; + while (t.getCause() != null) { + t = t.getCause(); + } + + return t; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java new file mode 100644 index 000000000..57cf58bc9 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -0,0 +1,577 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.MessageDigest; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper class for dealing with files + * + * @author Robert von Burg + */ +public class FileHelper { + + private static final int MAX_FILE_SIZE = 50 * 1024 * 1024; + private static final Logger logger = LoggerFactory.getLogger(FileHelper.class); + + /** + * Reads the contents of a file into a byte array. + * + * @param file + * the file to read + * + * @return the contents of a file as a string + */ + public static final byte[] readFile(File file) { + if (file.length() > MAX_FILE_SIZE) + throw new RuntimeException(String.format("Only allowed to read files up to %s. File too large: %s", //$NON-NLS-1$ + humanizeFileSize(MAX_FILE_SIZE), humanizeFileSize(file.length()))); + + byte[] data = new byte[(int) file.length()]; + int pos = 0; + + try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));) { + byte[] bytes = new byte[8192]; + int read; + while ((read = in.read(bytes)) != -1) { + System.arraycopy(bytes, 0, data, pos, read); + pos += read; + } + } catch (FileNotFoundException e) { + throw new RuntimeException("Filed does not exist " + file.getAbsolutePath()); //$NON-NLS-1$ + } catch (IOException e) { + throw new RuntimeException("Could not read file " + file.getAbsolutePath()); //$NON-NLS-1$ + } + + return data; + } + + /** + * Reads the contents of a file into a string. Note, no encoding is checked. It is expected to be UTF-8 + * + * @param file + * the file to read + * + * @return the contents of a file as a string + */ + public static final String readFileToString(File file) { + + try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file));) { + + StringBuilder sb = new StringBuilder(); + + String line; + + while ((line = bufferedReader.readLine()) != null) { + sb.append(line + "\n"); //$NON-NLS-1$ + } + + return sb.toString(); + + } catch (FileNotFoundException e) { + throw new RuntimeException("File does not exist " + file.getAbsolutePath()); //$NON-NLS-1$ + } catch (IOException e) { + throw new RuntimeException("Could not read file " + file.getAbsolutePath()); //$NON-NLS-1$ + } + } + + /** + * Reads the contents of a {@link InputStream} into a string. Note, no encoding is checked. It is expected to be + * UTF-8 + * + * @param stream + * the stream to read + * + * @return the contents of a file as a string + */ + public static final String readStreamToString(InputStream stream) { + + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream));) { + + StringBuilder sb = new StringBuilder(); + + String line; + + while ((line = bufferedReader.readLine()) != null) { + sb.append(line + "\n"); //$NON-NLS-1$ + } + + return sb.toString(); + + } catch (IOException e) { + throw new RuntimeException("Could not read strean " + stream); //$NON-NLS-1$ + } + } + + /** + * Writes the given byte array to the given file + * + * @param bytes + * the data to write to the file + * @param dstFile + * the path to which to write the data + */ + public static final void writeToFile(byte[] bytes, File dstFile) { + + try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dstFile));) { + out.write(bytes); + } catch (FileNotFoundException e) { + throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath()); //$NON-NLS-1$ + } catch (IOException e) { + throw new RuntimeException("Could not write to file " + dstFile.getAbsolutePath()); //$NON-NLS-1$ + } + } + + /** + * Writes the string to dstFile + * + * @param string + * string to write to file + * @param dstFile + * the file to write to + */ + public static final void writeStringToFile(String string, File dstFile) { + + try (BufferedWriter bufferedwriter = new BufferedWriter(new FileWriter(dstFile));) { + bufferedwriter.write(string); + } catch (FileNotFoundException e) { + throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath()); //$NON-NLS-1$ + } catch (IOException e) { + throw new RuntimeException("Could not write to file " + dstFile.getAbsolutePath()); //$NON-NLS-1$ + } + } + + /** + * Deletes files recursively. No question asked, but logging is done in case of problems + * + * @param file + * the file to delete + * @param log + * @return true if all went well, and false if it did not work. The log will contain the problems encountered + */ + public final static boolean deleteFile(File file, boolean log) { + return FileHelper.deleteFiles(new File[] { file }, log); + } + + /** + * Deletes files recursively. No question asked, but logging is done in case of problems + * + * @param files + * the files to delete + * @param log + * @return true if all went well, and false if it did not work. The log will contain the problems encountered + */ + public final static boolean deleteFiles(File[] files, boolean log) { + + boolean worked = true; + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isDirectory()) { + boolean done = FileHelper.deleteFiles(file.listFiles(), log); + if (!done) { + worked = false; + FileHelper.logger.warn("Could not empty the directory: " + file.getAbsolutePath()); //$NON-NLS-1$ + } else { + done = file.delete(); + if (done) { + if (log) + FileHelper.logger.info("Deleted DIR " + file.getAbsolutePath()); //$NON-NLS-1$ + } else { + worked = false; + FileHelper.logger.warn("Could not delete the directory: " + file.getAbsolutePath()); //$NON-NLS-1$ + } + } + } else { + boolean done = file.delete(); + if (done) { + if (log) + FileHelper.logger.info("Deleted FILE " + file.getAbsolutePath()); //$NON-NLS-1$ + } else { + worked = false; + FileHelper.logger.warn(("Could not delete the file: " + file.getAbsolutePath())); //$NON-NLS-1$ + } + } + } + return worked; + } + + /** + *

+ * Copy a given list of {@link File Files}. Recursively copies the files and directories to the destination. + *

+ * + * @param srcFiles + * The source files to copy + * @param dstDirectory + * The destination where to copy the files + * @param checksum + * if true, then a MD5 checksum is made to validate copying + * @return true if and only if the copying succeeded; false otherwise + */ + public final static boolean copy(File[] srcFiles, File dstDirectory, boolean checksum) { + + if (!dstDirectory.isDirectory() || !dstDirectory.canWrite()) { + String msg = "Destination is not a directory or is not writeable: {0}"; //$NON-NLS-1$ + throw new IllegalArgumentException(MessageFormat.format(msg, dstDirectory.getAbsolutePath())); + } + + for (File srcFile : srcFiles) { + + File dstFile = new File(dstDirectory, srcFile.getName()); + if (srcFile.isDirectory()) { + dstFile.mkdir(); + if (!copy(srcFile.listFiles(), dstFile, checksum)) { + String msg = "Failed to copy contents of {0} to {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, srcFile.getAbsolutePath(), dstFile.getAbsolutePath()); + logger.error(msg); + return false; + } + } else { + if (!copy(srcFile, dstFile, checksum)) { + return false; + } + } + } + + return true; + } + + /** + * Copy a {@link File} The renameTo method does not allow action across NFS mounted filesystems this method is the + * workaround + * + * @param fromFile + * The existing File + * @param toFile + * The new File + * @param checksum + * if true, then a MD5 checksum is made to validate copying + * @return true if and only if the renaming succeeded; false otherwise + */ + public final static boolean copy(File fromFile, File toFile, boolean checksum) { + + try (BufferedInputStream inBuffer = new BufferedInputStream(new FileInputStream(fromFile)); + BufferedOutputStream outBuffer = new BufferedOutputStream(new FileOutputStream(toFile));) { + + int theByte = 0; + + while ((theByte = inBuffer.read()) > -1) { + outBuffer.write(theByte); + } + + outBuffer.flush(); + + if (checksum) { + String fromFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(fromFile)); + String toFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(toFile)); + if (!fromFileMD5.equals(toFileMD5)) { + FileHelper.logger.error(MessageFormat.format( + "Copying failed, as MD5 sums are not equal: {0} / {1}", fromFileMD5, toFileMD5)); //$NON-NLS-1$ + toFile.delete(); + + return false; + } + } + + // cleanup if files are not the same length + if (fromFile.length() != toFile.length()) { + String msg = "Copying failed, as new files are not the same length: {0} / {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, fromFile.length(), toFile.length()); + FileHelper.logger.error(msg); + toFile.delete(); + + return false; + } + + } catch (Exception e) { + String msg = MessageFormat.format("Failed to copy path from {0} to + {1} due to:", fromFile, toFile); //$NON-NLS-1$ + FileHelper.logger.error(msg, e); + return false; + } + + return true; + } + + /** + * Move a File The renameTo method does not allow action across NFS mounted filesystems this method is the + * workaround + * + * @param fromFile + * The existing File + * @param toFile + * The new File + * @return true if and only if the renaming succeeded; false otherwise + */ + public final static boolean move(File fromFile, File toFile) { + + if (fromFile.renameTo(toFile)) { + return true; + } + + FileHelper.logger.warn("Simple File.renameTo failed, trying copy/delete..."); //$NON-NLS-1$ + + // delete if copy was successful, otherwise move will fail + if (FileHelper.copy(fromFile, toFile, true)) { + FileHelper.logger.info("Deleting fromFile: " + fromFile.getAbsolutePath()); //$NON-NLS-1$ + return fromFile.delete(); + } + + return false; + } + + /** + * Finds the common parent for the files in the given list + * + * @param files + * the files to find the common parent for + * + * @return the {@link File} representing the common parent, or null if there is none + */ + public static File findCommonParent(List files) { + + // be gentle with bad input data + if (files.size() == 0) + return null; + if (files.size() == 1) + return files.get(0).getParentFile(); + + File commonParent = null; + int commonParentDepth = -1; + + // find the common parent among all the files + for (int i = 0; i < files.size() - 1; i++) { + + // get first file + File file = files.get(i); + + // get list of parents for this file + List parents = new ArrayList<>(); + File parent = file.getParentFile(); + while (parent != null) { + parents.add(parent); + parent = parent.getParentFile(); + } + // reverse + Collections.reverse(parents); + + // and now the same for the next file + File fileNext = files.get(i + 1); + List parentsNext = new ArrayList<>(); + File parentNext = fileNext.getParentFile(); + while (parentNext != null) { + parentsNext.add(parentNext); + parentNext = parentNext.getParentFile(); + } + // reverse + Collections.reverse(parentsNext); + + //logger.info("Files: " + file + " / " + fileNext); + + // now find the common parent + File newCommonParent = null; + int newCommonParentDepth = -1; + for (int j = 0; j < (parents.size()); j++) { + + // don't overflow the size of the next list of parents + if (j >= parentsNext.size()) + break; + + // if we once found a common parent, and our current depth is + // greater than the one for the common parent, then stop as + // there can't be a deeper parent + if (commonParent != null && j > commonParentDepth) + break; + + // get the next parents to compare + File aParent = parents.get(j); + File bParent = parentsNext.get(j); + + //logger.info("Comparing " + aParent + " |||| " + bParent); + + // if they parent are the same, then break, as we won't + // have another match + if (!aParent.equals(bParent)) + break; + + // save the parent and the depth where we found the parent + newCommonParent = aParent; + newCommonParentDepth = j; + } + + // if no common parent was found, then break as there won't be one + if (commonParent == null && newCommonParent == null) + break; + + // if there is no new common parent, then check the next file + if (newCommonParent == null) + continue; + + // store the common parent + commonParent = newCommonParent; + commonParentDepth = newCommonParentDepth; + //logger.info("Temporary common parent: (" + commonParentDepth + ") " + commonParent); + } + + //logger.info("Common parent: " + commonParent); + return commonParent; + } + + /** + * Returns the size of the file in a human readable form. Everything smaller than 1024 bytes is returned as x bytes, + * next is KB, then MB and then GB + * + * @param file + * the file for which the humanized size is to be returned + * + * @return the humanized form of the files size + */ + public final static String humanizeFileSize(File file) { + return FileHelper.humanizeFileSize(file.length()); + } + + /** + * Returns the size of the file in a human readable form. Everything smaller than 1024 bytes is returned as x bytes, + * next is KB, then MB and then GB + * + * @param fileSize + * the size of a file for which the humanized size is to be returned + * + * @return the humanized form of the files size + */ + @SuppressWarnings("nls") + public final static String humanizeFileSize(long fileSize) { + if (fileSize < 1024) + return String.format("%d bytes", fileSize); + + if (fileSize < 1048576) + return String.format("%.1f KB", (fileSize / 1024.0d)); + + if (fileSize < 1073741824) + return String.format("%.1f MB", (fileSize / 1048576.0d)); + + return String.format("%.1f GB", (fileSize / 1073741824.0d)); + } + + /** + * Creates the MD5 hash of the given file, returning the hash as a byte array. Use + * {@link StringHelper#getHexString(byte[])} to create a HEX string of the bytes + * + * @param file + * the file to hash + * + * @return the hash as a byte array + */ + public static byte[] hashFileMd5(File file) { + return FileHelper.hashFile(file, "MD5"); //$NON-NLS-1$ + } + + /** + * Creates the SHA1 hash of the given file, returning the hash as a byte array. Use + * {@link StringHelper#getHexString(byte[])} to create a HEX string of the bytes + * + * @param file + * the file to hash + * + * @return the hash as a byte array + */ + public static byte[] hashFileSha1(File file) { + return FileHelper.hashFile(file, "SHA-1"); //$NON-NLS-1$ + } + + /** + * Creates the SHA256 hash of the given file, returning the hash as a byte array. Use + * {@link StringHelper#getHexString(byte[])} to create a HEX string of the bytes + * + * @param file + * the file to hash + * + * @return the hash as a byte array + */ + public static byte[] hashFileSha256(File file) { + return FileHelper.hashFile(file, "SHA-256"); //$NON-NLS-1$ + } + + /** + * Creates the hash of the given file with the given algorithm, returning the hash as a byte array. Use + * {@link StringHelper#getHexString(byte[])} to create a HEX string of the bytes + * + * @param file + * the file to hash + * @param algorithm + * the hashing algorithm to use + * + * @return the hash as a byte array + */ + public static byte[] hashFile(File file, String algorithm) { + try (InputStream fis = new FileInputStream(file);) { + + byte[] buffer = new byte[1024]; + MessageDigest complete = MessageDigest.getInstance(algorithm); + int numRead; + do { + numRead = fis.read(buffer); + if (numRead > 0) { + complete.update(buffer, 0, numRead); + } + } while (numRead != -1); + + return complete.digest(); + } catch (Exception e) { + throw new RuntimeException("Something went wrong while hashing file: " + file.getAbsolutePath()); //$NON-NLS-1$ + } + } + + /** + * Helper method to append bytes to a specified file. The file is created if it does not exist otherwise the bytes + * are simply appended + * + * @param dstFile + * the file to append to + * @param bytes + * the bytes to append + */ + public static void appendFilePart(File dstFile, byte[] bytes) { + + try (FileOutputStream outputStream = new FileOutputStream(dstFile, true);) { + + outputStream.write(bytes); + outputStream.flush(); + + } catch (IOException e) { + throw new RuntimeException("Could not create and append the bytes to the file " + dstFile.getAbsolutePath()); //$NON-NLS-1$ + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java new file mode 100644 index 000000000..6c868b889 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java @@ -0,0 +1,125 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.helper; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * A helper class that contains mathematical computations that can be used throughout. + * + * @author Martin Smock + * @author Michael Gatto + */ +public class MathHelper { + + public static final double PRECISION = 1.0E08; + public static final int PRECISION_DIGITS = 3; + + /** + * Check if the two values are equal with respect to the precision + * + * @param firstValue + * the first value to compare + * @param secondValue + * the second value to compare to + * @return boolean True, if the two values are equal under the set precision. Fales, otherwise. + */ + public static boolean isEqualPrecision(double firstValue, double secondValue) { + + return (java.lang.Math.abs(firstValue - secondValue) < (1.0d / PRECISION)); + } + + /** + * Comparison between the two values. Given the precision, this function determines if the given value is smaller + * than the given bound. + *

+ * Note: this implementation tests if the value < bound, and if this is not so, checks if the values are equal under + * the precision. Thus, it's efficient whenever the value is expected to be smaller than the bound. + * + * @param value + * The value to check + * @param bound + * The bound given + * @return true, if value < bound under the given precision. False, otherwise. + */ + public static boolean isSmallerEqualPrecision(double value, double bound) { + if (value < bound) + return true; + return isEqualPrecision(value, bound); + } + + /** + * Comparison between two values. Given the precision, this function determines if the given value is greater than + * the given bound. + *

+ * Note: This implementation tests if value > bound and, if it is so, if equality does NOT hold. Thus, it is + * efficient whenever the value is not expected to be greater than the bound. + * + * @param value + * The value to check + * @param bound + * The bound given. + * @return true, if value > bound and the values do not test equal under precision. False, otherwise. + */ + public static boolean isGreaterPrecision(double value, double bound) { + return (value > bound) && !isEqualPrecision(value, bound); + } + + /** + *

+ * Rounds the given double value to the number of decimals + *

+ * + *

+ * Warning: Do not use the returned value for further calculations. Always finish calculates and use one of + * the following methods: + *

    + *
  • {@link #isEqualPrecision(double, double)},
  • + *
  • {@link #isGreaterPrecision(double, double)} or
  • + *
  • {@link #isSmallerEqualPrecision(double, double)}
  • + *
+ * to test on equality or greater than/ smaller than + *

+ * + * @param value + * the double value to round + * @param decimals + * number of decimals + * + * @return the rounded number + */ + public static double toPrecision(double value, int decimals) { + if (value == 0.0) + return 0.0; + if (Double.isNaN(value)) + return Double.NaN; + if (value == Double.NEGATIVE_INFINITY) + return Double.NEGATIVE_INFINITY; + if (value == Double.POSITIVE_INFINITY) + return Double.POSITIVE_INFINITY; + return new BigDecimal(value).setScale(decimals, RoundingMode.HALF_EVEN).doubleValue(); + } + + /** + * Returns the value with the precision where precision is set to {@link #PRECISION_DIGITS} + * + * @see #toPrecision(double, int) + */ + public static double toPrecision(double value) { + return toPrecision(value, PRECISION_DIGITS); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java new file mode 100644 index 000000000..2d84b0624 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -0,0 +1,172 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + */ +public class ProcessHelper { + + private static final Logger logger = LoggerFactory.getLogger(ProcessHelper.class); + + public static ProcessResult runCommand(String... commandAndArgs) { + return runCommand(null, commandAndArgs); + } + + public static ProcessResult runCommand(File workingDirectory, String... commandAndArgs) { + return runCommand(30, TimeUnit.SECONDS, workingDirectory, commandAndArgs); + } + + public static ProcessResult runCommand(long timeout, TimeUnit unit, String... commandAndArgs) { + return runCommand(timeout, unit, null, commandAndArgs); + } + + public static ProcessResult runCommand(long timeout, TimeUnit unit, File workingDirectory, + String... commandAndArgs) { + + if (workingDirectory != null && !workingDirectory.isDirectory()) { + String msg = "Working directory does not exist or is not a directory at {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, workingDirectory.getAbsolutePath()); + throw new RuntimeException(msg); + } + if (commandAndArgs == null || commandAndArgs.length == 0) + throw new RuntimeException("No command passed!"); //$NON-NLS-1$ + + final StringBuffer sb = new StringBuffer(); + sb.append("=====================================\n"); //$NON-NLS-1$ + try { + + ProcessBuilder pb = new ProcessBuilder(commandAndArgs); + pb.environment(); + pb.directory(workingDirectory); + + long start = System.nanoTime(); + logger.info(MessageFormat.format("Starting command (Timeout {0} {1}) {2}", timeout, unit.name(), + Arrays.stream(commandAndArgs).collect(Collectors.joining(" ")))); + final Process process = pb.start(); + int[] returnValue = new int[1]; + + try (BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); + BufferedReader inputStream = new BufferedReader(new InputStreamReader(process.getInputStream()));) { + + Thread errorIn = new Thread(() -> readStream(sb, "[ERROR] ", errorStream), "errorIn"); + errorIn.start(); + + Thread infoIn = new Thread(() -> readStream(sb, "[INFO] ", inputStream), "infoIn"); + infoIn.start(); + + boolean ok = process.waitFor(timeout, unit); + if (!ok) + logger.error("Command failed to end before timeout or failed to execute."); + + if (!process.isAlive()) { + returnValue[0] = process.exitValue(); + } else { + logger.error("Forcibly destroying as still running..."); + process.destroyForcibly(); + process.waitFor(5, TimeUnit.SECONDS); + returnValue[0] = -1; + } + + errorIn.join(100l); + infoIn.join(100l); + sb.append("=====================================\n"); //$NON-NLS-1$ + } + + logger.info("Command ended after " + StringHelper.formatNanoDuration(System.nanoTime() - start)); + return new ProcessResult(returnValue[0], sb.toString(), null); + + } catch (IOException e) { + throw new RuntimeException("Failed to perform command: " + e.getLocalizedMessage(), e); //$NON-NLS-1$ + } catch (InterruptedException e) { + logger.error("Interrupted!"); //$NON-NLS-1$ + sb.append("[FATAL] Interrupted"); //$NON-NLS-1$ + return new ProcessResult(-1, sb.toString(), e); + } + } + + public static class ProcessResult { + public final int returnValue; + public final String processOutput; + public final Throwable throwable; + + public ProcessResult(int returnValue, String processOutput, Throwable t) { + this.returnValue = returnValue; + this.processOutput = processOutput; + this.throwable = t; + } + } + + static void readStream(StringBuffer sb, String prefix, BufferedReader bufferedReader) { + String line; + try { + while ((line = bufferedReader.readLine()) != null) { + sb.append(prefix + line + StringHelper.NEW_LINE); + } + } catch (IOException e) { + String msg = "Faild to read from {0} stream: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, prefix, e.getMessage()); + sb.append("[FATAL] "); //$NON-NLS-1$ + sb.append(msg); + sb.append(StringHelper.NEW_LINE); + } + } + + public static void openFile(File pdfPath) { + + ProcessResult processResult; + if (SystemHelper.isLinux()) { + processResult = runCommand("xdg-open " + pdfPath.getAbsolutePath()); //$NON-NLS-1$ + } else if (SystemHelper.isMacOS()) { + processResult = runCommand("open " + pdfPath.getAbsolutePath()); //$NON-NLS-1$ + } else if (SystemHelper.isWindows()) { + // remove the first char (/) from the report path (/D:/temp.....) + String pdfFile = pdfPath.getAbsolutePath(); + if (pdfFile.charAt(0) == '/') + pdfFile = pdfFile.substring(1); + processResult = runCommand("rundll32 url.dll,FileProtocolHandler " + pdfFile); //$NON-NLS-1$ + } else { + String msg = MessageFormat.format("Unexpected OS: {0}", SystemHelper.osName); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + logProcessResult(processResult); + } + + public static void logProcessResult(ProcessResult processResult) { + if (processResult.returnValue == 0) { + logger.info("Process executed successfully"); //$NON-NLS-1$ + } else if (processResult.returnValue == -1) { + logger.error("Process execution failed:\n" + processResult.processOutput); //$NON-NLS-1$ + logger.error(processResult.throwable.getMessage(), processResult.throwable); + } else { + String msg = "Process execution was not successful with return value:{0}\n{1}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, processResult.returnValue, processResult.processOutput)); + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java new file mode 100644 index 000000000..f09f66e6c --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java @@ -0,0 +1,187 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.text.MessageFormat; +import java.util.Properties; + +/** + * @author Robert von Burg + */ +public class PropertiesHelper { + + /** + * Returns the property with the given key from the given {@link Properties}. If def is null, and the property is + * not set, then a {@link RuntimeException} is thrown + * + * @param properties + * the {@link Properties} from which to retrieve the property + * @param context + * The context should be the name of the caller, so that stack trace gives enough detail as to which + * class expected the configuration property if no property was found + * @param key + * the key of the property to return + * @param def + * the default value, if null, then an exception will be thrown if the property is not set + * + * @return the property under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static String getProperty(Properties properties, String context, String key, String def) + throws RuntimeException { + String property = properties.getProperty(key, def); + if (property == null) { + String msg = "[{0}] Property {1} is not set, and no default was given!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, context, key); + throw new RuntimeException(msg); + } + + return property; + } + + /** + * Returns the {@link System#getProperty(String)} with the given key. If def is null, and the property is not set, + * then a {@link RuntimeException} is thrown + * + * @param context + * The context should be the name of the caller, so that stack trace gives enough detail as to which + * class expected the configuration property if no property was found + * @param key + * the key of the property to return + * @param def + * the default value, if null, then an exception will be thrown if the property is not set + * + * @return the property under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static String getProperty(String context, String key, String def) throws RuntimeException { + return getProperty(System.getProperties(), context, key, def); + } + + /** + * Delegates to {@link #getProperty(Properties, String, String, String)} but returns the value as a {@link Boolean} + * where {@link Boolean#valueOf(String)} defines the actual value + * + * @param properties + * the {@link Properties} from which to retrieve the property + * @return the property as a {@link Boolean} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Boolean getPropertyBool(Properties properties, String context, String key, Boolean def) + throws RuntimeException { + return Boolean.valueOf(getProperty(properties, context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(String, String, String)} but returns the value as a {@link Boolean} where + * {@link Boolean#valueOf(String)} defines the actual value + * + * @return the property as a {@link Boolean} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Boolean getPropertyBool(String context, String key, Boolean def) throws RuntimeException { + return Boolean.valueOf(getProperty(context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(Properties, String, String, String)} but returns the value as an {@link Integer} + * where {@link Integer#valueOf(String)} defines the actual value + * + * @return the property as a {@link Integer} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Integer getPropertyInt(Properties properties, String context, String key, Integer def) + throws RuntimeException { + return Integer.valueOf(getProperty(properties, context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(String, String, String)} but returns the value as an {@link Integer} where + * {@link Integer#valueOf(String)} defines the actual value + * + * @return the property as a {@link Integer} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Integer getPropertyInt(String context, String key, Integer def) throws RuntimeException { + return Integer.valueOf(getProperty(context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(Properties, String, String, String)} but returns the value as an {@link Double} + * where {@link Double#valueOf(String)} defines the actual value + * + * @return the property as a {@link Double} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Double getPropertyDouble(Properties properties, String context, String key, Double def) + throws RuntimeException { + return Double.valueOf(getProperty(properties, context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(String, String, String)} but returns the value as an {@link Double} where + * {@link Double#valueOf(String)} defines the actual value + * + * @return the property as a {@link Double} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Double getPropertyDouble(String context, String key, Double def) throws RuntimeException { + return Double.valueOf(getProperty(context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(Properties, String, String, String)} but returns the value as an {@link Long} + * where {@link Long#valueOf(String)} defines the actual value + * + * @return the property as a {@link Long} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Long getPropertyLong(Properties properties, String context, String key, Long def) + throws RuntimeException { + return Long.valueOf(getProperty(properties, context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(String, String, String)} but returns the value as an {@link Long} where + * {@link Long#valueOf(String)} defines the actual value + * + * @return the property as a {@link Long} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Long getPropertyLong(String context, String key, Long def) throws RuntimeException { + return Long.valueOf(getProperty(context, key, def == null ? null : def.toString())); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java new file mode 100644 index 000000000..a7a3fcc94 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -0,0 +1,717 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.MessageFormat; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A helper class to perform different actions on {@link String}s + * + * @author Robert von Burg + */ +public class StringHelper { + + public static final String NEW_LINE = "\n"; //$NON-NLS-1$ + public static final String EMPTY = ""; //$NON-NLS-1$ + public static final String SPACE = " "; //$NON-NLS-1$ + public static final String NULL = "null"; //$NON-NLS-1$ + public static final String DASH = "-"; //$NON-NLS-1$ + public static final String UNDERLINE = "_"; //$NON-NLS-1$ + public static final String COMMA = ","; //$NON-NLS-1$ + public static final String DOT = "."; //$NON-NLS-1$ + public static final String SEMICOLON = ";"; //$NON-NLS-1$ + public static final String COLON = ":"; //$NON-NLS-1$ + + private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); + + /** + * the semi-unique id which is incremented on every {@link #getUniqueId()}-method call + */ + private static long uniqueId = System.currentTimeMillis() - 1119953500000l; + + /** + * Hex char table for fast calculating of hex values + */ + private static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', + (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', + (byte) 'e', (byte) 'f' }; + + /** + * Converts each byte of the given byte array to a HEX value and returns the concatenation of these values + * + * @param raw + * the bytes to convert to String using numbers in hexadecimal + * + * @return the encoded string + * + * @throws RuntimeException + */ + public static String getHexString(byte[] raw) throws RuntimeException { + try { + byte[] hex = new byte[2 * raw.length]; + int index = 0; + + for (byte b : raw) { + int v = b & 0xFF; + hex[index++] = HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = HEX_CHAR_TABLE[v & 0xF]; + } + + return new String(hex, "ASCII"); //$NON-NLS-1$ + + } catch (UnsupportedEncodingException e) { + String msg = MessageFormat.format("Something went wrong while converting to HEX: {0}", e.getMessage()); //$NON-NLS-1$ + throw new RuntimeException(msg, e); + } + } + + /** + * Returns a byte array of a given string by converting each character of the string to a number base 16 + * + * @param encoded + * the string to convert to a byt string + * + * @return the encoded byte stream + */ + public static byte[] fromHexString(String encoded) { + if ((encoded.length() % 2) != 0) + throw new IllegalArgumentException("Input string must contain an even number of characters."); //$NON-NLS-1$ + + final byte result[] = new byte[encoded.length() / 2]; + final char enc[] = encoded.toCharArray(); + for (int i = 0; i < enc.length; i += 2) { + StringBuilder curr = new StringBuilder(2); + curr.append(enc[i]).append(enc[i + 1]); + result[i / 2] = (byte) Integer.parseInt(curr.toString(), 16); + } + + return result; + } + + /** + * Generates the MD5 Hash of a string and converts it to a HEX string + * + * @param string + * the string to hash + * + * @return the hash or null, if an exception was thrown + */ + public static String hashMd5AsHex(String string) { + return getHexString(hashMd5(string.getBytes())); + } + + /** + * Generates the MD5 Hash of a string. Use {@link StringHelper#getHexString(byte[])} to convert the byte array to a + * Hex String which is printable + * + * @param string + * the string to hash + * + * @return the hash or null, if an exception was thrown + */ + public static byte[] hashMd5(String string) { + return hashMd5(string.getBytes()); + } + + /** + * Generates the MD5 Hash of a byte array Use {@link StringHelper#getHexString(byte[])} to convert the byte array to + * a Hex String which is printable + * + * @param bytes + * the bytes to hash + * + * @return the hash or null, if an exception was thrown + */ + public static byte[] hashMd5(byte[] bytes) { + return hash("MD5", bytes); //$NON-NLS-1$ + } + + /** + * Generates the SHA1 Hash of a string and converts it to a HEX String + * + * @param string + * the string to hash + * + * @return the hash or null, if an exception was thrown + */ + public static String hashSha1AsHex(String string) { + return getHexString(hashSha1(string.getBytes())); + } + + /** + * Generates the SHA1 Hash of a string Use {@link StringHelper#getHexString(byte[])} to convert the byte array to a + * Hex String which is printable + * + * @param string + * the string to hash + * + * @return the hash or null, if an exception was thrown + */ + public static byte[] hashSha1(String string) { + return hashSha1(string.getBytes()); + } + + /** + * Generates the SHA1 Hash of a byte array Use {@link StringHelper#getHexString(byte[])} to convert the byte array + * to a Hex String which is printable + * + * @param bytes + * the bytes to hash + * + * @return the hash or null, if an exception was thrown + */ + public static byte[] hashSha1(byte[] bytes) { + return hash("SHA-1", bytes); //$NON-NLS-1$ + } + + /** + * Generates the SHA-256 Hash of a string and converts it to a HEX String + * + * @param string + * the string to hash + * + * @return the hash or null, if an exception was thrown + */ + public static String hashSha256AsHex(String string) { + return getHexString(hashSha256(string.getBytes())); + } + + /** + * Generates the SHA-256 Hash of a string Use {@link StringHelper#getHexString(byte[])} to convert the byte array to + * a Hex String which is printable + * + * @param string + * the string to hash + * + * @return the hash or null, if an exception was thrown + */ + public static byte[] hashSha256(String string) { + return hashSha256(string.getBytes()); + } + + /** + * Generates the SHA1 Hash of a byte array Use {@link StringHelper#getHexString(byte[])} to convert the byte array + * to a Hex String which is printable + * + * @param bytes + * the bytes to hash + * + * @return the hash or null, if an exception was thrown + */ + public static byte[] hashSha256(byte[] bytes) { + return hash("SHA-256", bytes); //$NON-NLS-1$ + } + + /** + * Returns the hash of an algorithm + * + * @param algorithm + * the algorithm to use + * @param string + * the string to hash + * + * @return the hash or null, if an exception was thrown + */ + public static String hashAsHex(String algorithm, String string) { + return getHexString(hash(algorithm, string)); + } + + /** + * Returns the hash of an algorithm + * + * @param algorithm + * the algorithm to use + * @param string + * the string to hash + * + * @return the hash or null, if an exception was thrown + */ + public static byte[] hash(String algorithm, String string) { + try { + + MessageDigest digest = MessageDigest.getInstance(algorithm); + byte[] hashArray = digest.digest(string.getBytes()); + + return hashArray; + + } catch (NoSuchAlgorithmException e) { + String msg = MessageFormat.format("Algorithm {0} does not exist!", algorithm); //$NON-NLS-1$ + throw new RuntimeException(msg, e); + } + } + + /** + * Returns the hash of an algorithm + * + * @param algorithm + * the algorithm to use + * @param bytes + * the bytes to hash + * + * @return the hash or null, if an exception was thrown + */ + public static String hashAsHex(String algorithm, byte[] bytes) { + return getHexString(hash(algorithm, bytes)); + } + + /** + * Returns the hash of an algorithm + * + * @param algorithm + * the algorithm to use + * @param bytes + * the bytes to hash + * + * @return the hash or null, if an exception was thrown + */ + public static byte[] hash(String algorithm, byte[] bytes) { + try { + + MessageDigest digest = MessageDigest.getInstance(algorithm); + byte[] hashArray = digest.digest(bytes); + + return hashArray; + + } catch (NoSuchAlgorithmException e) { + String msg = MessageFormat.format("Algorithm {0} does not exist!", algorithm); //$NON-NLS-1$ + throw new RuntimeException(msg, e); + } + } + + /** + * Normalizes the length of a String. Does not shorten it when it is too long, but lengthens it, depending on the + * options set: adding the char at the beginning or appending it at the end + * + * @param value + * string to normalize + * @param length + * length string must have + * @param beginning + * add at beginning of value + * @param c + * char to append when appending + * @return the new string + */ + public static String normalizeLength(String value, int length, boolean beginning, char c) { + return normalizeLength(value, length, beginning, false, c); + } + + /** + * Normalizes the length of a String. Shortens it when it is too long, giving out a logger warning, or lengthens it, + * depending on the options set: appending the char at the beginning or the end + * + * @param value + * string to normalize + * @param length + * length string must have + * @param beginning + * append at beginning of value + * @param shorten + * allow shortening of value + * @param c + * char to append when appending + * @return the new string + */ + public static String normalizeLength(String value, int length, boolean beginning, boolean shorten, char c) { + + if (value.length() == length) + return value; + + if (value.length() < length) { + + String tmp = value; + while (tmp.length() != length) { + if (beginning) { + tmp = c + tmp; + } else { + tmp = tmp + c; + } + } + + return tmp; + + } else if (shorten) { + + logger.warn(MessageFormat.format("Shortening length of value: {0}", value)); //$NON-NLS-1$ + logger.warn(MessageFormat.format("Length is: {0} max: {1}", value.length(), length)); //$NON-NLS-1$ + + return value.substring(0, length); + } + + return value; + } + + /** + * Calls {@link #replacePropertiesIn(Properties, String)}, with {@link System#getProperties()} as input + * + * @return a new string with all defined system properties replaced or if an error occurred the original value is + * returned + */ + public static String replaceSystemPropertiesIn(String value) { + return replacePropertiesIn(System.getProperties(), value); + } + + /** + * Traverses the given string searching for occurrences of ${...} sequences. Theses sequences are replaced with a + * {@link Properties#getProperty(String)} value if such a value exists in the properties map. If the value of the + * sequence is not in the properties, then the sequence is not replaced + * + * @param properties + * the {@link Properties} in which to get the value + * @param value + * the value in which to replace any system properties + * + * @return a new string with all defined properties replaced or if an error occurred the original value is returned + */ + public static String replacePropertiesIn(Properties properties, String value) { + + return replacePropertiesIn(properties, '$', value); + } + + /** + * Traverses the given string searching for occurrences of prefix{...} sequences. Theses sequences are + * replaced with a {@link Properties#getProperty(String)} value if such a value exists in the properties map. If the + * value of the sequence is not in the properties, then the sequence is not replaced + * + * @param properties + * the {@link Properties} in which to get the value + * @param prefix + * the prefix to use, for instance use $ to replace occurrences of ${...} + * @param value + * the value in which to replace any system properties + * + * @return a new string with all defined properties replaced or if an error occurred the original value is returned + */ + public static String replacePropertiesIn(Properties properties, char prefix, String value) { + + String prefixS = String.valueOf(prefix); + + // get a copy of the value + String tmpValue = value; + + // get first occurrence of $ character + int pos = -1; + int stop = 0; + + // loop on $ character positions + while ((pos = tmpValue.indexOf(prefix, pos + 1)) != -1) { + + // if pos+1 is not { character then continue + if (tmpValue.charAt(pos + 1) != '{') { + continue; + } + + // find end of sequence with } character + stop = tmpValue.indexOf('}', pos + 1); + + // if no stop found, then break as another sequence should be able to start + if (stop == -1) { + logger.error(MessageFormat.format("Sequence starts at offset {0} but does not end!", pos)); //$NON-NLS-1$ + tmpValue = value; + break; + } + + // get sequence enclosed by pos and stop + String sequence = tmpValue.substring(pos + 2, stop); + + // make sure sequence doesn't contain $ { } characters + if (sequence.contains(prefixS) || sequence.contains("{") || sequence.contains("}")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + String msg = "Enclosed sequence in offsets {0} - {1} contains one of the illegal chars: {2} { }: {3}"; + msg = MessageFormat.format(msg, pos, stop, prefixS, sequence); + logger.error(msg); + tmpValue = value; + break; + } + + // sequence is good, so see if we have a property for it + String property = properties.getProperty(sequence, StringHelper.EMPTY); + + // if no property exists, then log and continue + if (property.isEmpty()) { + // logger.warn("No system property found for sequence " + sequence); + continue; + } + + // property exists, so replace in value + tmpValue = tmpValue.replace(prefixS + "{" + sequence + "}", property); //$NON-NLS-1$ //$NON-NLS-2$ + } + + return tmpValue; + } + + /** + * Calls {@link #replaceProperties(Properties, Properties)} with null as the second argument. This allows for + * replacing all properties with itself + * + * @param properties + * the properties in which the values must have any ${...} replaced by values of the respective key + */ + public static void replaceProperties(Properties properties) { + replaceProperties(properties, null); + } + + /** + * Checks every value in the {@link Properties} and then then replaces any ${...} variables with keys in this + * {@link Properties} value using {@link StringHelper#replacePropertiesIn(Properties, String)} + * + * @param properties + * the properties in which the values must have any ${...} replaced by values of the respective key + * @param altProperties + * if properties does not contain the ${...} key, then try these alternative properties + */ + public static void replaceProperties(Properties properties, Properties altProperties) { + + for (Object keyObj : properties.keySet()) { + String key = (String) keyObj; + String property = properties.getProperty(key); + String newProperty = replacePropertiesIn(properties, property); + + // try first properties + if (!property.equals(newProperty)) { + // logger.info("Key " + key + " has replaced property " + property + " with new value " + newProperty); + properties.put(key, newProperty); + } else if (altProperties != null) { + + // try alternative properties + newProperty = replacePropertiesIn(altProperties, property); + if (!property.equals(newProperty)) { + // logger.info("Key " + key + " has replaced property " + property + " from alternative properties with new value " + newProperty); + properties.put(key, newProperty); + } + } + } + } + + /** + * This is a helper method with which it is possible to print the location in the two given strings where they start + * to differ. The length of string returned is currently 40 characters, or less if either of the given strings are + * shorter. The format of the string is 3 lines. The first line has information about where in the strings the + * difference occurs, and the second and third lines contain contexts + * + * @param s1 + * the first string + * @param s2 + * the second string + * + * @return the string from which the strings differ with a length of 40 characters within the original strings + */ + public static String printUnequalContext(String s1, String s2) { + + byte[] bytes1 = s1.getBytes(); + byte[] bytes2 = s2.getBytes(); + int i = 0; + for (; i < bytes1.length; i++) { + if (i > bytes2.length) + break; + + if (bytes1[i] != bytes2[i]) + break; + } + + int maxContext = 40; + int start = Math.max(0, (i - maxContext)); + int end = Math.min(i + maxContext, (Math.min(bytes1.length, bytes2.length))); + + StringBuilder sb = new StringBuilder(); + sb.append("Strings are not equal! Start of inequality is at " + i); //$NON-NLS-1$ + sb.append(". Showing " + maxContext); //$NON-NLS-1$ + sb.append(" extra characters and start and end:\n"); //$NON-NLS-1$ + sb.append("context s1: "); //$NON-NLS-1$ + sb.append(s1.substring(start, end)); + sb.append("\n"); //$NON-NLS-1$ + sb.append("context s2: "); //$NON-NLS-1$ + sb.append(s2.substring(start, end)); + sb.append("\n"); //$NON-NLS-1$ + + return sb.toString(); + } + + /** + * Formats the given number of milliseconds to a time like #h/m/s/ms/us/ns + * + * @param millis + * the number of milliseconds + * + * @return format the given number of milliseconds to a time like #h/m/s/ms/us/ns + */ + public static String formatMillisecondsDuration(final long millis) { + return formatNanoDuration(millis * 1000000L); + } + + /** + * Formats the given number of nanoseconds to a time like #h/m/s/ms/us/ns + * + * @param nanos + * the number of nanoseconds + * + * @return format the given number of nanoseconds to a time like #h/m/s/ms/us/ns + */ + public static String formatNanoDuration(final long nanos) { + if (nanos >= 3600000000000L) { + return String.format("%.0fh", (nanos / 3600000000000.0D)); //$NON-NLS-1$ + } else if (nanos >= 60000000000L) { + return String.format("%.0fm", (nanos / 60000000000.0D)); //$NON-NLS-1$ + } else if (nanos >= 1000000000L) { + return String.format("%.0fs", (nanos / 1000000000.0D)); //$NON-NLS-1$ + } else if (nanos >= 1000000L) { + return String.format("%.0fms", (nanos / 1000000.0D)); //$NON-NLS-1$ + } else if (nanos >= 1000L) { + return String.format("%.0fus", (nanos / 1000.0D)); //$NON-NLS-1$ + } else { + return nanos + "ns"; //$NON-NLS-1$ + } + } + + /** + * @see ExceptionHelper#formatException(Throwable) + */ + public static String getExceptionMessage(Throwable t) { + return ExceptionHelper.getExceptionMessage(t); + } + + /** + * @see ExceptionHelper#formatException(Throwable) + */ + public static String formatException(Throwable t) { + return ExceptionHelper.formatException(t); + } + + /** + * @see ExceptionHelper#formatExceptionMessage(Throwable) + */ + public static String formatExceptionMessage(Throwable t) { + return ExceptionHelper.formatExceptionMessage(t); + } + + /** + * Simply returns true if the value is null, or empty + * + * @param value + * the value to check + * + * @return true if the value is null, or empty + */ + public static boolean isEmpty(String value) { + return value == null || value.isEmpty(); + } + + /** + * Simply returns true if the value is neither null nor empty + * + * @param value + * the value to check + * + * @return true if the value is neither null nor empty + */ + public static boolean isNotEmpty(String value) { + return value != null && !value.isEmpty(); + } + + /** + *

+ * Parses the given string value to a boolean. This extends the default {@link Boolean#parseBoolean(String)} as it + * throws an exception if the string value is not equal to "true" or "false" being case insensitive. + *

+ * + *

+ * This additional restriction is important where false should really be caught, not any random vaue for false + *

+ * + * @param value + * the value to check + * + * @return true or false, depending on the string value + * + * @throws RuntimeException + * if the value is empty, or not equal to the case insensitive value "true" or "false" + */ + public static boolean parseBoolean(String value) throws RuntimeException { + if (isEmpty(value)) + throw new RuntimeException("Value to parse to boolean is empty! Expected case insensitive true or false"); //$NON-NLS-1$ + String tmp = value.toLowerCase(); + if (tmp.equals(Boolean.TRUE.toString())) { + return true; + } else if (tmp.equals(Boolean.FALSE.toString())) { + return false; + } else { + String msg = "Value {0} can not be parsed to boolean! Expected case insensitive true or false"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, value); + throw new RuntimeException(msg); + } + } + + public static String commaSeparated(String... values) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < values.length; i++) { + sb.append(values[i]); + if (i < values.length - 1) + sb.append(", "); //$NON-NLS-1$ + } + return sb.toString(); + } + + public static String[] splitCommaSeparated(String values) { + String[] split = values.split(","); //$NON-NLS-1$ + for (int i = 0; i < split.length; i++) { + split[i] = split[i].trim(); + } + return split; + } + + /** + * If the value parameter is empty, then a {@link #DASH} is returned, otherwise the value is returned + * + * @param value + * + * @return the non-empty value, or a {@link #DASH} + */ + public static String valueOrDash(String value) { + if (isNotEmpty(value)) + return value; + return DASH; + } + + /** + * Return a pseudo unique id which is incremented on each call. The id is initialized from the current time + * + * @return a pseudo unique id + */ + public static synchronized String getUniqueId() { + return Long.toString(getUniqueIdLong()); + } + + /** + * Return a pseudo unique id which is incremented on each call. The id is initialized from the current time + * + * @return a pseudo unique id + */ + public static synchronized long getUniqueIdLong() { + + if (uniqueId == Long.MAX_VALUE - 1) { + uniqueId = 0; + } + + uniqueId += 1; + return uniqueId; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java new file mode 100644 index 000000000..f7f97d8f0 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +/** + * A helper class for {@link System} methods + * + * @author Robert von Burg + */ +public class SystemHelper { + + private static final SystemHelper instance; + + static { + instance = new SystemHelper(); + } + + public static SystemHelper getInstance() { + return SystemHelper.instance; + } + + public static final String osName = System.getProperty("os.name"); //$NON-NLS-1$ + public static final String osArch = System.getProperty("os.arch"); //$NON-NLS-1$ + public static final String osVersion = System.getProperty("os.version"); //$NON-NLS-1$ + public static final String javaVendor = System.getProperty("java.vendor"); //$NON-NLS-1$ + public static final String javaVersion = System.getProperty("java.version"); //$NON-NLS-1$ + + /** + * private constructor + */ + private SystemHelper() { + // + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return SystemHelper.asString(); + } + + /** + * @see java.lang.Object#toString() + */ + public static String asString() { + StringBuilder sb = new StringBuilder(); + sb.append(SystemHelper.osName); + sb.append(StringHelper.SPACE); + sb.append(SystemHelper.osArch); + sb.append(StringHelper.SPACE); + sb.append(SystemHelper.osVersion); + sb.append(StringHelper.SPACE); + sb.append("on Java "); //$NON-NLS-1$ + sb.append(SystemHelper.javaVendor); + sb.append(" version "); //$NON-NLS-1$ + sb.append(SystemHelper.javaVersion); + return sb.toString(); + } + + public static String getUserDir() { + return System.getProperty("user.dir"); //$NON-NLS-1$ + } + + public static boolean isMacOS() { + return SystemHelper.osName.startsWith("Mac"); //$NON-NLS-1$ + } + + public static boolean isWindows() { + return SystemHelper.osName.startsWith("Win"); //$NON-NLS-1$ + } + + public static boolean isLinux() { + return SystemHelper.osName.startsWith("Lin"); //$NON-NLS-1$ + } + + public static boolean is32bit() { + return SystemHelper.osArch.equals("x86") || SystemHelper.osArch.equals("i386") //$NON-NLS-1$ //$NON-NLS-2$ + || SystemHelper.osArch.equals("i686"); //$NON-NLS-1$ + } + + public static boolean is64bit() { + return SystemHelper.osArch.equals("x86_64") || SystemHelper.osArch.equals("amd64"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public static String getMaxMemory() { + return FileHelper.humanizeFileSize(Runtime.getRuntime().maxMemory()); + } + + public static String getUsedMemory() { + return FileHelper.humanizeFileSize(Runtime.getRuntime().totalMemory()); + } + + public static String getFreeMemory() { + return FileHelper.humanizeFileSize(Runtime.getRuntime().freeMemory()); + } + + public static String getMemorySummary() { + StringBuilder sb = new StringBuilder(); + sb.append("Memory available "); //$NON-NLS-1$ + sb.append(SystemHelper.getMaxMemory()); + sb.append(" / Used: "); //$NON-NLS-1$ + sb.append(SystemHelper.getUsedMemory()); + sb.append(" / Free:"); //$NON-NLS-1$ + sb.append(SystemHelper.getFreeMemory()); + return sb.toString(); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java new file mode 100644 index 000000000..175df4922 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java @@ -0,0 +1,264 @@ +package ch.eitchnet.utils.helper; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.KeyStore; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.TrustedCertificateEntry; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.crypto.dsig.DigestMethod; +import javax.xml.crypto.dsig.Reference; +import javax.xml.crypto.dsig.SignatureMethod; +import javax.xml.crypto.dsig.SignedInfo; +import javax.xml.crypto.dsig.Transform; +import javax.xml.crypto.dsig.XMLSignature; +import javax.xml.crypto.dsig.XMLSignatureFactory; +import javax.xml.crypto.dsig.dom.DOMSignContext; +import javax.xml.crypto.dsig.dom.DOMValidateContext; +import javax.xml.crypto.dsig.keyinfo.KeyInfo; +import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; +import javax.xml.crypto.dsig.keyinfo.X509Data; +import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; +import javax.xml.crypto.dsig.spec.TransformParameterSpec; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import ch.eitchnet.utils.dbc.DBC; + +public class XmlDomSigner { + + private static final Logger logger = LoggerFactory.getLogger(XmlDomSigner.class); + + private KeyStore keyStore; + private String privateKeyAlias; + private String trustAlias; + + private char[] password; + + public XmlDomSigner(File keyStorePath, String privateKeyAlias, String trustAlias, char[] password) { + + DBC.PRE.assertNotEmpty("privateKeyAlias", privateKeyAlias); + DBC.PRE.assertNotEmpty("trustAlias", trustAlias); + try { + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(new FileInputStream(keyStorePath), password); + this.keyStore = keyStore; + this.privateKeyAlias = privateKeyAlias; + this.trustAlias = trustAlias; + this.password = password; + + } catch (Exception e) { + throw new RuntimeException("Failed to read keystore " + keyStorePath); + } + } + + public void sign(Document document) throws RuntimeException { + + try { + + String id = "Signed_" + UUID.randomUUID().toString(); + Element rootElement = document.getDocumentElement(); + rootElement.setAttribute("ID", id); + rootElement.setIdAttribute("ID", true); + + // Create a DOM XMLSignatureFactory that will be used to + // generate the enveloped signature. + XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); + + // Create a Reference to the enveloped document (in this case, + // you are signing the whole document, so a URI of "" signifies + // that, and also specify the SHA1 digest algorithm and + // the ENVELOPED Transform. + List transforms = new ArrayList<>(); + transforms.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)); + transforms.add(fac.newTransform(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null)); + DigestMethod digestMethod = fac.newDigestMethod(DigestMethod.SHA1, null); + Reference ref = fac.newReference("#" + id, digestMethod, transforms, null, null); + + // Create the SignedInfo. + SignedInfo signedInfo = fac.newSignedInfo( + fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null), // + fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), // + Collections.singletonList(ref)); + + // Load the KeyStore and get the signing key and certificate. + PrivateKeyEntry keyEntry = (PrivateKeyEntry) this.keyStore.getEntry(this.privateKeyAlias, + new KeyStore.PasswordProtection(this.password)); + PrivateKey privateKey = keyEntry.getPrivateKey(); + X509Certificate cert = (X509Certificate) keyEntry.getCertificate(); + + // Create the KeyInfo containing the X509Data. + KeyInfoFactory kif = fac.getKeyInfoFactory(); + List x509Content = new ArrayList<>(); + + x509Content.add(cert.getSubjectX500Principal().getName()); + x509Content.add(cert); + X509Data xd = kif.newX509Data(x509Content); + KeyInfo keyInfo = kif.newKeyInfo(Collections.singletonList(xd)); + + // Create a DOMSignContext and specify the RSA PrivateKey and + // location of the resulting XMLSignature's parent element. + DOMSignContext dsc = new DOMSignContext(privateKey, rootElement); + //dsc.setDefaultNamespacePrefix("samlp"); + dsc.putNamespacePrefix(XMLSignature.XMLNS, "ds"); + + // Create the XMLSignature, but don't sign it yet. + XMLSignature signature = fac.newXMLSignature(signedInfo, keyInfo); + + // Marshal, generate, and sign the enveloped signature. + signature.sign(dsc); + + } catch (Exception e) { + throw new RuntimeException("Failed to sign document", e); + } + } + + public void validate(Document doc) throws RuntimeException { + + try { + + // Create a DOM XMLSignatureFactory that will be used to + // generate the enveloped signature. + XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); + + // Find Signature element. + NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + if (nl.getLength() == 0) { + throw new Exception("Cannot find Signature element!"); + } else if (nl.getLength() > 1) { + throw new Exception("Found multiple Signature elements!"); + } + + // Load the KeyStore and get the signing key and certificate. + TrustedCertificateEntry entry = (TrustedCertificateEntry) this.keyStore.getEntry(trustAlias, null); + PublicKey publicKey = entry.getTrustedCertificate().getPublicKey(); + + // Create a DOMValidateContext and specify a KeySelector + // and document context. + Node signatureNode = nl.item(0); + DOMValidateContext valContext = new DOMValidateContext(publicKey, signatureNode); + + // Unmarshal the XMLSignature. + valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE); + XMLSignature signature = fac.unmarshalXMLSignature(valContext); + + // Validate the XMLSignature. + boolean coreValidity = signature.validate(valContext); + + // Check core validation status. + if (!coreValidity) { + logger.error("Signature failed core validation"); + boolean sv = signature.getSignatureValue().validate(valContext); + logger.error("signature validation status: " + sv); + if (!sv) { + // Check the validation status of each Reference. + Iterator i = signature.getSignedInfo().getReferences().iterator(); + for (int j = 0; i.hasNext(); j++) { + boolean refValid = ((Reference) i.next()).validate(valContext); + logger.error("ref[" + j + "] validity status: " + refValid); + } + } + throw new RuntimeException("Uh-oh validation, failed!"); + } + } catch (Exception e) { + if (e instanceof RuntimeException) + throw (RuntimeException) e; + throw new RuntimeException("Failed to validate document", e); + } + } + + public static byte[] transformToBytes(Document doc) { + return transformToBytes(doc, false); + } + + public static byte[] transformToBytes(Document doc, boolean indent) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + + if (indent) { + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + transformer.transform(new DOMSource(doc), new StreamResult(out)); + return out.toByteArray(); + } catch (TransformerFactoryConfigurationError | TransformerException e) { + throw new RuntimeException("Failed to transform document to bytes!", e); + } + } + + public static void writeTo(Document doc, File file) { + try { + writeTo(doc, new FileOutputStream(file)); + } catch (FileNotFoundException e) { + throw new RuntimeException("Failed to write document to " + file.getAbsolutePath(), e); + } + } + + public static void writeTo(Document doc, OutputStream out) { + try { + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.transform(new DOMSource(doc), new StreamResult(out)); + } catch (Exception e) { + throw new RuntimeException("Failed to write document to output stream!", e); + } + } + + public static Document parse(byte[] bytes) { + return parse(new ByteArrayInputStream(bytes)); + } + + public static Document parse(File signedXmlFile) { + try { + return parse(new FileInputStream(signedXmlFile)); + } catch (Exception e) { + throw new RuntimeException("Failed to parse signed file at " + signedXmlFile.getAbsolutePath(), e); + } + } + + public static Document parse(InputStream in) { + + Document doc; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + doc = dbf.newDocumentBuilder().parse(in); + } catch (Exception e) { + throw new RuntimeException("Failed to parse input stream", e); + } + + return doc; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java new file mode 100644 index 000000000..15410fd5b --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -0,0 +1,284 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.text.MessageFormat; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.utils.exceptions.XmlException; + +/** + * Helper class for performing XML based tasks + * + * @author Robert von Burg + */ +public class XmlHelper { + + /** + * PROP_LINE_SEPARATOR = "line.separator" : the system property to fetch defined line separator + */ + public static final String PROP_LINE_SEPARATOR = "line.separator"; //$NON-NLS-1$ + + /** + * UNIX_LINE_SEP = "\n" : mostly we want this line separator, instead of the windows version + */ + public static final String UNIX_LINE_SEP = "\n"; //$NON-NLS-1$ + + /** + * DEFAULT_ENCODING = "utf-8" : defines the default UTF-8 encoding expected of XML files + */ + public static final String DEFAULT_ENCODING = "UTF-8"; //$NON-NLS-1$ + + private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); + + /** + * Parses an XML file on the file system and returns the resulting {@link Document} object + * + * @param xmlFile + * the {@link File} which has the path to the XML file to read + */ + public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { + + try (FileInputStream xmlFileInputStream = new FileInputStream(xmlFile);) { + + parseDocument(xmlFileInputStream, xmlHandler); + String msg = "SAX parsed file {0}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, xmlFile.getAbsolutePath())); + + } catch (IOException e) { + String msg = "Failed to parse XML file: {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, xmlFile.getAbsolutePath(), e.getMessage()); + throw new XmlException(msg, e); + } + } + + /** + * Parses an XML file on the file system and returns the resulting {@link Document} object + * + * @param xmlFileInputStream + * the XML {@link InputStream} which is to be parsed + */ + public static void parseDocument(InputStream xmlFileInputStream, DefaultHandler xmlHandler) { + + try { + + SAXParserFactory spf = SAXParserFactory.newInstance(); + + SAXParser sp = spf.newSAXParser(); + sp.parse(xmlFileInputStream, xmlHandler); + + } catch (ParserConfigurationException e) { + throw new XmlException("Failed to initialize a SAX Parser: " + e.getMessage(), e); //$NON-NLS-1$ + } catch (SAXException e) { + throw new XmlException("The XML stream is not parseable: " + e.getMessage(), e); //$NON-NLS-1$ + } catch (IOException e) { + throw new XmlException("The XML stream not be read: " + e.getMessage(), e); //$NON-NLS-1$ + } + } + + /** + * Writes an {@link Element} to an XML file on the file system + * + * @param rootElement + * the {@link Element} to write to the file system + * @param file + * the {@link File} describing the path on the file system where the XML file should be written to + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element + */ + public static void writeElement(Element rootElement, File file) throws RuntimeException { + Document document = createDocument(); + document.appendChild(rootElement); + XmlHelper.writeDocument(document, file, DEFAULT_ENCODING); + } + + /** + * Writes a {@link Document} to an XML file on the file system + * + * @param document + * the {@link Document} to write to the file system + * @param file + * the {@link File} describing the path on the file system where the XML file should be written to + * @param encoding + * encoding to use to write the file + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element + */ + public static void writeDocument(Document document, File file) throws RuntimeException { + writeDocument(document, file, DEFAULT_ENCODING); + } + + /** + * Writes a {@link Document} to an XML file on the file system + * + * @param document + * the {@link Document} to write to the file system + * @param file + * the {@link File} describing the path on the file system where the XML file should be written to + * @param encoding + * encoding to use to write the file + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element + */ + public static void writeDocument(Document document, File file, String encoding) throws RuntimeException { + String msg = "Exporting document element {0} to {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, document.getNodeName(), file.getAbsolutePath()); + XmlHelper.logger.info(msg); + writeDocument(document, new StreamResult(file), encoding); + } + + /** + * Writes a {@link Document} to an XML file on the file system + * + * @param document + * the {@link Document} to write to the file system + * @param outputStream + * stream to write document to + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element + */ + public static void writeDocument(Document document, OutputStream outputStream) throws RuntimeException { + writeDocument(document, new StreamResult(outputStream), DEFAULT_ENCODING); + } + + /** + * Writes a {@link Document} to an XML file on the file system + * + * @param document + * the {@link Document} to write to the file system + * @param outputStream + * stream to write document to + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element + */ + public static String writeToString(Document document) throws RuntimeException { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + writeDocument(document, new StreamResult(out), DEFAULT_ENCODING); + return out.toString(DEFAULT_ENCODING); + } catch (UnsupportedEncodingException e) { + throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); //$NON-NLS-1$ + } + } + + /** + * Writes a {@link Document} to an XML file on the file system + * + * @param document + * the {@link Document} to write to the file system + * @param file + * the {@link File} describing the path on the file system where the XML file should be written to + * @param encoding + * encoding to use to write the file + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element + */ + public static void writeDocument(Document document, StreamResult streamResult, String encoding) + throws RuntimeException { + + String lineSep = System.getProperty(PROP_LINE_SEPARATOR); + try { + + String docEncoding = document.getInputEncoding(); + if (StringHelper.isEmpty(docEncoding)) { + docEncoding = encoding; + } + + if (!lineSep.equals("\n")) { //$NON-NLS-1$ + XmlHelper.logger.info("Overriding line separator to \\n"); //$NON-NLS-1$ + System.setProperty(PROP_LINE_SEPARATOR, UNIX_LINE_SEP); + } + + // Set up a transformer + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer transformer = transfac.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); //$NON-NLS-1$ + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ + transformer.setOutputProperty(OutputKeys.ENCODING, docEncoding); + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); //$NON-NLS-1$ //$NON-NLS-2$ + // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + + // Transform to file + Source xmlSource = new DOMSource(document); + transformer.transform(xmlSource, streamResult); + + } catch (Exception e) { + + throw new XmlException("Exception while exporting to file: " + e, e); //$NON-NLS-1$ + + } finally { + + System.setProperty(PROP_LINE_SEPARATOR, lineSep); + } + } + + /** + * Returns a new document instance + * + * @return a new document instance + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration + */ + public static Document createDocument() throws RuntimeException { + try { + + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document document = docBuilder.newDocument(); + + return document; + + } catch (DOMException e) { + throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); //$NON-NLS-1$ + } catch (ParserConfigurationException e) { + throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); //$NON-NLS-1$ + } + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java new file mode 100644 index 000000000..60a236da7 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.utils.io; + +/** + *

+ * This interface defines an API for use in situations where long running jobs notify observers of the jobs status. The + * jobs has a size which is a primitive long value e.g. the number of bytes parsed/ to be parsed in a file + *

+ * + * @author Robert von Burg + */ +public interface FileProgressListener { + + /** + * Notify the listener that the progress has begun + * + * @param percent + * percent completed + * @param position + * the position relative to the job size + * @param size + * the size of the job which is to be accomplished + */ + public void begin(int percent, long position, long size); + + /** + * Notifies the listener of incremental progress + * + * @param percent + * percent completed + * @param position + * the position relative to the job size + */ + public void progress(int percent, long position); + + /** + * Notifies the listener that the progress is completed + * + * @param percent + * the percent completed. Ideally the value would be 100, but in cases of errors it can be less + * @param position + * the position where the job finished. Ideally the value would be the same as the size given at + * {@link #begin(int, long, long)} but in case of errors it can be different + */ + public void end(int percent, long position); +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java new file mode 100644 index 000000000..10c0a2ca2 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.utils.io; + +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * File stream progress monitoring thread + * + * @author Robert von Burg + */ +public class FileStreamProgressWatcher implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(FileStreamProgressWatcher.class); + private ProgressableFileInputStream inputStream; + private boolean run = false; + private FileProgressListener progressListener; + private long millis; + + /** + * @param millis + * @param progressListener + * @param inputStream + */ + public FileStreamProgressWatcher(long millis, FileProgressListener progressListener, + ProgressableFileInputStream inputStream) { + this.millis = millis; + this.progressListener = progressListener; + this.inputStream = inputStream; + } + + @Override + public void run() { + this.run = true; + this.progressListener.begin(this.inputStream.getPercentComplete(), this.inputStream.getBytesRead(), + this.inputStream.getFileSize()); + + while (this.run) { + try { + int percentComplete = this.inputStream.getPercentComplete(); + if (this.inputStream.isClosed()) { + logger.info(MessageFormat.format("Input Stream is closed at: {0}%", percentComplete)); //$NON-NLS-1$ + this.run = false; + this.progressListener.end(percentComplete, this.inputStream.getBytesRead()); + } else if (percentComplete < 100) { + this.progressListener.progress(percentComplete, this.inputStream.getBytesRead()); + } else if (percentComplete >= 100) { + this.run = false; + this.progressListener.end(percentComplete, this.inputStream.getBytesRead()); + } + + if (this.run) + Thread.sleep(this.millis); + + } catch (InterruptedException e) { + this.run = false; + int percentComplete = this.inputStream.getPercentComplete(); + if (percentComplete != 100) + logger.info(MessageFormat.format("Work stopped: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + this.progressListener.end(percentComplete, this.inputStream.getBytesRead()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + this.run = false; + int percentComplete = this.inputStream.getPercentComplete(); + this.progressListener.end(percentComplete, Long.MAX_VALUE); + } + } + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java new file mode 100644 index 000000000..96b133e07 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.utils.io; + +import java.text.MessageFormat; + +import org.slf4j.Logger; + +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +public class LoggingFileProgressListener implements FileProgressListener { + + private final Logger logger; + private final String name; + + /** + * @param logger + * @param name + */ + public LoggingFileProgressListener(Logger logger, String name) { + this.logger = logger; + this.name = name; + } + + @Override + public void begin(int percent, long position, long size) { + String msg = "Starting to read {0} {1} of {2} ({3}%)"; //$NON-NLS-1$ + log(MessageFormat.format(msg, this.name, position, FileHelper.humanizeFileSize(size), percent)); + } + + @Override + public void progress(int percent, long position) { + String msg = "Read {0}% of {1} at position {2}"; //$NON-NLS-1$ + log(MessageFormat.format(msg, percent, this.name, FileHelper.humanizeFileSize(position))); + } + + @Override + public void end(int percent, long position) { + String msg = "Finished reading {0} at position {1} ({2}%)"; //$NON-NLS-1$ + log(MessageFormat.format(msg, this.name, FileHelper.humanizeFileSize(position), percent)); + } + + private void log(String msg) { + this.logger.info(msg); + } + +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java new file mode 100644 index 000000000..b98f525a3 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java @@ -0,0 +1,161 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.utils.io; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + *

+ * This sub class of {@link FileInputStream} allows to follow the currently read bytes of a {@link File}. In conjunction + * with a {@link Thread} and a {@link FileProgressListener} it is possible to track the progress of a long running job + * on bigger files + *

+ * + * @author Robert von Burg + */ +public class ProgressableFileInputStream extends FileInputStream { + + private volatile long fileSize; + private volatile long bytesRead; + private volatile boolean closed; + + /** + * Constructs a normal {@link FileInputStream} with the given {@link File} + * + * @param file + * the file to read + * @throws FileNotFoundException + * thrown if the {@link File} does not exist + */ + public ProgressableFileInputStream(File file) throws FileNotFoundException { + super(file); + this.fileSize = file.length(); + } + + /** + * @see java.io.FileInputStream#read() + */ + @Override + public int read() throws IOException { + synchronized (this) { + this.bytesRead++; + } + return super.read(); + } + + /** + * @see java.io.FileInputStream#read(byte[], int, int) + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + int read = super.read(b, off, len); + if (read != -1) { + synchronized (this) { + this.bytesRead += read; + } + } + return read; + } + + /** + * @see java.io.FileInputStream#read(byte[]) + */ + @Override + public int read(byte[] b) throws IOException { + int read = super.read(b); + if (read != -1) { + synchronized (this) { + this.bytesRead += read; + } + } + return read; + } + + /** + * @see java.io.FileInputStream#skip(long) + */ + @Override + public long skip(long n) throws IOException { + long skip = super.skip(n); + if (skip != -1) { + synchronized (this) { + this.bytesRead += skip; + } + } + return skip; + } + + /** + * @see java.io.FileInputStream#close() + */ + @Override + public void close() throws IOException { + this.closed = true; + super.close(); + } + + /** + * Returns the size of the file being read + * + * @return the size of the file being read + */ + public long getFileSize() { + return this.fileSize; + } + + /** + * Returns the number of bytes already read + * + * @return the number of bytes already read + */ + public long getBytesRead() { + synchronized (this) { + if (this.bytesRead > this.fileSize) + this.bytesRead = this.fileSize; + return this.bytesRead; + } + } + + /** + * Returns the percent read of the file + * + * @return the percentage complete of the process + */ + public int getPercentComplete() { + + long currentRead; + synchronized (this) { + if (this.bytesRead > this.fileSize) + this.bytesRead = this.fileSize; + currentRead = this.bytesRead; + } + + double read = (100.0d / this.fileSize * currentRead); + return (int) Math.ceil(read); + } + + /** + * Returns true if {@link #close()} was called, false otherwise + * + * @return the closed + */ + public boolean isClosed() { + return this.closed; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java new file mode 100644 index 000000000..b3ef20ada --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.iso8601; + +import java.util.Date; + +/** + * Interface for date formatting + * + * Martin Smock + */ +public interface DateFormat { + + /** + * format a long to string + * + * @param timepoint + * @return the formatted string of the long value + */ + public String format(long timepoint); + + /** + * format a Date to string + * + * @param date + * @return the formatted string of the long value + */ + public String format(Date date); + + /** + * parse a string to long + * + * @param s + * @return the value parsed + */ + public long parseLong(String s); + + /** + * parse a string to Date + * + * @param s + * @return the value parsed + */ + public Date parse(String s); +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java new file mode 100644 index 000000000..b5189d2e7 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.iso8601; + +/** + * Interface for duration formatting + * + * Martin Smock + */ +public interface DurationFormat { + + /** + * format a long to string + * + * @param l + * @return formatted string if the long argument + */ + public String format(long l); + + /** + * parse a string to long + * + * @param s + * @return the long value parsed + */ + public long parse(String s); + +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java new file mode 100644 index 000000000..e728ebe38 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java @@ -0,0 +1,142 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.iso8601; + +import java.util.Date; + +/** + * This interface defines methods for formatting values for UI representation and also defines factory methods for + * formatters for parsing and formatting duration and date values + * + * Martin Smock + */ +public interface FormatFactory { + + /** + * return the formatter for dates + * + * @return {@link DurationFormat} + */ + public DateFormat getDateFormat(); + + /** + * return the formatter for durations + * + * @return {@link DurationFormat} + */ + public DurationFormat getDurationFormat(); + + /** + * return the formatter for work time + * + * @return {@link WorktimeFormat} + */ + public WorktimeFormat getWorktimeFormat(); + + /** + * the date format used in xml import and export + * + * @return {@link DateFormat} + */ + public DateFormat getXmlDateFormat(); + + /** + * the duration format used in xml import and export + * + * @return {@link DurationFormat} + */ + public DurationFormat getXmlDurationFormat(); + + /** + * Formats a date using {@link #getDateFormat()} + * + * @param date + * the date to format to string + * + * @return String representation of the date + */ + public String formatDate(Date date); + + /** + * Formats a long as date using {@link #getDateFormat()} + * + * @param date + * the date to format to string + * + * @return String representation of the date + */ + public String formatDate(long date); + + /** + * Formats a duration using {@link #getDateFormat()} + * + * @param duration + * the duration to format to string + * + * @return String representation of the duration + */ + public String formatDuration(long duration); + + /** + * Formats a work time duration using {@link #getDateFormat()} + * + * @param worktime + * the work time duration to format to string + * + * @return String representation of the work time duration + */ + public String formatWorktime(long worktime); + + /** + * Formats a floating point number to have the configured number of decimals + * + * @param value + * the value to format + * + * @return the floating point formatted as a string + */ + public String formatFloat(double value); + + /** + * Parses a date using {@link #getDateFormat()} + * + * @param date + * the string to parse to date + * + * @return the date + */ + public Date parseDate(String date); + + /** + * Parses a duration using {@link #getDateFormat()} + * + * @param duration + * the string to parse to duration + * + * @return the duration + */ + public long parseDuration(String duration); + + /** + * Parses a work time duration using {@link #getDateFormat()} + * + * @param worktime + * the string duration to parse to work time + * + * @return the work time + */ + public long parseWorktime(String worktime); +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java new file mode 100644 index 000000000..9f66894a1 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java @@ -0,0 +1,297 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.iso8601; + +import java.text.DecimalFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Martin Smock + */ +@SuppressWarnings("nls") +public class ISO8601 implements DateFormat { + + private static final Logger logger = LoggerFactory.getLogger(ISO8601.class); + + //misc. numeric formats used in formatting + private DecimalFormat xxFormat = new DecimalFormat("00"); + private DecimalFormat xxxFormat = new DecimalFormat("000"); + private DecimalFormat xxxxFormat = new DecimalFormat("0000"); + + /** + * + */ + private Calendar parseToCalendar(String text) { + + // check optional leading sign + char sign; + int start; + if (text.startsWith("-")) { + sign = '-'; + start = 1; + } else if (text.startsWith("+")) { + sign = '+'; + start = 1; + } else { + sign = '+'; // no sign specified, implied '+' + start = 0; + } + + /** + * format of the string is: YYYY-MM-DDThh:mm:ss.SSSTZD + */ + int year, month, day, hour, min, sec, millisec; + String timeZone; + try { + + // year (YYYY) + year = Integer.parseInt(text.substring(start, start + 4)); + start += 4; + // delimiter '-' + if (text.charAt(start) != '-') { + return null; + } + start++; + + // month (MM) + month = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + // delimiter '-' + if (text.charAt(start) != '-') { + return null; + } + start++; + + // day (DD) + day = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + // delimiter 'T' + if (text.charAt(start) != 'T') { + return null; + } + start++; + + // hour (hh) + hour = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + // delimiter ':' + if (text.charAt(start) != ':') { + return null; + } + start++; + + // minute (mm) + min = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + // delimiter ':' + if (text.charAt(start) != ':') { + return null; + } + start++; + + // second (ss) + sec = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + + // delimiter '.' + if (text.charAt(start) == '.') { + start++; + // millisecond (SSS) + millisec = Integer.parseInt(text.substring(start, start + 3)); + start += 3; + } else { + millisec = 0; + } + + if (text.charAt(start) == '+' || text.charAt(start) == '-') { + timeZone = "GMT" + text.substring(start); + } else if (text.substring(start).equals("Z")) { + timeZone = "GMT"; + } else { + return null; + } + + } catch (IndexOutOfBoundsException e) { + return null; + } catch (NumberFormatException e) { + return null; + } + + TimeZone tz = TimeZone.getTimeZone(timeZone); + if (!tz.getID().equals(timeZone)) { + // invalid time zone + return null; + } + + // create Calendar + Calendar cal = Calendar.getInstance(tz); + cal.setLenient(false); + + if (sign == '-' || year == 0) { + // + cal.set(Calendar.YEAR, year + 1); + cal.set(Calendar.ERA, GregorianCalendar.BC); + } else { + cal.set(Calendar.YEAR, year); + cal.set(Calendar.ERA, GregorianCalendar.AD); + } + + // + cal.set(Calendar.MONTH, month - 1); + cal.set(Calendar.DAY_OF_MONTH, day); + cal.set(Calendar.HOUR_OF_DAY, hour); + cal.set(Calendar.MINUTE, min); + cal.set(Calendar.SECOND, sec); + cal.set(Calendar.MILLISECOND, millisec); + + try { + cal.getTime(); + } catch (IllegalArgumentException e) { + return null; + } + + return cal; + } + + /** + * + */ + private String format(Calendar cal) { + + if (cal == null) { + throw new IllegalArgumentException("argument can not be null"); + } + + // determine era and adjust year if necessary + int year = cal.get(Calendar.YEAR); + if (cal.isSet(Calendar.ERA) && cal.get(Calendar.ERA) == GregorianCalendar.BC) { + /** + * calculate year using astronomical system: year n BCE => astronomical year -n + 1 + */ + year = 0 - year + 1; + } + + /** + * format of date/time string is: YYYY-MM-DDThh:mm:ss.SSSTZD + */ + StringBuilder sWriter = new StringBuilder(); + sWriter.append(this.xxxxFormat.format(year)); + sWriter.append('-'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.MONTH) + 1)); + sWriter.append('-'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.DAY_OF_MONTH))); + sWriter.append('T'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.HOUR_OF_DAY))); + sWriter.append(':'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.MINUTE))); + sWriter.append(':'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.SECOND))); + sWriter.append('.'); + sWriter.append(this.xxxFormat.format(cal.get(Calendar.MILLISECOND))); + TimeZone tz = cal.getTimeZone(); + + int offset = tz.getOffset(cal.getTimeInMillis()); + if (offset != 0) { + int hours = Math.abs((offset / (60 * 1000)) / 60); + int minutes = Math.abs((offset / (60 * 1000)) % 60); + sWriter.append(offset < 0 ? '-' : '+'); + sWriter.append(this.xxFormat.format(hours)); + sWriter.append(':'); + sWriter.append(this.xxFormat.format(minutes)); + } else { + sWriter.append('Z'); + } + return sWriter.toString(); + } + + @Override + public String format(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return format(cal); + } + + /** + * added by msmock convert a long to ISO8601 + * + * @param timePoint + * @return time point as ISO8601 String + */ + @Override + public String format(long timePoint) { + + if (timePoint == Long.MAX_VALUE || timePoint == Long.MIN_VALUE) { + return "-"; + } + + // else + try { + Date date = new Date(); + date.setTime(timePoint); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return format(cal); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return null; + } + } + + @Override + public long parseLong(String s) { + return parse(s).getTime(); + } + + /** + * parse ISO8601 date to long + * + * @param s + * the string to parse + * @return time point as long + * @throws NumberFormatException + */ + @Override + public Date parse(String s) { + + if (StringHelper.isEmpty(s)) { + String msg = "An empty value can not pe parsed to a date!"; + throw new IllegalArgumentException(msg); + } + + if (s.equals("-")) { + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.setTimeZone(TimeZone.getTimeZone("GMT0")); + return cal.getTime(); + } + + Calendar cal = parseToCalendar(s); + if (cal != null) { + return cal.getTime(); + } + + String msg = "Input string '" + s + "' cannot be parsed to date."; + throw new IllegalArgumentException(msg); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java new file mode 100644 index 000000000..61e63f94d --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java @@ -0,0 +1,264 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.iso8601; + +/** + *

+ * Duration is defined as a duration of time, as specified in ISO 8601, Section 5.5.3.2. Its lexical representation is + * the ISO 8601 extended format: PnYnMnDnTnHnMnS + *

+ *
    + *
  • The "P" (period) is required
  • + *
  • "n" represents a positive number
  • + *
  • years is (Y)
  • + *
  • months is (M)
  • + *
  • days is (D)
  • + *
  • time separator is (T), required if any lower terms are given
  • + *
  • hours is (H)
  • + *
  • minutes is (M)
  • + *
  • seconds is (S)
  • + *
+ *

+ * An optional preceding minus sign ("-") is also allowed to indicate a negative duration. If the sign is omitted then a + * positive duration is assumed. For example: is a 2 hour, 5 minute, and + * 2.37 second duration + *

+ *

+ * Remark: since a duration of a day may be measured in hours may vary from 23 an 25 a duration day unit doesn't + * have a meaning, if we do not know either the start or the end, we restrict ourself to measure a duration in hours, + * minutes and seconds + *

+ * + * @author Martin Smock + * @author Michael Gatto (reimplementation using enum) + */ +@SuppressWarnings("nls") +public class ISO8601Duration implements DurationFormat { + + /** + * The time representations available, as enum, with the associated millis. + * + * @author gattom + */ + public enum TimeDuration { + SECOND(1000, 'S'), + MINUTE(60 * SECOND.duration(), 'M'), + HOUR(60 * MINUTE.duration(), 'H'), + DAY(24 * HOUR.duration(), 'D'), + WEEK(7 * DAY.duration(), 'W'), + MONTH(30 * DAY.duration(), 'M'), + YEAR(12 * MONTH.duration(), 'Y'); + + final long millis; + final char isoChar; + + TimeDuration(long milli, char isorep) { + this.millis = milli; + this.isoChar = isorep; + } + + public long duration() { + return this.millis; + } + + public static TimeDuration getTimeDurationFor(String isostring, int unitIndex) { + char duration = isostring.charAt(unitIndex); + switch (duration) { + case 'S': + if (isostring.substring(0, unitIndex).contains("T")) + return SECOND; + throw new NumberFormatException( + duration + " is not a valid unit of time in ISO8601 without a preceeding T (e.g.: PT1S)"); + case 'H': + if (isostring.substring(0, unitIndex).contains("T")) + return HOUR; + throw new NumberFormatException( + duration + " is not a valid unit of time in ISO8601 without a preceeding T (e.g.: PT1H)"); + case 'D': + return DAY; + case 'W': + return WEEK; + case 'Y': + return YEAR; + case 'M': + if (isostring.substring(0, unitIndex).contains("T")) + return MINUTE; + return MONTH; + default: + throw new NumberFormatException(duration + " is not a valid unit of time in ISO8601"); + } + } + } + + /** + * check if c is a number char including the decimal decimal dot (.) + * + * @param c + * the character to check + * @return boolean return true if the given char is a number or a decimal dot (.), false otherwise + */ + private static boolean isNumber(char c) { + + boolean isNumber = Character.isDigit(c) || (c == '.'); + return isNumber; + + } + + /** + * Parses the given string to a pseudo ISO 8601 duration + * + * @param s + * the string to be parsed to a duration which must be coded as a ISO8601 value + * @return long the time value which represents the duration + */ + @Override + public long parse(String s) { + + long newResult = 0; + + // throw exception, if the string is not of length > 2 + if (s == null || s.length() < 3) + throw new NumberFormatException(s + " cannot be parsed to ISO 8601 Duration"); + + char p = s.charAt(0); + + // the first char must be a P for ISO8601 duration + if (p != 'P') + throw new NumberFormatException(s + " cannot be parsed to ISO 8601 Duration"); + + int newposition = 1; + do { + if (s.charAt(newposition) == 'T') { + // skip the separator specifying where the time starts. + newposition++; + } + + // read the string representing the numeric value + String val = parseNumber(newposition, s); + double numVal = Double.parseDouble(val); + newposition += val.length(); + + // get the time unit + TimeDuration unit = TimeDuration.getTimeDurationFor(s, newposition); + + // skip the time duration character + newposition++; + + // increment the value. + newResult += unit.duration() * numVal; + + } while (newposition < s.length()); + + return newResult; + } + + /** + * Return the substring of s starting at index i (in s) that contains a numeric string. + * + * @param index + * The start index in string s + * @param s + * The string to analyze + * @return the substring containing the numeric portion of s starting at index i. + */ + private String parseNumber(int index, String s) { + int i = index; + int start = i; + while (i < s.length()) { + if (!isNumber(s.charAt(i))) + break; + i++; + } + String substring = s.substring(start, i); + return substring; + } + + /** + * Format the given time duration unit into the string buffer. This function displays the given duration in units of + * the given unit, and returns the remainder. + *

+ * Thus, a duration of 86401000 (one day and one second) will add the representation of one day if unit is DAY (1D) + * and return 1000 as the remainder with respect of this unit. If the given unit is HOUR, then this function adds + * 24H to the {@link StringBuilder}, and returns 1000 as the remainder. + * + * @param sb + * The {@link StringBuilder} to add the given duration with the right unit + * @param duration + * The duration to add + * @param unit + * The unit of this duration + * @return The remainder of the given duration, modulo the time unit. + */ + private long formatTimeDuration(StringBuilder sb, long duration, TimeDuration unit) { + + long remainder = duration; + if (unit.equals(TimeDuration.SECOND) || remainder >= unit.duration()) { + + long quantity = remainder / unit.duration(); + remainder = remainder % unit.duration(); + sb.append(quantity); + + if (unit.equals(TimeDuration.SECOND)) { + long millis = remainder; + if (millis == 0) { + // to not have the decimal point + } else if (millis > 99) { + sb.append("." + millis); + } else if (millis > 9) { + sb.append(".0" + millis); + } else { + sb.append(".00" + millis); + } + } + + sb.append(unit.isoChar); + } + + return remainder; + } + + /** + * Formats the given time duration to a pseudo ISO 8601 duration string + * + * @param duration + * @return String the duration formatted as a ISO8601 duration string + */ + @Override + public String format(long duration) { + + // XXX this is a preliminary help to solve the situation where this method sometimes returns P + if (duration < 0l) + throw new RuntimeException("A duration can not be negative!"); + + if (duration == 0l) + return "P0D"; + + StringBuilder sb = new StringBuilder(); + sb.append('P'); + + long remainder = formatTimeDuration(sb, duration, TimeDuration.YEAR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MONTH); + remainder = formatTimeDuration(sb, remainder, TimeDuration.DAY); + if (remainder > 0) { + sb.append('T'); + remainder = formatTimeDuration(sb, remainder, TimeDuration.HOUR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MINUTE); + remainder = formatTimeDuration(sb, remainder, TimeDuration.SECOND); + } + return sb.toString(); + } + +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java new file mode 100644 index 000000000..2cc57f670 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java @@ -0,0 +1,109 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.iso8601; + +import java.util.Date; + +import ch.eitchnet.utils.helper.MathHelper; + +/** + * Default factory for date formats used for serialization. + * + * @author Martin Smock + */ +public class ISO8601FormatFactory implements FormatFactory { + + private static ISO8601FormatFactory instance = new ISO8601FormatFactory(); + + /** + * the singleton constructor + */ + private ISO8601FormatFactory() { + // singleton + } + + /** + * @return the instance + */ + public static ISO8601FormatFactory getInstance() { + return instance; + } + + @Override + public ISO8601 getDateFormat() { + return new ISO8601(); + } + + @Override + public ISO8601Duration getDurationFormat() { + return new ISO8601Duration(); + } + + @Override + public ISO8601Worktime getWorktimeFormat() { + return new ISO8601Worktime(); + } + + @Override + public ISO8601 getXmlDateFormat() { + return new ISO8601(); + } + + @Override + public ISO8601Duration getXmlDurationFormat() { + return new ISO8601Duration(); + } + + @Override + public String formatDate(Date date) { + return getDateFormat().format(date); + } + + @Override + public String formatDate(long date) { + return getDateFormat().format(date); + } + + @Override + public String formatDuration(long duration) { + return getDurationFormat().format(duration); + } + + @Override + public String formatWorktime(long worktime) { + return getDurationFormat().format(worktime); + } + + @Override + public String formatFloat(double value) { + return Double.toString(MathHelper.toPrecision(value)); + } + + @Override + public Date parseDate(String date) { + return getDateFormat().parse(date); + } + + @Override + public long parseDuration(String duration) { + return getDurationFormat().parse(duration); + } + + @Override + public long parseWorktime(String worktime) { + return getDurationFormat().parse(worktime); + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java new file mode 100644 index 000000000..e107a7be7 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java @@ -0,0 +1,237 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.iso8601; + +/** + *

+ * Duration is defined as a duration of time, as specified in ISO 8601, Section 5.5.3.2. Its lexical representation is + * the ISO 8601 extended format: PnYnMnDnTnHnMnS + *

+ *
    + *
  • The "P" (period) is required
  • + *
  • "n" represents a positive number
  • + *
  • years is (Y)
  • + *
  • months is (M)
  • + *
  • days is (D)
  • + *
  • time separator is (T), required if any lower terms are given
  • + *
  • hours is (H)
  • + *
  • minutes is (M)
  • + *
  • seconds is (S)
  • + *
+ *

+ * An optional preceding minus sign ("-") is also allowed to indicate a negative duration. If the sign is omitted then a + * positive duration is assumed. For example: is a 2 hour, 5 minute, and + * 2.37 second duration + *

+ *

+ * Remark: since a duration of a day may be measured in hours may vary from 23 an 25 a duration day unit doesn't + * have a meaning, if we do not know either the start or the end, we restrict ourself to measure a duration in hours, + * minutes and seconds + *

+ * + * @author Martin Smock + * @author Michael Gatto (reimplementation using enum) + */ +@SuppressWarnings("nls") +public class ISO8601Worktime implements WorktimeFormat { + + /** + * The time representations available, as enum, with the associated millis. + * + * @author gattom + */ + public enum TimeDuration { + + SECOND(1000, 'S'), MINUTE(60 * SECOND.duration(), 'M'), HOUR(60 * MINUTE.duration(), 'H'); + + final long millis; + final char isoChar; + + TimeDuration(long milli, char isorep) { + this.millis = milli; + this.isoChar = isorep; + } + + public long duration() { + return this.millis; + } + + public static TimeDuration getTimeDurationFor(String isostring, int unitIndex) { + char duration = isostring.charAt(unitIndex); + switch (duration) { + case 'S': + if (isostring.substring(0, unitIndex).contains("T")) + return SECOND; + throw new NumberFormatException( + duration + " is not a valid unit of time in ISO8601 without a preceeding T (e.g.: PT1S)"); + case 'H': + if (isostring.substring(0, unitIndex).contains("T")) + return HOUR; + throw new NumberFormatException( + duration + " is not a valid unit of time in ISO8601 without a preceeding T (e.g.: PT1H)"); + case 'M': + return MINUTE; + default: + throw new NumberFormatException(duration + " is not a valid unit of time in ISO8601"); + } + } + + } + + /** + * check if c is a number char including the decimal decimal dot (.) + * + * @param c + * the character to check + * @return boolean return true if the given char is a number or a decimal dot (.), false otherwise + */ + private static boolean isNumber(char c) { + boolean isNumber = Character.isDigit(c) || (c == '.'); + return isNumber; + } + + /** + * Parses the given string to a pseudo ISO 8601 duration + * + * @param s + * the string to be parsed to a duration which must be coded as a ISO8601 value + * @return long the time value which represents the duration + */ + @Override + public long parse(String s) { + + long newResult = 0; + + // throw exception, if the string is not of length > 2 + if (s.length() < 3) + throw new NumberFormatException(s + " cannot be parsed to ISA 8601 Duration"); + + char p = s.charAt(0); + + if (p == 'P') { + int newposition = 1; + do { + if (s.charAt(newposition) == 'T') { + // skip the separator specifying where the time starts. + newposition++; + } + // read the string representing the numeric value + String val = parseNumber(newposition, s); + double numVal = Double.parseDouble(val); + newposition += val.length(); + // get the time unit + TimeDuration unit = TimeDuration.getTimeDurationFor(s, newposition); + // skip the time duration character + newposition++; + // increment the value. + newResult += unit.duration() * numVal; + } while (newposition < s.length()); + + return newResult; + } + + throw new NumberFormatException(s + " cannot be parsed to ISO 8601 Duration"); + } + + /** + * Return the substring of s starting at index i (in s) that contains a numeric string. + * + * @param index + * The start index in string s + * @param s + * The string to analyze + * @return the substring containing the numeric portion of s starting at index i. + */ + private String parseNumber(int index, String s) { + int i = index; + int start = i; + while (i < s.length()) { + if (!isNumber(s.charAt(i))) + break; + i++; + } + String substring = s.substring(start, i); + return substring; + } + + /** + * Format the given time duration unit into the string buffer. This function displays the given duration in units of + * the given unit, and returns the remainder. + *

+ * Thus, a duration of 86401000 (one day and one second) will add the representation of one day if unit is DAY (1D) + * and return 1000 as the remainder with respect of this unit. If the given unit is HOUR, then this function adds + * 24H to the {@link StringBuilder}, and returns 1000 as the remainder. + * + * @param sb + * The {@link StringBuilder} to add the given duration with the right unit + * @param duration + * The duration to add + * @param unit + * The unit of this duration + * @return The remainder of the given duration, modulo the time unit. + */ + private long formatTimeDuration(StringBuilder sb, long duration, TimeDuration unit) { + + long remainder = duration; + + if (unit.equals(TimeDuration.SECOND) || remainder >= unit.duration()) { + + long quantity = remainder / unit.duration(); + remainder = remainder % unit.duration(); + sb.append(quantity); + + if (unit.equals(TimeDuration.SECOND)) { + + long millis = remainder; + if (millis == 0) { + // to not have the decimal point + } else if (millis > 99) { + sb.append("." + millis); + } else if (millis > 9) { + sb.append(".0" + millis); + } else { + sb.append(".00" + millis); + } + } + + sb.append(unit.isoChar); + } + return remainder; + } + + /** + * Formats the given time duration to a pseudo ISO 8601 duration string + * + * @param duration + * @return String the duration formatted as a ISO8601 duration string + */ + @Override + public String format(long duration) { + + if (duration == 0) + return "PT0S"; + + StringBuilder sb = new StringBuilder(); + sb.append('P'); + sb.append('T'); + long remainder = formatTimeDuration(sb, duration, TimeDuration.HOUR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MINUTE); + remainder = formatTimeDuration(sb, remainder, TimeDuration.SECOND); + + return sb.toString(); + } + +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java new file mode 100644 index 000000000..c026e060f --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 Martin Smock + * + * 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 ch.eitchnet.utils.iso8601; + +/** + * interface for the worktime format + * + * @author Martin Smock + */ +public interface WorktimeFormat { + + /** + * format a long to string + * + * @param l + * @return formatted string if the long argument + */ + public String format(long l); + + /** + * parse a string to long + * + * @param s + * @return the long value parsed + */ + public long parse(String s); + +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java new file mode 100644 index 000000000..8739e86d1 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -0,0 +1,149 @@ +/* + * Copyright 2013 Michael Gatto + * + * 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 ch.eitchnet.utils.objectfilter; + +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is a cache for objects whose operations (additions, modifications, removals) are first collected and then + * "deployed" in one go. + *

+ * Thus, this class keeps: + *

    + *
  • An ID of the object, such that it can be referenced externally. + *
  • A key for an object, which keeps the object's type.
  • + *
  • A reference to the current state of the object
  • + *
  • An identifier of the operation that needs to be performed on this
  • + *
+ *

+ * + * @author Michael Gatto + * @author Robert von Burg + */ +public class ObjectCache { + + private final static Logger logger = LoggerFactory.getLogger(ObjectCache.class); + + /** + * UNSET Marker to determine if ids have not been set. + */ + public static final long UNSET = 0; + + /** + * id The unique ID of this object in this session + */ + private final long id; + + /** + * key The key defining who's registered for this object's state + */ + private final String key; + + /** + * operation The operation that has occurred on this object. + */ + private Operation operation; + + /** + * object The object that shall be cached + */ + private Object object; + + /** + * @param id + * @param key + * @param object + * @param operation + */ + @SuppressWarnings("nls") + public ObjectCache(long id, String key, Object object, Operation operation) { + + this.id = id; + this.key = key; + this.object = object; + this.operation = operation; + + if (logger.isDebugEnabled()) { + StringBuilder sb = new StringBuilder(); + sb.append("Instanciated Cache: ID"); + sb.append(this.id); + sb.append(" / "); + sb.append(key); + sb.append(" OP: "); + sb.append(this.operation); + sb.append(" / "); + sb.append(object.toString()); + logger.debug(sb.toString()); + } + } + + /** + * Set the new object version of this cache. + * + * @param object + */ + public void setObject(Object object) { + if (logger.isDebugEnabled()) { + logger.debug(MessageFormat.format("Updating ID {0} to value {1}", this.id, object.toString())); //$NON-NLS-1$ + } + this.object = object; + } + + /** + * Change the operation to execute for this object. + * + * @param newOperation + */ + public void setOperation(Operation newOperation) { + if (logger.isDebugEnabled()) { + String msg = "Updating Operation of ID {0} from {1} to {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.id, this.operation, newOperation); + logger.debug(msg); + } + this.operation = newOperation; + } + + /** + * @return the id + */ + public long getId() { + return this.id; + } + + /** + * @return the key + */ + public String getKey() { + return this.key; + } + + /** + * @return the operation + */ + public Operation getOperation() { + return this.operation; + } + + /** + * @return the object + */ + public Object getObject() { + return this.object; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java new file mode 100644 index 000000000..6ea65d1ac --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -0,0 +1,693 @@ +/* + * Copyright 2013 Michael Gatto + * + * 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 ch.eitchnet.utils.objectfilter; + +import java.text.MessageFormat; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class implements a filter where modifications to an object are collected, and only the most recent action and + * version of the object is returned. + *

+ * In its current implementation, any instance of an object may "live" in the cache registered only under one single + * key. + *

+ *

+ * The rules of the cache are defined as follows. The top row represents the state of the cache for an object, given in + * version O1. Here, "N/A" symbolizes that the object is not yet present in the cache or, if it is a result of an + * operation, that it is removed from the cache. Each other row symbolizes the next action that is performed on an + * object, with the cell containing the "final" action for this object with the version of the object to be retained. + * Err! symbolize incorrect sequences of events that cause an exception. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Action \ State in CacheN/AAdd(01)Update(O1)Remove(O1)
Add (O2)Add(O2)Err!Err!Update(O2)
Update (O2)Update(O2)Add(O2)Update(O2)Err!
Remove (O2)Remove(O2)N/ARemove(O2)Err!
+ * + * @author Michael Gatto (initial version) + * @author Robert von Burg (minor modifications, refactorings) + */ +public class ObjectFilter { + + private final static Logger logger = LoggerFactory.getLogger(ObjectFilter.class); + + private static long id = ObjectCache.UNSET; + + private final Map cache; + private final Set keySet; + + /** + * Default constructor initializing the filter + */ + public ObjectFilter() { + this.cache = new HashMap<>(); + this.keySet = new HashSet<>(); + } + + /** + * Register, under the given key, the addition of the given object. + *

+ * This is the single point where the updating logic is applied for the cache in case of addition. The logic is: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Action\State in CacheN/AAdd(01)Update(O1)Remove(O1)
Add (O2)Add(O2)Err!Err!Update(O2)
+ * + * @param key + * the key to register the object with + * @param objectToAdd + * The object for which addition shall be registered. + */ + public void add(String key, Object objectToAdd) { + + if (ObjectFilter.logger.isDebugEnabled()) + ObjectFilter.logger.debug(MessageFormat.format("add object {0} with key {1}", objectToAdd, key)); //$NON-NLS-1$ + + // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. + ObjectCache cached = this.cache.get(objectToAdd); + if (cached == null) { + + // The object has not yet been added to the cache. + // Hence, we add it now, with the ADD operation. + ObjectCache cacheObj = new ObjectCache(dispenseID(), key, objectToAdd, Operation.ADD); + this.cache.put(objectToAdd, cacheObj); + + } else { + + String existingKey = cached.getKey(); + if (!existingKey.equals(key)) { + String msg = "Invalid key provided for object with transaction ID {0} and operation {1}: existing key is {2}, new key is {3}. Object may be present in the same filter instance only once, registered using one key only. Object:{4}"; //$NON-NLS-1$ + throw new IllegalArgumentException(MessageFormat.format(msg, Long.toString(id), + Operation.ADD.toString(), existingKey, key, objectToAdd.toString())); + } + + // The object is in cache: update the version as required, keeping in mind that most + // of the cases here will be mistakes... + Operation op = cached.getOperation(); + switch (op) { + case ADD: + throw new IllegalStateException("Stale State exception: Invalid + after +"); //$NON-NLS-1$ + case MODIFY: + throw new IllegalStateException("Stale State exception: Invalid + after +="); //$NON-NLS-1$ + case REMOVE: + // replace key if necessary + replaceKey(cached.getObject(), objectToAdd); + + // update operation's object + cached.setObject(objectToAdd); + cached.setOperation(Operation.MODIFY); + break; + default: + throw new IllegalStateException("Stale State exception: Unhandled state " + op); //$NON-NLS-1$ + } // switch + }// else of object not in cache + + // register the key + this.keySet.add(key); + } + + private void replaceKey(Object oldObject, Object newObject) { + if (oldObject != newObject) { + if (ObjectFilter.logger.isDebugEnabled()) { + String msg = "Replacing key for object as they are not the same reference: old: {0} / new: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, oldObject, newObject); + ObjectFilter.logger.warn(msg); + } + ObjectCache objectCache = this.cache.remove(oldObject); + this.cache.put(newObject, objectCache); + } + } + + /** + * Register, under the given key, the update of the given object. *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Action \ State in CacheN/AAdd(01)Update(O1)Remove(O1)
Update (O2)Update(O2)Add(O2)Update(O2)Err!
+ * + * @param key + * the key to register the object with + * @param objectToUpdate + * The object for which update shall be registered. + */ + public void update(String key, Object objectToUpdate) { + + if (ObjectFilter.logger.isDebugEnabled()) + ObjectFilter.logger.debug(MessageFormat.format("update object {0} with key {1}", objectToUpdate, key)); //$NON-NLS-1$ + + // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. + ObjectCache cached = this.cache.get(objectToUpdate); + if (cached == null) { + + // The object got an ID during this run, but was not added to this cache. + // Hence, we add it now, with the current operation. + ObjectCache cacheObj = new ObjectCache(dispenseID(), key, objectToUpdate, Operation.MODIFY); + this.cache.put(objectToUpdate, cacheObj); + + } else { + + String existingKey = cached.getKey(); + if (!existingKey.equals(key)) { + String msg = "Invalid key provided for object with transaction ID {0} and operation {1}: existing key is {2}, new key is {3}. Object may be present in the same filter instance only once, registered using one key only. Object:{4}"; //$NON-NLS-1$ + throw new IllegalArgumentException(MessageFormat.format(msg, Long.toString(id), + Operation.MODIFY.toString(), existingKey, key, objectToUpdate.toString())); + } + + // The object is in cache: update the version as required. + Operation op = cached.getOperation(); + switch (op) { + case ADD: + case MODIFY: + // replace key if necessary + replaceKey(cached.getObject(), objectToUpdate); + + // update operation's object + cached.setObject(objectToUpdate); + break; + case REMOVE: + throw new IllegalStateException("Stale State exception: Invalid += after -"); //$NON-NLS-1$ + default: + throw new IllegalStateException("Stale State exception: Unhandled state " + op); //$NON-NLS-1$ + } // switch + }// else of object not in cache + + // register the key + this.keySet.add(key); + } + + /** + * Register, under the given key, the removal of the given object. *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Action \ State in CacheN/AAdd(01)Update(O1)Remove(O1)
Remove (O2)Remove(O2)N/ARemove(O2)Err!
+ * + * @param key + * the key to register the object with + * @param objectToRemove + * The object for which removal shall be registered. + */ + public void remove(String key, Object objectToRemove) { + + if (ObjectFilter.logger.isDebugEnabled()) + ObjectFilter.logger.debug(MessageFormat.format("remove object {0} with key {1}", objectToRemove, key)); //$NON-NLS-1$ + + // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. + ObjectCache cached = this.cache.get(objectToRemove); + if (cached == null) { + // The object got an ID during this run, but was not added to this cache. + // Hence, we add it now, with the current operation. + ObjectCache cacheObj = new ObjectCache(dispenseID(), key, objectToRemove, Operation.REMOVE); + this.cache.put(objectToRemove, cacheObj); + } else { + + String existingKey = cached.getKey(); + if (!existingKey.equals(key)) { + String msg = "Invalid key provided for object with transaction ID {0} and operation {1}: existing key is {2}, new key is {3}. Object may be present in the same filter instance only once, registered using one key only. Object:{4}"; //$NON-NLS-1$ + throw new IllegalArgumentException(MessageFormat.format(msg, Long.toString(id), + Operation.REMOVE.toString(), existingKey, key, objectToRemove.toString())); + } + + // The object is in cache: update the version as required. + Operation op = cached.getOperation(); + switch (op) { + case ADD: + // this is a case where we're removing the object from the cache, since we are + // removing it now and it was added previously. + this.cache.remove(objectToRemove); + break; + case MODIFY: + // replace key if necessary + replaceKey(cached.getObject(), objectToRemove); + + // update operation's object + cached.setObject(objectToRemove); + cached.setOperation(Operation.REMOVE); + break; + case REMOVE: + throw new IllegalStateException("Stale State exception: Invalid - after -"); //$NON-NLS-1$ + default: + throw new IllegalStateException("Stale State exception: Unhandled state " + op); //$NON-NLS-1$ + } // switch + } + + // register the key + this.keySet.add(key); + } + + /** + * Register, under the given key, the addition of the given list of objects. + * + * @param key + * the key to register the objects with + * @param addedObjects + * The objects for which addition shall be registered. + */ + public void addAll(String key, Collection addedObjects) { + for (Object addObj : addedObjects) { + add(key, addObj); + } + } + + /** + * Register, under the given key, the update of the given list of objects. + * + * @param key + * the key to register the objects with + * @param updatedObjects + * The objects for which update shall be registered. + */ + public void updateAll(String key, Collection updatedObjects) { + for (Object update : updatedObjects) { + update(key, update); + } + } + + /** + * Register, under the given key, the removal of the given list of objects. + * + * @param key + * the key to register the objects with + * @param removedObjects + * The objects for which removal shall be registered. + */ + public void removeAll(String key, Collection removedObjects) { + for (Object removed : removedObjects) { + remove(key, removed); + } + } + + /** + * Register the addition of the given object. Since no key is provided, the class name is used as a key. + * + * @param object + * The object that shall be registered for addition + */ + public void add(Object object) { + add(object.getClass().getName(), object); + } + + /** + * Register the update of the given object. Since no key is provided, the class name is used as a key. + * + * @param object + * The object that shall be registered for updating + */ + public void update(Object object) { + update(object.getClass().getName(), object); + } + + /** + * Register the removal of the given object. Since no key is provided, the class name is used as a key. + * + * @param object + * The object that shall be registered for removal + */ + public void remove(Object object) { + remove(object.getClass().getName(), object); + } + + /** + * Register the addition of all objects in the list. Since no key is provided, the class name of each object is used + * as a key. + * + * @param objects + * The objects that shall be registered for addition + */ + public void addAll(List objects) { + for (Object addedObj : objects) { + add(addedObj.getClass().getName(), addedObj); + } + } + + /** + * Register the updating of all objects in the list. Since no key is provided, the class name of each object is used + * as a key. + * + * @param updateObjects + * The objects that shall be registered for updating + */ + public void updateAll(List updateObjects) { + for (Object update : updateObjects) { + update(update.getClass().getName(), update); + } + } + + /** + * Register the removal of all objects in the list. Since no key is provided, the class name of each object is used + * as a key. + * + * @param removedObjects + * The objects that shall be registered for removal + */ + public void removeAll(List removedObjects) { + for (Object removed : removedObjects) { + remove(removed.getClass().getName(), removed); + } + } + + /** + * Get all objects that were registered under the given key and that have as a resulting final action an addition. + * + * @param key + * The registration key of the objects to match + * + * @return The list of all objects registered under the given key and that need to be added. + */ + public List getAdded(String key) { + List addedObjects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { + addedObjects.add(objectCache.getObject()); + } + } + return addedObjects; + } + + /** + * Get all objects that were registered under the given key and that have as a resulting final action an addition. + * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. + * @param key + * The registration key of the objects to match + * + * @return The list of all objects registered under the given key and that need to be added. + */ + public List getAdded(Class clazz, String key) { + List addedObjects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + addedObjects.add(object); + } + } + } + return addedObjects; + } + + /** + * Get all objects that were registered under the given key and that have as a resulting final action an update. + * + * @param key + * registration key of the objects to match + * + * @return The list of all objects registered under the given key and that need to be updated. + */ + public List getUpdated(String key) { + List updatedObjects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { + updatedObjects.add(objectCache.getObject()); + } + } + return updatedObjects; + } + + /** + * Get all objects that were registered under the given key and that have as a resulting final action an update. + * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. + * @param key + * registration key of the objects to match + * + * @return The list of all objects registered under the given key and that need to be updated. + */ + public List getUpdated(Class clazz, String key) { + List updatedObjects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + updatedObjects.add(object); + } + } + } + return updatedObjects; + } + + /** + * Get all objects that were registered under the given key that have as a resulting final action their removal. + * + * @param key + * The registration key of the objects to match + * + * @return The list of object registered under the given key that have, as a final action, removal. + */ + public List getRemoved(String key) { + List removedObjects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { + removedObjects.add(objectCache.getObject()); + } + } + return removedObjects; + } + + /** + * Get all objects that were registered under the given key that have as a resulting final action their removal. + * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. + * @param key + * The registration key of the objects to match + * + * @return The list of object registered under the given key that have, as a final action, removal. + */ + public List getRemoved(Class clazz, String key) { + List removedObjects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + removedObjects.add(object); + } + } + } + return removedObjects; + } + + /** + * Get all objects that were registered under the given key + * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. + * @param key + * The registration key of the objects to match + * + * @return The list of object registered under the given key that have, as a final action, removal. + */ + public List getAll(Class clazz, String key) { + List objects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getKey().equals(key)) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + objects.add(object); + } + } + } + return objects; + } + + /** + * Get all objects that of the given class + * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. + * + * @return The list of all objects that of the given class + */ + public List getAll(Class clazz) { + List objects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + objects.add(object); + } + } + return objects; + } + + /** + * Get all the objects that were processed in this transaction, that were registered under the given key. No action + * is associated to the object. + * + * @param key + * The registration key for which the objects shall be retrieved + * + * @return The list of objects matching the given key. + */ + public List getAll(String key) { + List allObjects = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getKey().equals(key)) { + allObjects.add(objectCache.getObject()); + } + } + return allObjects; + } + + /** + * Get all the objects that were processed in this transaction, that were registered under the given key. No action + * is associated to the object. + * + * @param key + * The registration key for which the objects shall be retrieved + * + * @return The list of objects matching the given key. + */ + public List getCache(String key) { + List allCache = new LinkedList<>(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getKey().equals(key)) { + allCache.add(objectCache); + } + } + return allCache; + } + + /** + * Return the set of keys that are currently present in the object filter. + * + * @return The set containing the keys of that have been added to the filter. + */ + public Set keySet() { + return this.keySet; + } + + /** + * Clears the cache + */ + public void clearCache() { + this.cache.clear(); + this.keySet.clear(); + } + + /** + * @return the set of keys used to register objects + */ + public int sizeKeySet() { + return this.keySet.size(); + } + + /** + * @return the number of objects registered in this filter + */ + public int sizeCache() { + return this.cache.size(); + } + + /** + * @return get a unique transaction ID + */ + public synchronized long dispenseID() { + ObjectFilter.id++; + if (ObjectFilter.id == Long.MAX_VALUE) { + ObjectFilter.logger.error("Rolling IDs of objectFilter back to 1. Hope this is fine."); //$NON-NLS-1$ + ObjectFilter.id = 1; + } + return ObjectFilter.id; + } +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java new file mode 100644 index 000000000..a54197fe4 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Michael Gatto + * + * 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 ch.eitchnet.utils.objectfilter; + +/** + * A discrete set of operations associated to some object / state + * + * @author Michael Gatto + */ +public enum Operation { + /** + * ADD The operation associated to something is addition. + */ + ADD, + /** + * MODIFY The operation associated to something is a modification. + */ + MODIFY, + /** + * REMOVE The operation associated to something is removal + */ + REMOVE; +} diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java new file mode 100644 index 000000000..f813b230c --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java @@ -0,0 +1,73 @@ +package ch.eitchnet.utils.xml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.xml.bind.annotation.XmlAttribute; + +public class XmlKeyValue { + + @XmlAttribute(name = "key") + private String key; + @XmlAttribute(name = "value") + private String value; + + public XmlKeyValue(String key, String value) { + this.key = key; + this.value = value; + } + + public XmlKeyValue() { + // no-arg constructor for JAXB + } + + /** + * @return the key + */ + public String getKey() { + return this.key; + } + + /** + * @param key + * the key to set + */ + public void setKey(String key) { + this.key = key; + } + + /** + * @return the value + */ + public String getValue() { + return this.value; + } + + /** + * @param value + * the value to set + */ + public void setValue(String value) { + this.value = value; + } + + public static List valueOf(Map map) { + List keyValues = new ArrayList<>(map.size()); + for (Entry entry : map.entrySet()) { + keyValues.add(new XmlKeyValue(entry.getKey(), entry.getValue())); + } + return keyValues; + } + + public static Map toMap(List values) { + Map propertyMap = new HashMap<>(values.size()); + for (XmlKeyValue xmlKeyValue : values) { + propertyMap.put(xmlKeyValue.getKey(), xmlKeyValue.getValue()); + } + + return propertyMap; + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/main/java/javanet/staxutils/Indentation.java b/ch.eitchnet.utils/src/main/java/javanet/staxutils/Indentation.java new file mode 100644 index 000000000..844513789 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/javanet/staxutils/Indentation.java @@ -0,0 +1,36 @@ +package javanet.staxutils; + +/** + * Characters that represent line breaks and indentation. These are represented as String-valued JavaBean properties. + */ +@SuppressWarnings("nls") +public interface Indentation { + + /** Two spaces; the default indentation. */ + public static final String DEFAULT_INDENT = " "; + + /** + * Set the characters used for one level of indentation. The default is {@link #DEFAULT_INDENT}. "\t" is a popular + * alternative. + */ + void setIndent(String indent); + + /** The characters used for one level of indentation. */ + String getIndent(); + + /** + * "\n"; the normalized representation of end-of-line in XML. + */ + public static final String NORMAL_END_OF_LINE = "\n"; + + /** + * Set the characters that introduce a new line. The default is {@link #NORMAL_END_OF_LINE}. + * {@link IndentingXMLStreamWriter#getLineSeparator}() is a popular alternative. + */ + public void setNewLine(String newLine); + + /** The characters that introduce a new line. */ + String getNewLine(); + +} diff --git a/ch.eitchnet.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/ch.eitchnet.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java new file mode 100644 index 000000000..f4af62c62 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2006, John Kristian + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of StAX-Utils nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package javanet.staxutils; + +import javanet.staxutils.helpers.StreamWriterDelegate; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * A filter that indents an XML stream. To apply it, construct a filter that contains another {@link XMLStreamWriter}, + * which you pass to the constructor. Then call methods of the filter instead of the contained stream. For example: + * + *
+ * {@link XMLStreamWriter} stream = ...
+ * stream = new {@link IndentingXMLStreamWriter}(stream);
+ * stream.writeStartDocument();
+ * ...
+ * 
+ * + *

+ * The filter inserts characters to format the document as an outline, with nested elements indented. Basically, it + * inserts a line break and whitespace before: + *

    + *
  • each DTD, processing instruction or comment that's not preceded by data
  • + *
  • each starting tag that's not preceded by data
  • + *
  • each ending tag that's preceded by nested elements but not data
  • + *
+ * This works well with 'data-oriented' XML, wherein each element contains either data or nested elements but not both. + * It can work badly with other styles of XML. For example, the data in a 'mixed content' document are apt to be + * polluted with indentation characters. + *

+ * Indentation can be adjusted by setting the newLine and indent properties. But set them to whitespace only, for best + * results. Non-whitespace is apt to cause problems, for example when this class attempts to insert newLine before the + * root element. + * + * @author John Kristian + */ +@SuppressWarnings("nls") +public class IndentingXMLStreamWriter extends StreamWriterDelegate implements Indentation { + + public IndentingXMLStreamWriter(XMLStreamWriter out) { + this(out, DEFAULT_INDENT, NORMAL_END_OF_LINE); + } + + public IndentingXMLStreamWriter(XMLStreamWriter out, String indent) { + this(out, indent, NORMAL_END_OF_LINE); + } + + public IndentingXMLStreamWriter(XMLStreamWriter out, String indent, String newLine) { + super(out); + setIndent(indent); + setNewLine(newLine); + } + + /** How deeply nested the current scope is. The root element is depth 1. */ + private int depth = 0; // document scope + + /** stack[depth] indicates what's been written into the current scope. */ + private int[] stack = new int[] { 0, 0, 0, 0 }; // nothing written yet + + private static final int WROTE_MARKUP = 1; + + private static final int WROTE_DATA = 2; + + private String indent = DEFAULT_INDENT; + + private String newLine = NORMAL_END_OF_LINE; + + /** newLine followed by copies of indent. */ + private char[] linePrefix = null; + + @Override + public void setIndent(String indent) { + if (!indent.equals(this.indent)) { + this.indent = indent; + this.linePrefix = null; + } + } + + @Override + public String getIndent() { + return this.indent; + } + + @Override + public void setNewLine(String newLine) { + if (!newLine.equals(this.newLine)) { + this.newLine = newLine; + this.linePrefix = null; + } + } + + /** + * @return System.getProperty("line.separator"); or {@link #NORMAL_END_OF_LINE} if that fails. + */ + public static String getLineSeparator() { + try { + return System.getProperty("line.separator"); + } catch (SecurityException ignored) { + // + } + return NORMAL_END_OF_LINE; + } + + @Override + public String getNewLine() { + return this.newLine; + } + + @Override + public void writeStartDocument() throws XMLStreamException { + beforeMarkup(); + this.out.writeStartDocument(); + afterMarkup(); + } + + @Override + public void writeStartDocument(String version) throws XMLStreamException { + beforeMarkup(); + this.out.writeStartDocument(version); + afterMarkup(); + } + + @Override + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + beforeMarkup(); + this.out.writeStartDocument(encoding, version); + afterMarkup(); + } + + @Override + public void writeDTD(String dtd) throws XMLStreamException { + beforeMarkup(); + this.out.writeDTD(dtd); + afterMarkup(); + } + + @Override + public void writeProcessingInstruction(String target) throws XMLStreamException { + beforeMarkup(); + this.out.writeProcessingInstruction(target); + afterMarkup(); + } + + @Override + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + beforeMarkup(); + this.out.writeProcessingInstruction(target, data); + afterMarkup(); + } + + @Override + public void writeComment(String data) throws XMLStreamException { + beforeMarkup(); + this.out.writeComment(data); + afterMarkup(); + } + + @Override + public void writeEmptyElement(String localName) throws XMLStreamException { + beforeMarkup(); + this.out.writeEmptyElement(localName); + afterMarkup(); + } + + @Override + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + beforeMarkup(); + this.out.writeEmptyElement(namespaceURI, localName); + afterMarkup(); + } + + @Override + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + beforeMarkup(); + this.out.writeEmptyElement(prefix, localName, namespaceURI); + afterMarkup(); + } + + @Override + public void writeStartElement(String localName) throws XMLStreamException { + beforeStartElement(); + this.out.writeStartElement(localName); + afterStartElement(); + } + + @Override + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + beforeStartElement(); + this.out.writeStartElement(namespaceURI, localName); + afterStartElement(); + } + + @Override + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + beforeStartElement(); + this.out.writeStartElement(prefix, localName, namespaceURI); + afterStartElement(); + } + + @Override + public void writeCharacters(String text) throws XMLStreamException { + this.out.writeCharacters(text); + afterData(); + } + + @Override + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + this.out.writeCharacters(text, start, len); + afterData(); + } + + @Override + public void writeCData(String data) throws XMLStreamException { + this.out.writeCData(data); + afterData(); + } + + @Override + public void writeEntityRef(String name) throws XMLStreamException { + this.out.writeEntityRef(name); + afterData(); + } + + @Override + public void writeEndElement() throws XMLStreamException { + beforeEndElement(); + this.out.writeEndElement(); + afterEndElement(); + } + + @Override + public void writeEndDocument() throws XMLStreamException { + try { + while (this.depth > 0) { + writeEndElement(); // indented + } + } catch (Exception ignored) { + ignored.printStackTrace(); + } + this.out.writeEndDocument(); + afterEndDocument(); + } + + /** Prepare to write markup, by writing a new line and indentation. */ + protected void beforeMarkup() { + int soFar = this.stack[this.depth]; + if ((soFar & WROTE_DATA) == 0 // no data in this scope + && (this.depth > 0 || soFar != 0)) // not the first line + { + try { + writeNewLine(this.depth); + if (this.depth > 0 && getIndent().length() > 0) { + afterMarkup(); // indentation was written + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** Note that markup or indentation was written. */ + protected void afterMarkup() { + this.stack[this.depth] |= WROTE_MARKUP; + } + + /** Note that data were written. */ + protected void afterData() { + this.stack[this.depth] |= WROTE_DATA; + } + + /** Prepare to start an element, by allocating stack space. */ + protected void beforeStartElement() { + beforeMarkup(); + if (this.stack.length <= this.depth + 1) { + // Allocate more space for the stack: + int[] newStack = new int[this.stack.length * 2]; + System.arraycopy(this.stack, 0, newStack, 0, this.stack.length); + this.stack = newStack; + } + this.stack[this.depth + 1] = 0; // nothing written yet + } + + /** Note that an element was started. */ + protected void afterStartElement() { + afterMarkup(); + ++this.depth; + } + + /** Prepare to end an element, by writing a new line and indentation. */ + protected void beforeEndElement() { + if (this.depth > 0 && this.stack[this.depth] == WROTE_MARKUP) { // but not data + try { + writeNewLine(this.depth - 1); + } catch (Exception ignored) { + ignored.printStackTrace(); + } + } + } + + /** Note that an element was ended. */ + protected void afterEndElement() { + if (this.depth > 0) { + --this.depth; + } + } + + /** Note that a document was ended. */ + protected void afterEndDocument() { + if (this.stack[this.depth = 0] == WROTE_MARKUP) { // but not data + try { + writeNewLine(0); + } catch (Exception ignored) { + ignored.printStackTrace(); + } + } + this.stack[this.depth] = 0; // start fresh + } + + /** Write a line separator followed by indentation. */ + protected void writeNewLine(int indentation) throws XMLStreamException { + final int newLineLength = getNewLine().length(); + final int prefixLength = newLineLength + (getIndent().length() * indentation); + if (prefixLength > 0) { + if (this.linePrefix == null) { + this.linePrefix = (getNewLine() + getIndent()).toCharArray(); + } + while (prefixLength > this.linePrefix.length) { + // make linePrefix longer: + char[] newPrefix = new char[newLineLength + ((this.linePrefix.length - newLineLength) * 2)]; + System.arraycopy(this.linePrefix, 0, newPrefix, 0, this.linePrefix.length); + System.arraycopy(this.linePrefix, newLineLength, newPrefix, this.linePrefix.length, + this.linePrefix.length - newLineLength); + this.linePrefix = newPrefix; + } + this.out.writeCharacters(this.linePrefix, 0, prefixLength); + } + } + +} diff --git a/ch.eitchnet.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java b/ch.eitchnet.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java new file mode 100644 index 000000000..9922aafa7 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2006, John Kristian + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of StAX-Utils nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package javanet.staxutils.helpers; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * Abstract class for writing filtered XML streams. This class provides methods that merely delegate to the contained + * stream. Subclasses should override some of these methods, and may also provide additional methods and fields. + * + * @author John Kristian + */ +public abstract class StreamWriterDelegate implements XMLStreamWriter { + + protected StreamWriterDelegate(XMLStreamWriter out) { + this.out = out; + } + + protected XMLStreamWriter out; + + @Override + public Object getProperty(String name) throws IllegalArgumentException { + return this.out.getProperty(name); + } + + @Override + public NamespaceContext getNamespaceContext() { + return this.out.getNamespaceContext(); + } + + @Override + public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { + this.out.setNamespaceContext(context); + } + + @Override + public void setDefaultNamespace(String uri) throws XMLStreamException { + this.out.setDefaultNamespace(uri); + } + + @Override + public void writeStartDocument() throws XMLStreamException { + this.out.writeStartDocument(); + } + + @Override + public void writeStartDocument(String version) throws XMLStreamException { + this.out.writeStartDocument(version); + } + + @Override + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + this.out.writeStartDocument(encoding, version); + } + + @Override + public void writeDTD(String dtd) throws XMLStreamException { + this.out.writeDTD(dtd); + } + + @Override + public void writeProcessingInstruction(String target) throws XMLStreamException { + this.out.writeProcessingInstruction(target); + } + + @Override + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + this.out.writeProcessingInstruction(target, data); + } + + @Override + public void writeComment(String data) throws XMLStreamException { + this.out.writeComment(data); + } + + @Override + public void writeEmptyElement(String localName) throws XMLStreamException { + this.out.writeEmptyElement(localName); + } + + @Override + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + this.out.writeEmptyElement(namespaceURI, localName); + } + + @Override + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + this.out.writeEmptyElement(prefix, localName, namespaceURI); + } + + @Override + public void writeStartElement(String localName) throws XMLStreamException { + this.out.writeStartElement(localName); + } + + @Override + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + this.out.writeStartElement(namespaceURI, localName); + } + + @Override + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + this.out.writeStartElement(prefix, localName, namespaceURI); + } + + @Override + public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { + this.out.writeDefaultNamespace(namespaceURI); + } + + @Override + public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { + this.out.writeNamespace(prefix, namespaceURI); + } + + @Override + public String getPrefix(String uri) throws XMLStreamException { + return this.out.getPrefix(uri); + } + + @Override + public void setPrefix(String prefix, String uri) throws XMLStreamException { + this.out.setPrefix(prefix, uri); + } + + @Override + public void writeAttribute(String localName, String value) throws XMLStreamException { + this.out.writeAttribute(localName, value); + } + + @Override + public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { + this.out.writeAttribute(namespaceURI, localName, value); + } + + @Override + public void writeAttribute(String prefix, String namespaceURI, String localName, String value) + throws XMLStreamException { + this.out.writeAttribute(prefix, namespaceURI, localName, value); + } + + @Override + public void writeCharacters(String text) throws XMLStreamException { + this.out.writeCharacters(text); + } + + @Override + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + this.out.writeCharacters(text, start, len); + } + + @Override + public void writeCData(String data) throws XMLStreamException { + this.out.writeCData(data); + } + + @Override + public void writeEntityRef(String name) throws XMLStreamException { + this.out.writeEntityRef(name); + } + + @Override + public void writeEndElement() throws XMLStreamException { + this.out.writeEndElement(); + } + + @Override + public void writeEndDocument() throws XMLStreamException { + this.out.writeEndDocument(); + } + + @Override + public void flush() throws XMLStreamException { + this.out.flush(); + } + + @Override + public void close() throws XMLStreamException { + this.out.close(); + } + +} diff --git a/ch.eitchnet.utils/src/main/java/log4j.xml b/ch.eitchnet.utils/src/main/java/log4j.xml new file mode 100644 index 000000000..0a2a73d06 --- /dev/null +++ b/ch.eitchnet.utils/src/main/java/log4j.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java new file mode 100644 index 000000000..ec25837e9 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Copyright 2014 Robert von Burg + * + * 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. + */ +public class AbstractEndpointTest { + + static final Logger logger = LoggerFactory.getLogger(FileEndpointTest.class); + + public static TestIoMessage createTestMessage(String key1, String key2, String connectionId) { + return createTestMessage(CommandKey.key(key1, key2), connectionId); + } + + @SuppressWarnings("nls") + public static TestIoMessage createTestMessage(CommandKey key, String connectionId) { + TestIoMessage msg = new TestIoMessage(UUID.randomUUID().toString(), key, connectionId); + List lines = new ArrayList<>(); + lines.add("bla"); + lines.add("foo"); + lines.add("bar"); + lines.add("bla"); + msg.setContents(lines); + return msg; + } + + protected void waitForMessage(TestConnectionObserver observer) throws InterruptedException { + long start = System.currentTimeMillis(); + while (observer.getMessage() == null) { + if (System.currentTimeMillis() - start > 2000) + fail("Connection didn't send message in 2s!"); //$NON-NLS-1$ + Thread.sleep(50); + } + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java new file mode 100644 index 000000000..c7090924f --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; + +import ch.eitchnet.communication.console.ConsoleEndpoint; +import ch.eitchnet.communication.console.ConsoleMessageVisitor; + +/** + * @author Robert von Burg + */ +public class ConsoleEndpointTest extends AbstractEndpointTest { + + private static final String CONNECTION_ID = "Console"; //$NON-NLS-1$ + private CommunicationConnection connection; + + @Before + public void before() { + + Map parameters = new HashMap<>(); + CommunicationEndpoint endpoint = new ConsoleEndpoint(); + ConsoleMessageVisitor messageVisitor = new ConsoleMessageVisitorExtension(); + this.connection = new CommunicationConnection(CONNECTION_ID, ConnectionMode.ON, parameters, endpoint, + messageVisitor); + this.connection.configure(); + } + + @Test + public void testConsoleEndpoint() throws InterruptedException { + + this.connection.start(); + + CommandKey key = CommandKey.key(CONNECTION_ID, "logger"); //$NON-NLS-1$ + TestIoMessage msg = createTestMessage(key, CONNECTION_ID); + + TestConnectionObserver observer = new TestConnectionObserver(); + this.connection.addConnectionObserver(key, observer); + this.connection.send(msg); + waitForMessage(observer); + + assertEquals(msg.getKey(), observer.getMessage().getKey()); + + } + + private final class ConsoleMessageVisitorExtension extends ConsoleMessageVisitor { + public ConsoleMessageVisitorExtension() { + // no-op + } + + @Override + public void visit(Logger logger, IoMessage message) throws Exception { + TestIoMessage msg = (TestIoMessage) message; + for (String line : msg.getContents()) { + logger.info(line); + } + } + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java new file mode 100644 index 000000000..9ae899d8c --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import static org.junit.Assert.assertEquals; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.eitchnet.communication.file.FileEndpoint; +import ch.eitchnet.communication.file.FileEndpointMode; +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +public class FileEndpointTest extends AbstractEndpointTest { + + public static final String INBOUND_FILENAME = "target/test_in.txt"; //$NON-NLS-1$ + public static final String OUTBOUND_FILENAME = "target/test_out.txt"; //$NON-NLS-1$ + public static final String CONNECTION_ID = "FileTestEndpoint"; //$NON-NLS-1$ + + private CommunicationConnection connection; + + @Before + public void before() { + + new File(OUTBOUND_FILENAME).delete(); + new File(INBOUND_FILENAME).delete(); + + Map parameters = new HashMap<>(); + parameters.put(FileEndpoint.ENDPOINT_MODE, FileEndpointMode.READ_WRITE.name()); + parameters.put(FileEndpoint.INBOUND_FILENAME, INBOUND_FILENAME); + parameters.put(FileEndpoint.OUTBOUND_FILENAME, OUTBOUND_FILENAME); + + ConnectionMode mode = ConnectionMode.ON; + CommunicationEndpoint endpoint = new FileEndpoint(); + StreamMessageVisitor messageVisitor = new StreamMessageVisitorExtension(); + + this.connection = new CommunicationConnection(CONNECTION_ID, mode, parameters, endpoint, messageVisitor); + this.connection.configure(); + } + + @After + public void after() { + if (this.connection != null) + this.connection.stop(); + } + + @Test + public void testFileEndpoint() throws InterruptedException { + + String inboundFilename = new File(INBOUND_FILENAME).getName(); + String outboundFilename = new File(OUTBOUND_FILENAME).getName(); + + // send a message + this.connection.start(); + TestConnectionObserver outboundObserver = new TestConnectionObserver(); + TestIoMessage message = createTestMessage(outboundFilename, FileEndpointMode.WRITE.name(), CONNECTION_ID); + this.connection.addConnectionObserver(message.getKey(), outboundObserver); + this.connection.send(message); + + // wait till the message has been sent + waitForMessage(outboundObserver); + this.connection.stop(); + assertEquals(message.getKey(), outboundObserver.getMessage().getKey()); + + // now test reading a file + this.connection.start(); + CommandKey inboundKey = CommandKey.key(inboundFilename, FileEndpointMode.READ.name()); + TestConnectionObserver inboundObserver = new TestConnectionObserver(); + this.connection.addConnectionObserver(inboundKey, inboundObserver); + FileHelper.writeStringToFile("Hello\nWorld!", new File(INBOUND_FILENAME)); //$NON-NLS-1$ + + // wait for thread to pick up the file + waitForMessage(inboundObserver); + assertEquals(inboundKey, inboundObserver.getMessage().getKey()); + } + + public static final class StreamMessageVisitorExtension extends StreamMessageVisitor { + private String inboundFilename; + + @Override + public void configure(CommunicationConnection connection) { + super.configure(connection); + Map parameters = connection.getParameters(); + String filePath = parameters.get(FileEndpoint.INBOUND_FILENAME); + this.inboundFilename = new File(filePath).getName(); + } + + @Override + public IoMessage visit(InputStream inputStream) throws Exception { + + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + List lines = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + return new TestIoMessage(UUID.randomUUID().toString(), CommandKey.key(this.inboundFilename, + FileEndpointMode.READ.name()), CONNECTION_ID, lines); + } + + @Override + public void visit(OutputStream outputStream, IoMessage message) throws Exception { + TestIoMessage msg = (TestIoMessage) message; + for (String line : msg.getContents()) { + outputStream.write(line.getBytes()); + outputStream.write('\n'); + } + } + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java new file mode 100644 index 000000000..483041519 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.Test; + +/** + * @author Robert von Burg + */ +public class SimpleMessageArchiveTest extends AbstractEndpointTest { + + @Test + public void testArchive() throws InterruptedException { + + IoMessageArchive archive = new SimpleMessageArchive(20, 5); + + CommandKey key = CommandKey.key("key1", "key2"); //$NON-NLS-1$//$NON-NLS-2$ + String connectionId = "connection1"; //$NON-NLS-1$ + + int i = 0; + for (; i < 20; i++) { + TestIoMessage msg = new TestIoMessage(UUID.randomUUID().toString(), key, connectionId); + // update the time by plus 1, otherwise the tree set does not add it + msg.setUpdated(new Date(i + 1)); + archive.archive(msg); + } + + assertEquals(20, archive.size()); + + // add one more + TestIoMessage msg = new TestIoMessage(UUID.randomUUID().toString(), key, connectionId); + msg.setUpdated(new Date(i + 1)); + archive.archive(msg); + + // validate the trimming works + assertEquals(15, archive.size()); + + // Now make sure our last element is still in the list + List all = archive.getAll(); + Collections.sort(all, new Comparator() { + @Override + public int compare(IoMessage o1, IoMessage o2) { + return o1.getUpdated().compareTo(o2.getUpdated()); + } + }); + IoMessage message = all.get(all.size() - 1); + assertEquals(msg.getId(), message.getId()); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java new file mode 100644 index 000000000..d2db3c0b5 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import static org.junit.Assert.assertEquals; + +import java.io.BufferedReader; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.InputStreamReader; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.eitchnet.communication.tcpip.ClientSocketEndpoint; +import ch.eitchnet.communication.tcpip.ServerSocketEndpoint; +import ch.eitchnet.communication.tcpip.SocketEndpointConstants; +import ch.eitchnet.communication.tcpip.SocketMessageVisitor; + +/** + * @author Robert von Burg + */ +public class SocketEndpointTest extends AbstractEndpointTest { + + private static final String PORT = "45678"; //$NON-NLS-1$ + private static final String HOST = "localhost"; //$NON-NLS-1$ + private static final String CLIENT_CONNECTION_ID = "ClientSocket"; //$NON-NLS-1$ + private static final String SERVER_CONNECTION_ID = "ServerSocket"; //$NON-NLS-1$ + private CommunicationConnection clientConnection; + private CommunicationConnection serverConnection; + + @Before + public void before() { + + { + Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_ADDRESS, HOST); + parameters.put(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_PORT, PORT); + + // we close after send, so that the server can read whole lines, as that is what we are sending + parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.TRUE.toString()); + + CommunicationEndpoint endpoint = new ClientSocketEndpoint(); + SocketMessageVisitor messageVisitor = new SocketMessageVisitorExtension(CLIENT_CONNECTION_ID); + this.clientConnection = new CommunicationConnection(CLIENT_CONNECTION_ID, ConnectionMode.ON, parameters, + endpoint, messageVisitor); + this.clientConnection.configure(); + } + + { + Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_ADDRESS, HOST); + parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT, PORT); + + CommunicationEndpoint endpoint = new ServerSocketEndpoint(); + SocketMessageVisitor messageVisitor = new SocketMessageVisitorExtension(SERVER_CONNECTION_ID); + this.serverConnection = new CommunicationConnection(SERVER_CONNECTION_ID, ConnectionMode.ON, parameters, + endpoint, messageVisitor); + this.serverConnection.configure(); + } + } + + @After + public void after() { + if (this.clientConnection != null) + this.clientConnection.stop(); + if (this.serverConnection != null) + this.serverConnection.stop(); + } + + @Test + public void testSocketEndpoints() throws Exception { + + this.serverConnection.start(); + Thread.sleep(100); + this.clientConnection.start(); + + TestConnectionObserver serverObserver = new TestConnectionObserver(); + CommandKey inboundKey = CommandKey.key(SERVER_CONNECTION_ID, "lines"); //$NON-NLS-1$ + this.serverConnection.addConnectionObserver(inboundKey, serverObserver); + + TestConnectionObserver clientObserver = new TestConnectionObserver(); + CommandKey outboundKey = CommandKey.key(CLIENT_CONNECTION_ID, "lines"); //$NON-NLS-1$ + this.clientConnection.addConnectionObserver(outboundKey, clientObserver); + + TestIoMessage outboundMsg = createTestMessage(outboundKey, CLIENT_CONNECTION_ID); + this.clientConnection.send(outboundMsg); + waitForMessage(clientObserver); + assertEquals(outboundMsg.getKey(), clientObserver.getMessage().getKey()); + + waitForMessage(serverObserver); + assertEquals(inboundKey, serverObserver.getMessage().getKey()); + assertEquals(outboundMsg.getContents(), ((TestIoMessage) serverObserver.getMessage()).getContents()); + } + + private final class SocketMessageVisitorExtension extends SocketMessageVisitor { + + public SocketMessageVisitorExtension(String connectionId) { + super(connectionId); + } + + @Override + public void visit(DataInputStream inputStream, DataOutputStream outputStream, IoMessage message) + throws Exception { + TestIoMessage msg = (TestIoMessage) message; + logger.info(MessageFormat + .format("Writing {0} lines for message {1}", msg.getContents().size(), msg.getId())); //$NON-NLS-1$ + for (String line : msg.getContents()) { + outputStream.writeBytes(line); + outputStream.write('\n'); + } + outputStream.flush(); + } + + @Override + public IoMessage visit(DataInputStream inputStream, DataOutputStream outputStream) throws Exception { + + List lines = new ArrayList<>(); + + // since we are reading whole lines, we must close the stream when we read null i.e. EOF + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + logger.info("Reading from stream..."); //$NON-NLS-1$ + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } + logger.info(MessageFormat.format("Read {0} lines from stream.", lines.size())); //$NON-NLS-1$ + + return new TestIoMessage(UUID.randomUUID().toString(), + CommandKey.key(SERVER_CONNECTION_ID, "lines"), SERVER_CONNECTION_ID, lines); //$NON-NLS-1$ + } + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java new file mode 100644 index 000000000..36db42fc5 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + */ +public class TestConnectionObserver implements ConnectionObserver { + + private static final Logger logger = LoggerFactory.getLogger(FileEndpointTest.class); + + private IoMessage message; + + public IoMessage getMessage() { + return this.message; + } + + @Override + public void notify(CommandKey key, IoMessage message) { + this.message = message; + logger.info(MessageFormat.format("Received message with key {0} and message {1}", key, message)); //$NON-NLS-1$ + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java new file mode 100644 index 000000000..c4d473d08 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.communication; + +import java.util.List; + +/** + * @author Robert von Burg + */ +public class TestIoMessage extends IoMessage { + + private List contents; + + public TestIoMessage(String id, CommandKey key, String connectionId) { + super(id, key, connectionId); + } + + public TestIoMessage(String id, CommandKey key, String connectionId, List contents) { + super(id, key, connectionId); + this.contents = contents; + } + + public List getContents() { + return this.contents; + } + + public void setContents(List contents) { + this.contents = contents; + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java new file mode 100644 index 000000000..c387ac305 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * @author Robert von Burg + * + */ +public class StringMatchModeTest { + + /** + * Test method for {@link ch.eitchnet.utils.StringMatchMode#isCaseSensitve()}. + */ + @Test + public void testIsCaseSensitve() { + assertFalse(StringMatchMode.EQUALS_CASE_INSENSITIVE.isCaseSensitve()); + assertTrue(StringMatchMode.EQUALS_CASE_SENSITIVE.isCaseSensitve()); + assertFalse(StringMatchMode.CONTAINS_CASE_INSENSITIVE.isCaseSensitve()); + assertTrue(StringMatchMode.CONTAINS_CASE_SENSITIVE.isCaseSensitve()); + } + + /** + * Test method for {@link ch.eitchnet.utils.StringMatchMode#isEquals()}. + */ + @Test + public void testIsEquals() { + assertTrue(StringMatchMode.EQUALS_CASE_INSENSITIVE.isEquals()); + assertTrue(StringMatchMode.EQUALS_CASE_SENSITIVE.isEquals()); + assertFalse(StringMatchMode.CONTAINS_CASE_INSENSITIVE.isEquals()); + assertFalse(StringMatchMode.CONTAINS_CASE_SENSITIVE.isEquals()); + } + + /** + * Test method for {@link ch.eitchnet.utils.StringMatchMode#matches(java.lang.String, java.lang.String)}. + */ + @SuppressWarnings("nls") + @Test + public void testMatches() { + assertTrue(StringMatchMode.CONTAINS_CASE_INSENSITIVE.matches("hello", "el")); + assertTrue(StringMatchMode.CONTAINS_CASE_SENSITIVE.matches("hello", "el")); + assertFalse(StringMatchMode.CONTAINS_CASE_SENSITIVE.matches("hello", "ael")); + assertFalse(StringMatchMode.CONTAINS_CASE_SENSITIVE.matches("hello", "EL")); + assertFalse(StringMatchMode.CONTAINS_CASE_SENSITIVE.matches("hello", "aEL")); + assertTrue(StringMatchMode.CONTAINS_CASE_INSENSITIVE.matches("hello", "EL")); + + assertTrue(StringMatchMode.EQUALS_CASE_SENSITIVE.matches("ab", "ab")); + assertFalse(StringMatchMode.EQUALS_CASE_SENSITIVE.matches("ab", "abc")); + assertTrue(StringMatchMode.EQUALS_CASE_INSENSITIVE.matches("ab", "ab")); + assertTrue(StringMatchMode.EQUALS_CASE_INSENSITIVE.matches("ab", "AB")); + assertTrue(StringMatchMode.EQUALS_CASE_INSENSITIVE.matches("ab", "aB")); + + assertFalse(StringMatchMode.EQUALS_CASE_SENSITIVE.matches("ab", "aB")); + assertFalse(StringMatchMode.EQUALS_CASE_SENSITIVE.matches("ab", "aB")); + assertFalse(StringMatchMode.EQUALS_CASE_SENSITIVE.matches("ab", "AB")); + assertFalse(StringMatchMode.EQUALS_CASE_SENSITIVE.matches("ab", "aba")); + assertTrue(StringMatchMode.EQUALS_CASE_SENSITIVE.matches("ab", "ab")); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/VersionTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/VersionTest.java new file mode 100644 index 000000000..5381a6f8f --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/VersionTest.java @@ -0,0 +1,107 @@ +package ch.eitchnet.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * Tests the {@link Version} class + */ +public class VersionTest { + + @Test + public void shouldParseMajoMinoMicro() { + Version version = Version.valueOf("1.0.2"); + assertEquals(1, version.getMajor()); + assertEquals(0, version.getMinor()); + assertEquals(2, version.getMicro()); + assertEquals(StringHelper.EMPTY, version.getQualifier()); + assertEquals(StringHelper.EMPTY, version.getQualifier()); + + assertEquals("1.0.2", version.toString()); + } + + @Test + public void shouldParseVersion() { + { + Version version = Version.valueOf("7.5.6.1"); + assertEquals(7, version.getMajor()); + assertEquals(5, version.getMinor()); + assertEquals(6, version.getMicro()); + assertEquals("1", version.getQualifier()); + assertTrue(version.isOsgiStyle()); + } + { + Version version = Version.valueOf("7.5.6-1"); + assertEquals(7, version.getMajor()); + assertEquals(5, version.getMinor()); + assertEquals(6, version.getMicro()); + assertEquals("1", version.getQualifier()); + assertFalse(version.isOsgiStyle()); + } + } + + @Test + public void shouldCompareVersions() { + assertEquals(0, Version.valueOf("7.5.6-1").compareTo(Version.valueOf("7.5.6-1"))); + assertTrue(Version.valueOf("7.5.6-1").compareTo(Version.valueOf("7.5.6-2")) < 0); + assertTrue(Version.valueOf("7.5.6-1").compareTo(Version.valueOf("7.6.0")) < 0); + assertTrue(Version.valueOf("7.5.6-1").compareTo(Version.valueOf("7.6.1")) < 0); + assertTrue(Version.valueOf("7.5.6-alpha").compareTo(Version.valueOf("7.6.1-beta")) < 0); + assertTrue(Version.valueOf("7.7.0-0").compareTo(Version.valueOf("7.6.99-9")) > 0); + assertTrue(Version.valueOf("0.0.1.a").compareTo(Version.valueOf("0.0.1.b")) < 0); + assertTrue(Version.valueOf("0.0.1.b").compareTo(Version.valueOf("0.0.1.a")) > 0); + assertTrue(Version.valueOf("0.0.1.a").compareTo(Version.valueOf("0.0.1.c")) < 0); + assertTrue(Version.valueOf("0.0.1.a").compareTo(Version.valueOf("0.0.1.aa")) < 0); + } + + @Test + public void shouldConvertToMajorMinorString() { + assertEquals("7.6", Version.valueOf("7.6.1-0").toMajorAndMinorString()); + } + + @Test + public void shouldKnowAboutBeingFullyQualified() { + assertFalse(Version.valueOf("7").isFullyQualified()); + assertFalse(Version.valueOf("7.6").isFullyQualified()); + assertFalse(Version.valueOf("7.6.1").isFullyQualified()); + assertTrue(Version.valueOf("7.6.1-0").isFullyQualified()); + } + + @Test + public void shouldDealWithEclipseStyleSnapshotQualifier() { + assertEquals(Version.valueOf("7.6.1-SNAPSHOT").toOsgiStyleString(), "7.6.1.qualifier"); + assertEquals(Version.valueOf("7.6.1-SNAPSHOT").toMavenStyleString(), "7.6.1-SNAPSHOT"); + assertEquals(Version.valueOf("7.6.1.qualifier").toOsgiStyleString(), "7.6.1.qualifier"); + assertEquals(Version.valueOf("7.6.1.qualifier").toMavenStyleString(), "7.6.1-SNAPSHOT"); + } + + @Test + public void shouldIncreaseVersion() { + + Version increased = Version.emptyVersion.add(0, 0, 0); + assertEquals("0.0.0", increased.toString()); + + increased = increased.add(0, 0, 1); + assertEquals("0.0.1", increased.toString()); + + increased = increased.add(0, 1, 0); + assertEquals("0.1.1", increased.toString()); + + increased = increased.add(1, 0, 0); + assertEquals("1.1.1", increased.toString()); + + increased = increased.add(-1, 0, 0); + assertEquals("0.1.1", increased.toString()); + + increased = increased.add(0, -1, 0); + assertEquals("0.0.1", increased.toString()); + + increased = increased.add(0, 0, -1); + assertEquals("0.0.0", increased.toString()); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java new file mode 100644 index 000000000..5ec0bbc75 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Date; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import ch.eitchnet.utils.dbc.DBC.DbcException; + +public class DateRangeTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void testFrom() { + Date now = new Date(); + DateRange dateRange = new DateRange(); + dateRange.from(now, true); + assertEquals(now, dateRange.getFromDate()); + assertNull(dateRange.getToDate()); + assertFalse(dateRange.isUnbounded()); + assertFalse(dateRange.isBounded()); + } + + /** + * Test method for {@link ch.eitchnet.utils.collections.DateRange#to(java.util.Date, boolean)}. + */ + @Test + public void testTo() { + Date now = new Date(); + DateRange dateRange = new DateRange(); + dateRange.to(now, true); + assertEquals(now, dateRange.getToDate()); + assertNull(dateRange.getFromDate()); + assertFalse(dateRange.isUnbounded()); + assertFalse(dateRange.isBounded()); + } + + /** + * Test method for {@link ch.eitchnet.utils.collections.DateRange#to(java.util.Date,boolean)}. + */ + @Test + public void testFromTo() { + Date from = new Date(); + Date to = new Date(); + DateRange dateRange = new DateRange(); + dateRange.from(from, true).to(to, true); + assertEquals(from, dateRange.getFromDate()); + assertEquals(to, dateRange.getToDate()); + assertFalse(dateRange.isUnbounded()); + assertTrue(dateRange.isBounded()); + } + + @Test + public void shouldNotOverlap() { + this.exception.expect(DbcException.class); + Date from = new Date(10); + Date to = new Date(20); + DateRange dateRange = new DateRange(); + dateRange.from(to, true).to(from, true); + } + + /** + * Test method for {@link ch.eitchnet.utils.collections.DateRange#isDate()}. + */ + @Test + public void testIsDate() { + + Date from = new Date(10); + Date to = new Date(20); + DateRange dateRange = new DateRange(); + dateRange.from(from, false).to(to, false); + assertFalse(dateRange.isDate()); + + dateRange = new DateRange(); + dateRange.from(from, false).to(from, false); + assertTrue(dateRange.isDate()); + } + + /** + * Test method for {@link ch.eitchnet.utils.collections.DateRange#contains(java.util.Date)}. + */ + @Test + public void testContains() { + Date from = new Date(10); + Date to = new Date(20); + DateRange dateRange = new DateRange(); + dateRange.from(from, false).to(to, false); + + Date earlier = new Date(5); + Date later = new Date(25); + Date contained = new Date(15); + + assertFalse(dateRange.contains(earlier)); + assertFalse(dateRange.contains(later)); + assertTrue(dateRange.contains(contained)); + + assertFalse(dateRange.contains(from)); + assertFalse(dateRange.contains(to)); + + dateRange = new DateRange(); + dateRange.from(from, true).to(to, true); + assertTrue(dateRange.contains(from)); + assertTrue(dateRange.contains(to)); + + dateRange = new DateRange(); + dateRange.from(from, false).to(to, true); + assertFalse(dateRange.contains(from)); + assertTrue(dateRange.contains(to)); + + dateRange = new DateRange(); + dateRange.from(from, true).to(to, false); + assertTrue(dateRange.contains(from)); + assertFalse(dateRange.contains(to)); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java new file mode 100644 index 000000000..1048d10e1 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +/** + * @author Robert von Burg + */ +public class DefaultedHashMapTest { + + private Map map; + + @Before + public void setUp() { + this.map = new DefaultedHashMap<>("foobar"); + this.map.put("foo", "foofoo"); + } + + @Test + public void shouldReturnMappedValue() { + assertTrue(this.map.containsKey("foo")); + assertEquals("foofoo", this.map.get("foo")); + } + + @Test + public void shouldReturnDefaultValue() { + assertFalse(this.map.containsKey("bar")); + assertEquals("foobar", this.map.get("bar")); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java new file mode 100644 index 000000000..12e4a7036 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 Robert von Burg + * + * 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 ch.eitchnet.utils.collections; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +/** + * @author Robert von Burg + */ +public class PagingTest { + + @Test + public void shouldReturnAll() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, -1, -1).getPage(); + assertEquals(list, page); + } + + @Test + public void shouldReturnFirstPage1() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 1, 1).getPage(); + assertEquals(Arrays.asList("a"), page); + } + + @Test + public void shouldReturnFirstPage2() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 2, 1).getPage(); + assertEquals(Arrays.asList("a", "b"), page); + } + + @Test + public void shouldReturnSecondPage1() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 1, 2).getPage(); + assertEquals(Arrays.asList("b"), page); + } + + @Test + public void shouldReturnSecondPage2() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 2, 2).getPage(); + assertEquals(Arrays.asList("c", "d"), page); + } + + @Test + public void shouldReturnLastPage1() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 1, 6).getPage(); + assertEquals(Arrays.asList("f"), page); + } + + @Test + public void shouldReturnLastPage2() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 2, 3).getPage(); + assertEquals(Arrays.asList("e", "f"), page); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java new file mode 100644 index 000000000..7c18a3ab2 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java @@ -0,0 +1,494 @@ +/* + * Copyright 2014 Robert von Burg + * + * 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 ch.eitchnet.utils.dbc; + +import java.io.File; +import java.text.MessageFormat; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import ch.eitchnet.utils.dbc.DBC.DbcException; + +/** + * The class DBCTest contains tests for the class {@link DBC}. + * + * @generatedBy CodePro at 2/2/14 8:13 PM + * @author Robert von Burg + * @version $Revision: 1.0 $ + */ +@SuppressWarnings("nls") +public class DBCTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_1() throws Exception { + String msg = ""; + Object value1 = null; + Object value2 = null; + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_2() throws Exception { + this.exception.expect(DbcException.class); + Object value1 = new Object(); + Object value2 = new Object(); + String msg = MessageFormat.format("{0}: {1} != {2}", "", value1, value2); + this.exception.expectMessage(msg); + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + // An unexpected exception was thrown in user code while executing this test: + // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Values are not equal: + // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_3() throws Exception { + this.exception.expect(DbcException.class); + + Object value1 = null; + Object value2 = new Object(); + + String msg = MessageFormat.format("{0}: {1} != {2}", "", value1, value2); + this.exception.expectMessage(msg); + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_4() throws Exception { + this.exception.expect(DbcException.class); + + Object value1 = new Object(); + Object value2 = null; + + String msg = MessageFormat.format("{0}: {1} != {2}", "", value1, value2); + this.exception.expectMessage(msg); + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_5() throws Exception { + String msg = ""; + Object value1 = "bla"; + Object value2 = "bla"; + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + // An unexpected exception was thrown in user code while executing this test: + // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Values are not equal: + // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_1() throws Exception { + this.exception.expect(DbcException.class); + + String msg = ""; + Object value1 = null; + Object value2 = null; + + String ex = "{0}: {1} == {2}"; + ex = MessageFormat.format(ex, msg, value1, value2); + this.exception.expectMessage(ex); + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_2() throws Exception { + String msg = ""; + Object value1 = new Object(); + Object value2 = new Object(); + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_3() throws Exception { + String msg = ""; + Object value1 = null; + Object value2 = new Object(); + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_4() throws Exception { + String msg = ""; + Object value1 = new Object(); + Object value2 = null; + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_5() throws Exception { + this.exception.expect(DbcException.class); + + String msg = ""; + Object value1 = "bla"; + Object value2 = "bla"; + + String ex = "{0}: {1} == {2}"; + ex = MessageFormat.format(ex, msg, value1, value2); + this.exception.expectMessage(ex); + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertExists(String,File) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertExists_1() throws Exception { + String msg = ""; + File file = new File("src"); + + DBC.PRE.assertExists(msg, file); + } + + /** + * Run the void assertExists(String,File) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertExists_2() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Illegal situation as file (srcc) does not exist:"); + + String msg = ""; + File file = new File("srcc"); + + DBC.PRE.assertExists(msg, file); + + // add additional test code here + // An unexpected exception was thrown in user code while executing this test: + // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Illegal situation as file () does not exist: + // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertExists(DBC.PRE.java:95) + } + + /** + * Run the void assertFalse(String,boolean) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertFalse_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Expected false, but was true: "); + + String msg = ""; + boolean value = true; + + DBC.PRE.assertFalse(msg, value); + } + + /** + * Run the void assertFalse(String,boolean) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertFalse_2() throws Exception { + String msg = ""; + boolean value = false; + + DBC.PRE.assertFalse(msg, value); + + // add additional test code here + } + + /** + * Run the void assertNotEmpty(String,String) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEmpty_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Illegal empty value: "); + + String msg = "Illegal empty value: "; + String value = ""; + + DBC.PRE.assertNotEmpty(msg, value); + } + + /** + * Run the void assertNotEmpty(String,String) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEmpty_2() throws Exception { + String msg = ""; + String value = "a"; + + DBC.PRE.assertNotEmpty(msg, value); + } + + /** + * Run the void assertNotExists(String,File) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotExists_1() throws Exception { + String msg = ""; + File file = new File("srcc"); + + DBC.PRE.assertNotExists(msg, file); + + // add additional test code here + } + + /** + * Run the void assertNotExists(String,File) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotExists_2() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Illegal situation as file (src) exists: "); + + String msg = ""; + File file = new File("src"); + + DBC.PRE.assertNotExists(msg, file); + + // add additional test code here + } + + /** + * Run the void assertNotNull(String,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotNull_1() throws Exception { + this.exception.expect(DbcException.class); + + String msg = ""; + Object value = null; + + String ex = "{0}: Illegal null value"; + ex = MessageFormat.format(ex, msg, value); + this.exception.expectMessage(ex); + + DBC.PRE.assertNotNull(msg, value); + } + + /** + * Run the void assertNotNull(String,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotNull_2() throws Exception { + String msg = ""; + Object value = new Object(); + + DBC.PRE.assertNotNull(msg, value); + + // add additional test code here + } + + /** + * Run the void assertNull(String,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNull_1() throws Exception { + this.exception.expect(DbcException.class); + + Object value = new Object(); + + String msg = MessageFormat.format("{0}: {1} != null", "", value); + this.exception.expectMessage(msg); + + DBC.PRE.assertNull(msg, value); + } + + /** + * Run the void assertNull(String,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNull_2() throws Exception { + String msg = ""; + Object value = null; + + DBC.PRE.assertNull(msg, value); + + // add additional test code here + } + + /** + * Run the void assertTrue(String,boolean) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertTrue_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Expected true, but was false: "); + + String msg = ""; + boolean value = false; + + DBC.PRE.assertTrue(msg, value); + + // add additional test code here + // An unexpected exception was thrown in user code while executing this test: + // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Expected true, but was false: + // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertTrue(DBC.PRE.java:47) + } + + /** + * Run the void assertTrue(String,boolean) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertTrue_2() throws Exception { + String msg = ""; + boolean value = true; + + DBC.PRE.assertTrue(msg, value); + + // add additional test code here + } +} \ No newline at end of file diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java new file mode 100644 index 000000000..31b069146 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java @@ -0,0 +1,156 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AesCryptoHelperTest { + + private static final Logger logger = LoggerFactory.getLogger(AesCryptoHelperTest.class); + + private static final char[] password = "A2589309-17AE-4819-B9E4-E577CFA7778F".toCharArray(); + private static final byte[] salt; + + static { + try { + salt = "E68761B3-4E8E-4122-9B12-8B89E0AEB233".getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Test + public void shouldWrapStreams() throws Exception { + + try { + byte[] clearTextBytes = "Some text".getBytes(); + + // encrypt data + ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); + try (OutputStream outputStream = AesCryptoHelper.wrapEncrypt(password, salt, encryptedOut)) { + outputStream.write(clearTextBytes); + outputStream.flush(); + } + + // decrypt data + byte[] encryptedBytes = encryptedOut.toByteArray(); + ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes); + try (InputStream inputStream = AesCryptoHelper.wrapDecrypt(password, salt, encryptedIn)) { + + ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); + byte[] readBuffer = new byte[64]; + int read = 0; + while ((read = inputStream.read(readBuffer)) != -1) { + decryptedOut.write(readBuffer, 0, read); + } + + byte[] decryptedBytes = decryptedOut.toByteArray(); + assertArrayEquals(clearTextBytes, decryptedBytes); + } + + } catch (RuntimeException e) { + if (ExceptionHelper.getRootCause(e).getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; + } + } + + @Test + public void shouldEncryptBytes() { + try { + + byte[] clearTextBytes = "Some text".getBytes(); + + byte[] encryptedBytes = AesCryptoHelper.encrypt(password, salt, clearTextBytes); + byte[] decryptedBytes = AesCryptoHelper.decrypt(password, salt, encryptedBytes); + + assertArrayEquals(clearTextBytes, decryptedBytes); + + } catch (RuntimeException e) { + if (ExceptionHelper.getRootCause(e).getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; + } + } + + @Test + public void shouldEncryptShortFile() { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_short.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_short.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_short.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptMiddleFile() { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_middle.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_middle.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_middle.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptLongFile() { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_long.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_long.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_long.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptBinaryFile() { + + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_image.ico"; + // encrypted file + String encryptedFileS = "target/encrypted_image.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_image.ico"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + + } + + private static void testCrypto(String clearTextFileS, String encryptedFileS, String decryptedFileS) { + try { + + AesCryptoHelper.encrypt(password, salt, clearTextFileS, encryptedFileS); + AesCryptoHelper.decrypt(password, salt, encryptedFileS, decryptedFileS); + + String inputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(clearTextFileS))); + String doutputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(decryptedFileS))); + + assertEquals(inputSha256, doutputSha256); + + } catch (RuntimeException e) { + if (ExceptionHelper.getRootCause(e).getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; + } + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java new file mode 100644 index 000000000..a77e5ee5c --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase16; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32Dmedia; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32Hex; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase64; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class BaseDecodingTest { + public static final String PROP_RUN_PERF_TESTS = "ch.eitchnet.utils.test.runPerfTests"; //$NON-NLS-1$ + private static final Logger logger = LoggerFactory.getLogger(BaseDecodingTest.class); + + public static boolean isSkipPerfTests() { + String context = BaseDecodingTest.class.getSimpleName(); + String key = PROP_RUN_PERF_TESTS; + boolean runPerfTests = PropertiesHelper.getPropertyBool(context, key, Boolean.FALSE); + return !runPerfTests; + } + + @Test + public void testBase64() { + assertEquals("", fromBase64("")); + assertEquals("f", fromBase64("Zg==")); + assertEquals("fo", fromBase64("Zm8=")); + assertEquals("foo", fromBase64("Zm9v")); + assertEquals("foob", fromBase64("Zm9vYg==")); + assertEquals("fooba", fromBase64("Zm9vYmE=")); + assertEquals("foobar", fromBase64("Zm9vYmFy")); + } + + @Test + public void testBase32() { + assertEquals("", fromBase32("")); + assertEquals("f", fromBase32("MY======")); + assertEquals("fo", fromBase32("MZXQ====")); + assertEquals("foo", fromBase32("MZXW6===")); + assertEquals("foob", fromBase32("MZXW6YQ=")); + assertEquals("fooba", fromBase32("MZXW6YTB")); + assertEquals("foobar", fromBase32("MZXW6YTBOI======")); + } + + @Test + public void testBase32Hex() { + assertEquals("", fromBase32Hex("")); + assertEquals("f", fromBase32Hex("CO======")); + assertEquals("fo", fromBase32Hex("CPNG====")); + assertEquals("foo", fromBase32Hex("CPNMU===")); + assertEquals("foob", fromBase32Hex("CPNMUOG=")); + assertEquals("fooba", fromBase32Hex("CPNMUOJ1")); + assertEquals("foobar", fromBase32Hex("CPNMUOJ1E8======")); + } + + @Test + public void testBase32Dmedia() { + + assertEquals("", fromBase32Dmedia("")); + assertEquals("binary foo", fromBase32Dmedia("FCNPVRELI7J9FUUI")); + assertEquals("f", fromBase32Dmedia("FR======")); + assertEquals("fo", fromBase32Dmedia("FSQJ====")); + assertEquals("foo", fromBase32Dmedia("FSQPX===")); + assertEquals("foob", fromBase32Dmedia("FSQPXRJ=")); + assertEquals("fooba", fromBase32Dmedia("FSQPXRM4")); + assertEquals("foobar", fromBase32Dmedia("FSQPXRM4HB======")); + } + + @Test + public void testBase16() { + assertEquals("", fromBase16("")); + assertEquals("f", fromBase16("66")); + assertEquals("fo", fromBase16("666F")); + assertEquals("foo", fromBase16("666F6F")); + assertEquals("foob", fromBase16("666F6F62")); + assertEquals("fooba", fromBase16("666F6F6261")); + assertEquals("foobar", fromBase16("666F6F626172")); + } + + @Test + public void testBase64Perf() { + if (isSkipPerfTests()) { + return; + } + + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = 'Z'; + } + long start = System.nanoTime(); + for (int i = 0; i < 200; i++) { + fromBase64(bytes); + } + long end = System.nanoTime(); + logger.info("Decoding 200MB Base64 took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = 'M'; + } + long start = System.nanoTime(); + for (int i = 0; i < 200; i++) { + fromBase32(bytes); + } + long end = System.nanoTime(); + logger.info("Decoding 200MB Base32 took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32HexPerf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = 'C'; + } + long start = System.nanoTime(); + for (int i = 0; i < 200; i++) { + fromBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Decoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32DmediaPerf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32Dmedia took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase16Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = '6'; + } + long start = System.nanoTime(); + for (int i = 0; i < 200; i++) { + fromBase16(bytes); + } + long end = System.nanoTime(); + logger.info("Decoding 200MB Base16 took " + StringHelper.formatNanoDuration(end - start)); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java new file mode 100644 index 000000000..4c2da50d7 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -0,0 +1,174 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import static ch.eitchnet.utils.helper.BaseDecodingTest.PROP_RUN_PERF_TESTS; +import static ch.eitchnet.utils.helper.BaseDecodingTest.isSkipPerfTests; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase16; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Dmedia; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase64; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class BaseEncodingTest { + + private static final Logger logger = LoggerFactory.getLogger(BaseEncodingTest.class); + + @Test + public void testBase64() { + assertEquals("", toBase64("")); + assertEquals("Zg==", toBase64("f")); + assertEquals("Zm8=", toBase64("fo")); + assertEquals("Zm9v", toBase64("foo")); + assertEquals("Zm9vYg==", toBase64("foob")); + assertEquals("Zm9vYmE=", toBase64("fooba")); + assertEquals("Zm9vYmFy", toBase64("foobar")); + } + + @Test + public void testBase32() { + assertEquals("", toBase32("")); + assertEquals("MY======", toBase32("f")); + assertEquals("MZXQ====", toBase32("fo")); + assertEquals("MZXW6===", toBase32("foo")); + assertEquals("MZXW6YQ=", toBase32("foob")); + assertEquals("MZXW6YTB", toBase32("fooba")); + assertEquals("MZXW6YTBOI======", toBase32("foobar")); + } + + @Test + public void testBase32Hex() { + assertEquals("", toBase32Hex("")); + assertEquals("CO======", toBase32Hex("f")); + assertEquals("CPNG====", toBase32Hex("fo")); + assertEquals("CPNMU===", toBase32Hex("foo")); + assertEquals("CPNMUOG=", toBase32Hex("foob")); + assertEquals("CPNMUOJ1", toBase32Hex("fooba")); + assertEquals("CPNMUOJ1E8======", toBase32Hex("foobar")); + } + + @Test + public void testBase32Dmedia() { + assertEquals("", toBase32Dmedia("")); + assertEquals("FCNPVRELI7J9FUUI", toBase32Dmedia("binary foo")); + assertEquals("FR======", toBase32Dmedia("f")); + assertEquals("FSQJ====", toBase32Dmedia("fo")); + assertEquals("FSQPX===", toBase32Dmedia("foo")); + assertEquals("FSQPXRJ=", toBase32Dmedia("foob")); + assertEquals("FSQPXRM4", toBase32Dmedia("fooba")); + assertEquals("FSQPXRM4HB======", toBase32Dmedia("foobar")); + } + + @Test + public void testBase16() { + assertEquals("", toBase16("")); + assertEquals("66", toBase16("f")); + assertEquals("666F", toBase16("fo")); + assertEquals("666F6F", toBase16("foo")); + assertEquals("666F6F62", toBase16("foob")); + assertEquals("666F6F6261", toBase16("fooba")); + assertEquals("666F6F626172", toBase16("foobar")); + } + + @Test + public void testBase64Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase64(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base64 took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32 took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32HexPerf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32DmediaPerf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32Dmedia took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase16Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase16(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base16 took " + StringHelper.formatNanoDuration(end - start)); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java new file mode 100644 index 000000000..eba21340e --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java @@ -0,0 +1,46 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class ExceptionHelperTest { + + @Test + public void shouldGetExceptionMsg() { + + Exception e = nestedException(); + assertEquals("Third", ExceptionHelper.getExceptionMessage(e)); + assertEquals("Third\nSecond\nFirst", ExceptionHelper.getExceptionMessageWithCauses(e)); + } + + @Test + public void shouldFormatException() { + + Exception e = nestedException(); + String formatException = ExceptionHelper.formatException(e); + assertTrue(formatException.contains("java.lang.RuntimeException: First")); + assertTrue(formatException.contains("java.lang.RuntimeException: Second")); + assertTrue(formatException.contains("java.lang.RuntimeException: Third")); + + formatException = ExceptionHelper.formatExceptionMessage(e); + assertEquals("Third\ncause:\nSecond\ncause:\nFirst", formatException); + } + + private Exception nestedException() { + try { + try { + try { + throw new RuntimeException("First"); + } catch (Exception e) { + throw new RuntimeException("Second", e); + } + } catch (Exception e) { + throw new RuntimeException("Third", e); + } + } catch (Exception e) { + return e; + } + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java new file mode 100644 index 000000000..e013c5659 --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.helper; + +import java.util.HashMap; +import java.util.Map; + +/** + * Simple helper class to generate the reverse alphabets for {@link BaseEncoding} + * + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class GenerateReverseBaseEncodingAlphabets { + + public static void main(String[] args) { + + System.out.println(generateReverseAlphabet("REV_BASE_16", BaseEncoding.BASE_16)); + System.out.println(generateReverseAlphabet("REV_BASE_32", BaseEncoding.BASE_32)); + System.out.println(generateReverseAlphabet("REV_BASE_32_CROCKFORD", BaseEncoding.BASE_32_CROCKFORD)); + System.out.println(generateReverseAlphabet("REV_BASE_32_DMEDIA", BaseEncoding.BASE_32_DMEDIA)); + System.out.println(generateReverseAlphabet("REV_BASE_32_HEX", BaseEncoding.BASE_32_HEX)); + System.out.println(generateReverseAlphabet("REV_BASE_64", BaseEncoding.BASE_64)); + System.out.println(generateReverseAlphabet("REV_BASE_64_SAFE", BaseEncoding.BASE_64_SAFE)); + } + + public static String generateReverseAlphabet(String name, byte[] alphabet) { + + Map valueToIndex = new HashMap<>(); + for (byte i = 0; i < alphabet.length; i++) { + Byte value = Byte.valueOf(i); + Byte key = Byte.valueOf(alphabet[value]); + if (valueToIndex.containsKey(key)) + throw new RuntimeException("Alphabet hast twice the same value " + key + " at index " + value); + valueToIndex.put(key, value); + } + + StringBuilder sb = new StringBuilder(); + sb.append("private static final byte[] " + name + " = { "); + + Byte minusOne = Byte.valueOf((byte) -1); + for (int i = 0; i < 128; i++) { + Byte index = Byte.valueOf((byte) i); + Byte value = valueToIndex.get(index); + if (value == null) + sb.append(minusOne.toString()); + else + sb.append(value.toString()); + + if (i < 127) + sb.append(", "); + } + + sb.append(" };"); + + return sb.toString(); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java new file mode 100644 index 000000000..f9525899d --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java @@ -0,0 +1,138 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertEquals; + +import java.util.Properties; + +import org.junit.Test; + +public class ReplacePropertiesInTest { + + @Test + public void shouldReplaceProps1() { + + String expr = "bla ${foo}"; + String expected = "bla bar"; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps2() { + + String expr = "${foo} bla "; + String expected = "bar bla "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps3() { + + String expr = "bla ${foo} "; + String expected = "bla bar "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps4() { + + String expr = "bla${foo}abr"; + String expected = "blabarabr"; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps5() { + + String expr = "bla '${foo}' "; + String expected = "bla 'bar' "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps6() { + + String expr = "${foo}bla ${foo} "; + String expected = "barbla bar "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps7() { + + String expr = "${foo}bla ${food} "; + String expected = "barbla foofoo "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + properties.setProperty("food", "foofoo"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps8() { + + String expr = "foo"; + String expected = "foo"; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps9() { + + String expr = "%{foo}bla %{food} "; + String expected = "barbla foofoo "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + properties.setProperty("food", "foofoo"); + + String result = StringHelper.replacePropertiesIn(properties, '%', expr); + + assertEquals(expected, result); + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java new file mode 100644 index 000000000..5964354ab --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java @@ -0,0 +1,206 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.TimeZone; + +import javax.xml.crypto.dsig.XMLSignature; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +public class XmlSignHelperTest { + + private static XmlDomSigner helper; + + @BeforeClass + public static void beforeClass() { + helper = new XmlDomSigner(new File("src/test/resources/test.jks"), "client", "server", + "changeit".toCharArray()); + } + + @Test + public void shouldSign() { + Document document = createDoc(); + helper.sign(document); + + assertSignatureElemExists(document); + } + + @Test + public void shouldSignWithNamespaces() { + + Document document = createDocWithNamespaces(); + + // hack for signing with namespaces problem + document = XmlDomSigner.parse(XmlDomSigner.transformToBytes(document)); + + helper.sign(document); + + assertSignatureElemExists(document); + } + + private void assertSignatureElemExists(Document document) { + NodeList nl = document.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + assertEquals("Expected exactly one Signature element!", 1, nl.getLength()); + } + + @Test + public void shouldValidate() { + + File signedXmlFile = new File("src/test/resources/SignedXmlFile.xml"); + Document document = XmlDomSigner.parse(signedXmlFile); + setIdAttr(document); + helper.validate(document); + } + + @Test + public void shouldValidateWithNamespaces() { + + File signedXmlFile = new File("src/test/resources/SignedXmlFileWithNamespaces.xml"); + Document document = XmlDomSigner.parse(signedXmlFile); + setIdAttrNs(document); + + helper.validate(document); + } + + private void setIdAttr(Document document) { + NodeList authnRequestNodes = document.getElementsByTagName("AuthnRequest"); + if (authnRequestNodes.getLength() != 1) + throw new IllegalStateException("Multiple or no AuthnRequest Node found in document!"); + Element authnRequestNode = (Element) authnRequestNodes.item(0); + authnRequestNode.setIdAttribute("ID", true); + } + + private void setIdAttrNs(Document document) { + NodeList authnRequestNodes = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol", + "AuthnRequest"); + if (authnRequestNodes.getLength() != 1) + throw new IllegalStateException("Multiple or no AuthnRequest Node found in document!"); + Element authnRequestNode = (Element) authnRequestNodes.item(0); + authnRequestNode.setIdAttribute("ID", true); + } + + @Test + public void shouldSignAndValidate() { + + Document document = createDoc(); + + helper.sign(document); + helper.validate(document); + } + + @Test + public void shouldSignAndValidateWithNamespaces() { + + Document document = createDocWithNamespaces(); + + // hack for signing with namespaces problem + document = XmlDomSigner.parse(XmlDomSigner.transformToBytes(document)); + + helper.sign(document); + helper.validate(document); + } + + public static Document createDoc() { + + String issuer = "test"; + String destination = "test"; + String assertionConsumerServiceUrl = "test"; + Calendar issueInstant = Calendar.getInstance(); + + // create dates + SimpleDateFormat simpleDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + simpleDf.setTimeZone(TimeZone.getTimeZone("UTC")); + String issueInstantS = simpleDf.format(issueInstant.getTime()); + String notBeforeS = issueInstantS; + issueInstant.add(Calendar.SECOND, 10); + String notOnOrAfterS = simpleDf.format(issueInstant.getTime()); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + DocumentBuilder docBuilder; + try { + docBuilder = dbf.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new RuntimeException("Failed to configure document builder!", e); + } + Document doc = docBuilder.newDocument(); + + Element authnReqE = doc.createElement("AuthnRequest"); + authnReqE.setAttribute("Version", "2.0"); + authnReqE.setAttribute("IssueInstant", issueInstantS); + authnReqE.setAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + authnReqE.setAttribute("AssertionConsumerServiceURL", assertionConsumerServiceUrl); + authnReqE.setAttribute("Destination", destination); + doc.appendChild(authnReqE); + + Element issuerE = doc.createElement("Issuer"); + issuerE.setTextContent(issuer); + authnReqE.appendChild(issuerE); + + Element conditionsE = doc.createElement("Conditions"); + conditionsE.setAttribute("NotBefore", notBeforeS); + conditionsE.setAttribute("NotOnOrAfter", notOnOrAfterS); + authnReqE.appendChild(conditionsE); + + return doc; + } + + public static Document createDocWithNamespaces() { + + String issuer = "test"; + String destination = "test"; + String assertionConsumerServiceUrl = "test"; + Calendar issueInstant = Calendar.getInstance(); + + // create dates + SimpleDateFormat simpleDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + simpleDf.setTimeZone(TimeZone.getTimeZone("UTC")); + String issueInstantS = simpleDf.format(issueInstant.getTime()); + String notBeforeS = issueInstantS; + issueInstant.add(Calendar.SECOND, 10); + String notOnOrAfterS = simpleDf.format(issueInstant.getTime()); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + DocumentBuilder docBuilder; + try { + docBuilder = dbf.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new RuntimeException("Failed to configure document builder!", e); + } + Document doc = docBuilder.newDocument(); + + Element authnReqE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest"); + authnReqE.setPrefix("samlp"); + authnReqE.setAttribute("Version", "2.0"); + authnReqE.setAttribute("IssueInstant", issueInstantS); + authnReqE.setAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + authnReqE.setAttribute("AssertionConsumerServiceURL", assertionConsumerServiceUrl); + authnReqE.setAttribute("Destination", destination); + doc.appendChild(authnReqE); + + Element issuerE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer"); + issuerE.setPrefix("saml"); + issuerE.setTextContent(issuer); + authnReqE.appendChild(issuerE); + + Element conditionsE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "Conditions"); + conditionsE.setPrefix("saml"); + conditionsE.setAttribute("NotBefore", notBeforeS); + conditionsE.setAttribute("NotOnOrAfter", notOnOrAfterS); + authnReqE.appendChild(conditionsE); + + return doc; + } +} diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java new file mode 100644 index 000000000..e1126d25b --- /dev/null +++ b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java @@ -0,0 +1,435 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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 ch.eitchnet.utils.objectfilter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.junit.Test; + +/** + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class ObjectFilterTest { + + @Test + public void shouldAdd() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + + testAssertions(filter, 1, 1, 1, 0, 0); + } + + @Test + public void shouldUpdate() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.update(myObj); + + testAssertions(filter, 1, 1, 0, 1, 0); + } + + @Test + public void shouldRemove() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj); + + testAssertions(filter, 1, 1, 0, 0, 1); + } + + @Test + public void shouldAddUpdateRemoveDifferentObjects() { + + Object objToAdd = new Object(); + Object objToUpdate = new Object(); + Object objToRemove = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(objToAdd); + filter.update(objToUpdate); + filter.remove(objToRemove); + + testAssertions(filter, 3, 1, 1, 1, 1); + } + + @Test + public void shouldAddUpdateRemoveSameObject() { + + Object objToAddUpdateRemove = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(objToAddUpdateRemove); + filter.update(objToAddUpdateRemove); + filter.remove(objToAddUpdateRemove); + + testAssertions(filter, 0, 1, 0, 0, 0); + } + + @Test + public void shouldNotAddTwice() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + + try { + filter.add(myObj); + fail("Should have failed adding twice!"); + } catch (RuntimeException e) { + assertEquals("Stale State exception: Invalid + after +", e.getMessage()); + } + + testAssertions(filter, 1, 1, 1, 0, 0); + } + + @Test + public void shouldNotRemoveTwice() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj); + + try { + filter.remove(myObj); + fail("Should have failed removing twice!"); + } catch (RuntimeException e) { + assertEquals("Stale State exception: Invalid - after -", e.getMessage()); + } + + testAssertions(filter, 1, 1, 0, 0, 1); + } + + @Test + public void shouldAcceptUpdateTwice() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.update(myObj); + filter.update(myObj); + testAssertions(filter, 1, 1, 0, 1, 0); + } + + @Test + public void shouldStillBeAddWithUpdate() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + filter.update(myObj); + filter.update(myObj); + testAssertions(filter, 1, 1, 1, 0, 0); + } + + @Test + public void shouldNotAcceptAddAfterModify() { + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.update(myObj); + + try { + filter.add(myObj); + fail("Should have failed add after modify"); + } catch (RuntimeException e) { + assertEquals("Stale State exception: Invalid + after +=", e.getMessage()); + } + + testAssertions(filter, 1, 1, 0, 1, 0); + } + + @Test + public void shouldAcceptAddAfterRemove() { + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj); + filter.add(myObj); + + testAssertions(filter, 1, 1, 0, 1, 0); + } + + @Test + public void shouldNotAcceptModifyAfterRemove() { + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj); + + try { + filter.update(myObj); + fail("Should have failed modify after remove"); + } catch (RuntimeException e) { + assertEquals("Stale State exception: Invalid += after -", e.getMessage()); + } + + testAssertions(filter, 1, 1, 0, 0, 1); + } + + @Test + public void shouldNotAcceptDifferentKeyForSameObject() { + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + + try { + filter.update("different_key", myObj); + fail("Should have failed because of different key for already registered object"); + } catch (RuntimeException e) { + String msg = "Object may be present in the same filter instance only once, registered using one key only"; + assertTrue("Encountered exception: " + e.getMessage(), e.getMessage().contains(msg)); + } + + testAssertions(filter, 1, 1, 1, 0, 0); + } + + @Test + public void shouldReplaceOnAddAfterRemove() { + + TestObject obj1 = new TestObject(1); + TestObject obj2 = new TestObject(1); + assertEquals("Test objects are not equal!", obj1, obj2); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(Object.class.getName(), obj1); + filter.add(Object.class.getName(), obj2); + + testAssertions(filter, 1, 1, 0, 1, 0); + + List updated = filter.getUpdated(Object.class.getName()); + Object updatedObj = updated.get(0); + String msg = "registered object is not the last operation's object"; + assertTrue(msg, obj2 == updatedObj); + } + + @Test + public void shouldReplaceOnUpdateAfterAdd() { + + TestObject obj1 = new TestObject(1); + TestObject obj2 = new TestObject(1); + assertEquals("Test objects are not equal!", obj1, obj2); + + ObjectFilter filter = new ObjectFilter(); + filter.add(Object.class.getName(), obj1); + filter.update(Object.class.getName(), obj2); + + testAssertions(filter, 1, 1, 1, 0, 0); + + List added = filter.getAdded(Object.class.getName()); + Object addedObj = added.get(0); + String msg = "registered object is not the last operation's object"; + assertTrue(msg, obj2 == addedObj); + } + + @Test + public void shouldReplaceOnUpdateAfterUpdate() { + + TestObject obj1 = new TestObject(1); + TestObject obj2 = new TestObject(1); + assertEquals("Test objects are not equal!", obj1, obj2); + + ObjectFilter filter = new ObjectFilter(); + filter.update(Object.class.getName(), obj1); + filter.update(Object.class.getName(), obj2); + + testAssertions(filter, 1, 1, 0, 1, 0); + + List updated = filter.getUpdated(Object.class.getName()); + Object updatedObj = updated.get(0); + String msg = "registered object is not the last operation's object"; + assertTrue(msg, obj2 == updatedObj); + } + + @Test + public void shouldReplaceOnRemoveAfterModify() { + + TestObject obj1 = new TestObject(1); + TestObject obj2 = new TestObject(1); + assertEquals("Test objects are not equal!", obj1, obj2); + + ObjectFilter filter = new ObjectFilter(); + filter.update(Object.class.getName(), obj1); + filter.remove(Object.class.getName(), obj2); + + testAssertions(filter, 1, 1, 0, 0, 1); + + List removed = filter.getRemoved(Object.class.getName()); + Object removedObj = removed.get(0); + String msg = "registered object is not the last operation's object"; + assertTrue(msg, obj2 == removedObj); + } + + @Test + public void shouldRemoveAfterAddAndRemove() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + filter.remove(myObj); + testAssertions(filter, 0, 1, 0, 0, 0); + } + + @Test + public void shouldClear() { + + Object myObj1 = new Object(); + Object myObj2 = new Object(); + Object myObj3 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj1); + filter.update(myObj2); + filter.remove(myObj3); + + filter.clearCache(); + + testAssertions(filter, 0, 0, 0, 0, 0); + } + + @Test + public void shouldGetAll() { + + Object myObj1 = new Object(); + Object myObj2 = new Object(); + Object myObj3 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj1); + filter.update(myObj2); + filter.remove(myObj3); + + testAssertions(filter, 3, 1, 1, 1, 1); + + List all = filter.getAll(Object.class.getName()); + assertEquals(3, all.size()); + assertTrue(all.contains(myObj1)); + assertTrue(all.contains(myObj2)); + assertTrue(all.contains(myObj3)); + } + + @Test + public void shouldGetAdded() { + + Object myObj1 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj1); + + testAssertions(filter, 1, 1, 1, 0, 0); + + List list = filter.getAdded(Object.class.getName()); + assertEquals(1, list.size()); + assertTrue(list.contains(myObj1)); + } + + @Test + public void shouldGetUpdated() { + + Object myObj1 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.update(myObj1); + + testAssertions(filter, 1, 1, 0, 1, 0); + + List list = filter.getUpdated(Object.class.getName()); + assertEquals(1, list.size()); + assertTrue(list.contains(myObj1)); + } + + @Test + public void shouldGetRemoved() { + + Object myObj1 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj1); + + testAssertions(filter, 1, 1, 0, 0, 1); + + List list = filter.getRemoved(Object.class.getName()); + assertEquals(1, list.size()); + assertTrue(list.contains(myObj1)); + } + + private void testAssertions(ObjectFilter filter, int size, int sizeKeySet, int added, int updated, int removed) { + assertEquals(size, filter.sizeCache()); + assertEquals(sizeKeySet, filter.sizeKeySet()); + + List addedList = filter.getAdded(Object.class.getName()); + assertEquals(added, addedList.size()); + + List updatedList = filter.getUpdated(Object.class.getName()); + assertEquals(updated, updatedList.size()); + + List removedList = filter.getRemoved(Object.class.getName()); + assertEquals(removed, removedList.size()); + } + + private class TestObject { + private int id; + + public TestObject(int id) { + this.id = id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getOuterType().hashCode(); + result = prime * result + this.id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TestObject other = (TestObject) obj; + if (!getOuterType().equals(other.getOuterType())) + return false; + if (this.id != other.id) + return false; + return true; + } + + private ObjectFilterTest getOuterType() { + return ObjectFilterTest.this; + } + } +} diff --git a/ch.eitchnet.utils/src/test/resources/SignedXmlFile.xml b/ch.eitchnet.utils/src/test/resources/SignedXmlFile.xml new file mode 100644 index 000000000..63197c399 --- /dev/null +++ b/ch.eitchnet.utils/src/test/resources/SignedXmlFile.xml @@ -0,0 +1,20 @@ +test6bxgcmypqGyvyQFgEqdOk1zLTOg=RAWnchzzSHwi84ZJcog6OnkYrGx7rBGBHDsysn1lmP05+AydKzcK7Jw3kbpkGnaaz1DIV/8A/hhA +UmOjwAl8RNygtziXuS5fZfvDUidhPugv2EUKeUkH7CwMkLSB5TONC+AS8eEhfyZbl/4GYMd4Jcqx +OQJgBRMNT6zfybFY+wfJDceFUqCCmyXFgcGBmtSjJqQivwH4B8k1ui49hO67ItCBcCo0aKpqoIxF +UA2IZCDvmrdR/qCq/oA9ssjUzpC+yJMvwPtZ6LDdoWt+MDzDBkCKA6lKUqtU51rZ8GwoRLve8FXH +vCG/ZiqxKn4JwBQL2DiFuZVXhrrMvLpYyi4ZRA==CN=test,OU=ch.eitchnet.utils,O=ch.eitchnet,L=Solothurn,ST=Solothurn,C=CHMIIDizCCAnOgAwIBAgIEPPVdzDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJDSDESMBAGA1UE +CBMJU29sb3RodXJuMRIwEAYDVQQHEwlTb2xvdGh1cm4xFDASBgNVBAoTC2NoLmVpdGNobmV0MRow +GAYDVQQLExFjaC5laXRjaG5ldC51dGlsczENMAsGA1UEAxMEdGVzdDAeFw0xNjAzMDMxMzEwMTRa +Fw0xNjA2MDExMzEwMTRaMHYxCzAJBgNVBAYTAkNIMRIwEAYDVQQIEwlTb2xvdGh1cm4xEjAQBgNV +BAcTCVNvbG90aHVybjEUMBIGA1UEChMLY2guZWl0Y2huZXQxGjAYBgNVBAsTEWNoLmVpdGNobmV0 +LnV0aWxzMQ0wCwYDVQQDEwR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxW8E +F/odm00xQTRQ95b9RtpmiQo3OQfpkE344vyp48BpOHMm8Q+sjTHde6hGUQQ3FSp6jUZtJBmuXDp+ +hFa4zNp1vleASqmUw4VCZZN1BgHx6coWA1zw/GWyCOFHvr+JTP6/LAfemudOnQVWKx6/NkMEgjNm +OR3qsbfj1km/VlfnIAOG9SvFvydp+Jan7y+nIKllEEXpcQFeiWouvC572aptu9k9qe43mRoQHJ96 +IngJHLONi27SBsF31ipvoHjkYEaTYfv8Izf5wt7h+8tZipFHu0+5r7LbZDRhSWzopC5KZakgVgLG +0JYEzcLotOCf9hmDDZZAAv3OyLoMqN8F0QIDAQABoyEwHzAdBgNVHQ4EFgQUd38xdRA7VcrWcjmz +CYmbBMD4SaUwDQYJKoZIhvcNAQELBQADggEBACqDrXrrYG2sfFBRTIQVni291q8tDqJ1etim1fND +s9ZdYa2oKTaLjMswdlE5hVXtEvRrN+XX3TIAK0lfiDwF4E4JBDww4a3SefEbwPvx110WN6CTE1NI +P6IPCUB7e5QOlg4uKAJoZfnY6HboRiHbeOQxIeis3Q9XpqQSYrO4/NzxFt66m48BHLqf8Hwi90GY +VYMljqr+hHvUTQWGzFD3NKr9Fq6yO2GcHGc5ifmLjwoz2EDAsSubrccbN+RQRRg3II6gFxyL9PYN +HgkGjqdg3v8TiWRxWAFL2MrgNLRzOX9Sl7NMFo6JwiizfLWTgcxZZVIkcU1ZP4heXi5iKUzgsJM= \ No newline at end of file diff --git a/ch.eitchnet.utils/src/test/resources/SignedXmlFileWithNamespaces.xml b/ch.eitchnet.utils/src/test/resources/SignedXmlFileWithNamespaces.xml new file mode 100644 index 000000000..012c1783b --- /dev/null +++ b/ch.eitchnet.utils/src/test/resources/SignedXmlFileWithNamespaces.xml @@ -0,0 +1,20 @@ +testKseWAs8E4H1ZGAbyl2EnlZ3RiG4=KhSDJRxo1u7eNVy1swN5RqA+37oCCeyY8QNCtT1RFz8UVZFqmXGiurscbctKA+tiYSekW4OkxEg9 +Nv03OGJcYlksdZ5CCGlsioac+NY/z2QngtlDaFudKIHwj9yZ9zMdiKT/4kdwnUQP+p9tzYV9GeA9 +gesLOielMdj382XoFQ/CIbrJevE4vpn9FSitbwHXV4kZ3/NxlBPYIgiM9yiTTT0NafFENTS38U+P +k1tL32FcDfHytWN6Twl2ZbHrRYltba/ncxaqkauMA37r9v2f+HS+hXXNluLTazRzAxnhSaetjPOt +GFP/nkG0TcRbiZFTP3YwIHeo94v2d/fs6rKHiQ==CN=test,OU=ch.eitchnet.utils,O=ch.eitchnet,L=Solothurn,ST=Solothurn,C=CHMIIDizCCAnOgAwIBAgIEPPVdzDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJDSDESMBAGA1UE +CBMJU29sb3RodXJuMRIwEAYDVQQHEwlTb2xvdGh1cm4xFDASBgNVBAoTC2NoLmVpdGNobmV0MRow +GAYDVQQLExFjaC5laXRjaG5ldC51dGlsczENMAsGA1UEAxMEdGVzdDAeFw0xNjAzMDMxMzEwMTRa +Fw0xNjA2MDExMzEwMTRaMHYxCzAJBgNVBAYTAkNIMRIwEAYDVQQIEwlTb2xvdGh1cm4xEjAQBgNV +BAcTCVNvbG90aHVybjEUMBIGA1UEChMLY2guZWl0Y2huZXQxGjAYBgNVBAsTEWNoLmVpdGNobmV0 +LnV0aWxzMQ0wCwYDVQQDEwR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxW8E +F/odm00xQTRQ95b9RtpmiQo3OQfpkE344vyp48BpOHMm8Q+sjTHde6hGUQQ3FSp6jUZtJBmuXDp+ +hFa4zNp1vleASqmUw4VCZZN1BgHx6coWA1zw/GWyCOFHvr+JTP6/LAfemudOnQVWKx6/NkMEgjNm +OR3qsbfj1km/VlfnIAOG9SvFvydp+Jan7y+nIKllEEXpcQFeiWouvC572aptu9k9qe43mRoQHJ96 +IngJHLONi27SBsF31ipvoHjkYEaTYfv8Izf5wt7h+8tZipFHu0+5r7LbZDRhSWzopC5KZakgVgLG +0JYEzcLotOCf9hmDDZZAAv3OyLoMqN8F0QIDAQABoyEwHzAdBgNVHQ4EFgQUd38xdRA7VcrWcjmz +CYmbBMD4SaUwDQYJKoZIhvcNAQELBQADggEBACqDrXrrYG2sfFBRTIQVni291q8tDqJ1etim1fND +s9ZdYa2oKTaLjMswdlE5hVXtEvRrN+XX3TIAK0lfiDwF4E4JBDww4a3SefEbwPvx110WN6CTE1NI +P6IPCUB7e5QOlg4uKAJoZfnY6HboRiHbeOQxIeis3Q9XpqQSYrO4/NzxFt66m48BHLqf8Hwi90GY +VYMljqr+hHvUTQWGzFD3NKr9Fq6yO2GcHGc5ifmLjwoz2EDAsSubrccbN+RQRRg3II6gFxyL9PYN +HgkGjqdg3v8TiWRxWAFL2MrgNLRzOX9Sl7NMFo6JwiizfLWTgcxZZVIkcU1ZP4heXi5iKUzgsJM= \ No newline at end of file diff --git a/ch.eitchnet.utils/src/test/resources/crypto_test_image.ico b/ch.eitchnet.utils/src/test/resources/crypto_test_image.ico new file mode 100644 index 000000000..b48b3442a Binary files /dev/null and b/ch.eitchnet.utils/src/test/resources/crypto_test_image.ico differ diff --git a/ch.eitchnet.utils/src/test/resources/crypto_test_long.txt b/ch.eitchnet.utils/src/test/resources/crypto_test_long.txt new file mode 100644 index 000000000..80a18dc58 --- /dev/null +++ b/ch.eitchnet.utils/src/test/resources/crypto_test_long.txt @@ -0,0 +1,447 @@ +Das Burgtheater in Wien ist ein österreichisches Bundestheater. Es gilt als eine der bedeutendsten Bühnen Europas und ist nach der Comédie-Française das zweitälteste europäische sowie das größte deutschsprachige Sprechtheater. Das alte Burgtheater befand sich seit 1748 am Michaelerplatz. Im Oktober 1888 wurde das neue Haus am heutigen Universitätsring (damals Franzensring, dann zwei andere Namen) eröffnet. Nachdem dieses 1945 infolge von Bombenangriffen vollständig ausgebrannt war, diente das Ronacher bis zur Wiedereröffnung am 14. Oktober 1955 als Ausweichquartier. Das Burgtheater gilt als österreichisches Nationaltheater.[1] + +Ältere Namen des Burgtheaters waren k.k. Theater nächst der Burg und danach bis 1918 k.k. Hof-Burgtheater. Vor allem in Wien wird es häufig kurz „Die Burg“ genannt, die Ensemblemitglieder kennt man als Burgschauspieler. Nach Zahl der Mitarbeiter und Budget ist es das „reichste und größte Repertoiretheater der Welt“.[2] Direktorin des Burgtheaters ist seit März 2014 Karin Bergmann. + +Inhaltsverzeichnis [Verbergen] +1 Geschichte +1.1 Das „alte“ Burgtheater am Michaelerplatz +1.2 Das neue Gebäude am Ring +1.3 Das Burgtheater in der Zeit des Nationalsozialismus +1.4 Das Burgtheater zu Kriegsende und nach dem Zweiten Weltkrieg +1.5 Das Burgtheater heute +1.5.1 Direktion Claus Peymann 1986–1999 +1.5.2 Direktion Klaus Bachler 1999–2009 +1.5.3 Direktion Matthias Hartmann 2009–2014 +1.5.4 Finanzskandal 2013/2014 +1.5.5 Direktion Karin Bergmann 2014 bis voraussichtlich 2019 +2 Hinter den Kulissen +2.1 Bühnentechnik und andere technische Besonderheiten +2.2 Kostüme, Requisiten +3 Weitere Spielstätten und Probebühnen des Burgtheaters +4 Die Wiener und „ihre“ Burg +4.1 Das „Burgtheaterdeutsch“ +4.2 Die großen Namen und ihre Wirkung +4.3 Ein Haus mit Tradition +4.4 Repertoire, Programm und Publikum +4.5 Abonnements, Kartenvorverkauf, Spielzeiten +4.6 Die Gesellschaft der Freunde des Burgtheaters +5 Mitarbeiter am Burgtheater +5.1 Das Ensemble +5.1.1 Derzeitige Ensemblemitglieder +5.1.2 Saison 2011/2012 +5.1.3 Ehemalige Ensemblemitglieder +5.1.4 Gastschauspieler +5.1.5 Ehrenmitglieder +5.2 Regisseure am Burgtheater +5.3 Andere Mitarbeiter +6 Anerkennung der schauspielerischen Leistung +6.1 Anerkennung der Schauspieler im Ensemble +6.2 Der Nestroy-Theaterpreis +7 Das Burgtheater im In- und Ausland +8 Die Leitung des Burgtheaters +8.1 Direktionen und künstlerische Leitungen des Burgtheaters +9 Das Burgtheater in Film und Literatur +10 Das Burgtheater im Fernsehen +11 Publikationen +12 Uraufführungen am Burgtheater (Auswahl) +13 Bildergalerie +14 Literatur +15 Weblinks +16 Einzelnachweise +Geschichte[Bearbeiten] + +Michaelerplatz mit dem alten k.k. Theater nächst der Burg (rechts) und der Winterreitschule der Hofburg (links) + +Innenraum des alten Burg­theaters, Gemälde von Gustav Klimt. Die Per­so­nen sind so detailliert dargestellt, dass die Identifizierung möglich ist. +Das „alte“ Burgtheater am Michaelerplatz[Bearbeiten] +Das ursprüngliche Burgtheater wurde in einem Ballhaus eingerichtet, das der römisch-deutsche König und spätere Kaiser Ferdinand I. 1540 im unteren Lustgarten der Hofburg erbauen ließ, nachdem das alte Ballhaus 1525 einem Brand zum Opfer gefallen war. Bis zu Beginn des 18. Jahrhunderts wurde dort das Jeu de Paume gespielt, ein Vorläufer des Tennis. Am 14. März 1741 erteilte schließlich Kaiserin Maria Theresia, die nach dem Tod ihres Vaters eine allgemeine Theatersperre angeordnet hatte, dem „Entrepreneur der königlichen Hofopern“ und Pächter des 1708 errichteten Theaters am Kärntnertor, Joseph Karl Selliers, die Erlaubnis, das Ballhaus in ein Theater umzuwandeln. Gleichzeitig wurde ein neues Ballhaus in unmittelbarer Nähe errichtet, das dem heutigen Ballhausplatz seinen Namen gab. + +Im Jahre 1748 wurde das neu gestaltete Theater nächst der Burg eröffnet. 1756 erfolgten größere Umbauarbeiten, wobei unter anderem eine neue Rückwand errichtet wurde. Der Zuschauerraum des alten Burgtheaters war noch eine reine Holzkonstruktion und fasste etwa 1200 Gäste. Die kaiserliche Familie konnte ihre Hofloge direkt von den kaiserlichen Gemächern aus erreichen, mit denen das Burgtheater baulich verbunden war. An der alten Spielstätte am Michaelerplatz wurden unter anderem mehrere Werke von Christoph Willibald Gluck, Ludwig van Beethoven, Wolfgang Amadeus Mozart sowie Franz Grillparzer uraufgeführt. + +Am 17. Februar 1776 erklärte Kaiser Joseph II. das Theater zum Teutschen Nationaltheater. Er war es auch, der per Dekret anordnete, dass die Stücke keine traurigen Ereignisse behandeln sollten, um die kaiserlichen Zuschauer in keine schlechte Stimmung zu bringen. Viele Stücke mussten deswegen geändert und mit einem Wiener Schluss (Happy End) versehen werden, beispielsweise Romeo und Julia oder Hamlet. Ab 1794 trug das Theater den Namen k.k. Hoftheater nächst der Burg. + +1798 wurde der Dichter August von Kotzebue zum Leiter des Burgtheaters ernannt, aber nach Auseinandersetzungen mit den Schauspielern verließ er 1799 Wien. Unter Direktor Joseph Schreyvogel wurde Deutsch statt Französisch und Italienisch als neue Bühnensprache eingeführt. + +Am 12. Oktober 1888 fand die letzte Vorstellung im alten Haus statt. Das Burgtheaterensemble übersiedelte in die neue Spielstätte am Ring. Das alte Burgtheater musste der Komplettierung des Michaelertrakts der Hofburg weichen. Die Pläne dazu hatte Joseph Emanuel Fischer von Erlach bereits knapp 200 Jahre vor dem Abriss des alten Burgtheaters gezeichnet. Eine 1817 errichtete, originalgetreue (aber verkleinerte) Kopie des Theatersaales befindet sich im rumänischen Oravita.[3] + +Das neue Gebäude am Ring[Bearbeiten] + +K.k. Hofburgtheater um 1900 + +Das bereits 1865 von Semper geplante, nicht realisierte Münchner Festspielhaus an der Isar + +„Das Theater von Taormina“ von Gustav Klimt, Deckengemälde in der Erzherzogsstiege + +Das Eingangsfoyer heute +Das „neue“ k.k. Hofburgtheater (wie die Aufschrift bis heute lautet) am Ring gegenüber dem Rathaus, am 14. Oktober 1888 mit Grillparzers Esther und Schillers Wallensteins Lager eröffnet, wurde im neubarocken Stil von Gottfried Semper (Grundriss) und Karl Freiherr von Hasenauer (Fassade) entworfen, die bereits das Kaiserforum in Wien gemeinsam geplant hatten. Die Bauarbeiten begannen am 16. Dezember 1874 und zogen sich 14 Jahre hin, in denen sich das Architektenduo zerstritt. Bereits 1876 zog sich Semper auf Grund gesundheitlicher Probleme nach Rom zurück und ließ Hasenauer seine Ideen alleine realisieren, der sich im Streit der Architekten vor allem für ein prachtvoll ausgestaltetes Logentheater eingesetzt hatte. + +Indes schuf der bekannte Wiener Maler Gustav Klimt gemeinsam mit seinem Bruder Ernst Klimt und mit Franz Matsch 1886–1888 die Deckengemälde in den beiden Stiegenhäusern des neuen Theaters. Die drei übernahmen diese Aufgabe nach ähnlichen Auftragsarbeiten in den Stadttheatern von Fiume und Karlsbad sowie im Bukarester Nationaltheater. In der Feststiege auf der dem Café Landtmann zugewandten Seite des Burgtheaters (Erzherzogstiege) bildete Gustav Klimt die Künstler des antiken Theaters in Taormina auf Sizilien, im Stiegenhaus auf der „Volksgarten“-Seite (Kaiserstiege, weil sie dem Kaiser vorbehalten war) das Londoner Globe Theatre und die Schlussszene aus William Shakespeares „Romeo und Julia“ nach. Über dem Eingang zum Zuschauerraum ist Der Eingebildete Kranke Molières zu entdecken. Im Hintergrund verewigte sich der Maler in Gesellschaft seiner beiden Kollegen. Kaiser Franz Joseph I. gefielen die Deckengemälde so sehr, dass er den Mitgliedern der Künstlerkompanie von Klimt das Goldene Verdienstkreuz verlieh. + +Das neue Gebäude ähnelt äußerlich der Dresdner Semperoper, mehr noch aber, aufgrund der beiden für Theaterbauten absolut untypischen Querflügel mit den Prunkstiegen, Sempers Münchner Projekt aus den Jahren 1865/1866 für ein Richard-Wagner-Festspielhaus über der Isar. Über dem Mitteltrakt befindet sich eine Loggia, die von zwei Seitenflügeln eingerahmt und aus einem Bühnenhaus mit Giebeldach und einem Zuschauerhaus mit Zeltdach geteilt wird. Über dem Mittelhaus schmückt eine Statue von Apollon die Fassade, der zwischen den Musen für Drama und Tragödie thront. Über den Haupteingängen befinden sich Friese mit Bacchus und Ariadne. An der Außenfassade rundum sind Porträtbüsten der Dichter Calderon, Shakespeare, Molière, Schiller, Goethe, Lessing, Halm, Grillparzer und Hebbel zu sehen. Die Masken, die ebenfalls hier zu sehen sind, weisen auf das antike Theater hin, außerdem schmücken allegorische Darstellungen die Seitentrakte: Liebe, Hass, Demut, Herrschsucht, Egoismus und Heroismus. Obwohl das Theater seit 1919 den Namen Burgtheater trägt, ist die alte Aufschrift K.K. Hofburgtheater über dem Haupteingang immer noch vorhanden. Einige Bilder der alten Porträtgalerie wurden im neuen Gebäude aufgehängt und sind heute noch zu sehen – allerdings waren diese Bilder ursprünglich kleiner, man musste sie „verlängern“, damit sie im hohen Raum besser wirken. Die Stellen dieser „Ergänzungen“ sind als feine Linien auf der Leinwand sichtbar. + +Das Burgtheater wurde zunächst auf Grund seines prachtvollen Aussehens und der technischen Neuerungen wie elektrischer Beleuchtung von den Wienern gut aufgenommen, doch bald wurde Kritik an der schlechten Akustik laut. 1897 erfolgte schließlich ein Umbau des Zuschauerraums, um die Akustikprobleme zu mindern. Das neue Theater wurde zu einem wichtigen Treffpunkt des Gesellschaftslebens und zählte schon bald zu den „Heiligtümern“ der Wiener. Im November 1918 ging die Aufsicht über das Theater vom Obersthofmeister des Kaisers auf den neuen Staat Deutschösterreich über. + +1922/1923 wurde das Akademietheater als Kammerspielbühne des Burgtheaters eröffnet. Am 8. Mai 1925 ging das Burgtheater in die österreichische Kriminalchronik ein, als hier Mentscha Karnitschewa ein Revolverattentat auf Todor Panitza verübte. + +Das Burgtheater in der Zeit des Nationalsozialismus[Bearbeiten] +Die nationalsozialistischen Ideen hinterließen auch Spuren in der Geschichte des Burgtheaters. 1939 erschien im Adolf Luser Verlag das stark antisemitisch geprägte Buch des Theaterwissenschaftlers Heinz Kindermann „Das Burgtheater. Erbe und Sendung eines Nationaltheaters“, in dem er unter anderem den „jüdischen Einfluss“ auf das Burgtheater analysierte.[4] Am 14. Oktober 1938 wurde zum 50-jährigen Eröffnungsjubiläum des Burgtheaters eine Don-Carlos-Inszenierung von Karl-Heinz Stroux gezeigt, die die Ideologie Hitlers bediente. Die Rolle des Marquis Posa spielte derselbe Ewald Balser, der ein Jahr zuvor in einer anderen Don-Carlos-Inszenierung (von Heinz Hilpert) am Deutschen Theater in derselben Rolle mit dem Satz Richtung Joseph Goebbels’ Loge wetterte: „Geben Sie Gedankenfreiheit!“. Der Schauspieler und Regisseur Lothar Müthel, der zwischen 1939 und 1945 Direktor des Burgtheaters war, inszenierte 1943 den Kaufmann von Venedig, in dem Werner Krauß den Juden Shylock eindeutig antisemitisch darstellte. Derselbe Regisseur inszenierte nach dem Krieg Lessings Parabel Nathan der Weise. Adolf Hitler selbst besuchte während des NS-Regimes das Burgtheater nur einmal (1938), später weigerte er sich aus panischer Angst vor einem Attentat. + +Für die Schauspieler und Theatermitarbeiter, die nach dem Reichsbürgergesetz von 1935 als „jüdisch“ eingestuft wurden, wurde rasch Auftrittsverbot verhängt, sie wurden innerhalb weniger Tage beurlaubt, entlassen oder verhaftet. Das Burgtheater-Ensemble leistete zwischen 1938 und 1945 keinen nennenswerten Widerstand gegen die NS-Ideologie, der Spielplan wurde stark zensiert, nur wenige schlossen sich aktiv dem Widerstand an, so Judith Holzmeister (damals auch am Volkstheater engagiert) oder der Schauspieler Fritz Lehmann. Den jüdischen Ensemblemitgliedern wurde zwar zur Emigration verholfen, dennoch wurde ein Schauspieler, Fritz Strassny, in ein Konzentrationslager gebracht und dort ermordet.[5] + +Das Burgtheater zu Kriegsende und nach dem Zweiten Weltkrieg[Bearbeiten] +Im Sommer 1944 musste auch das Burgtheater wegen der angeordneten allgemeinen Theatersperre geschlossen werden. Ab 1. April 1945, als sich die Rote Armee Wien näherte, lagerte eine militärische Einheit im Haus, ein Teil wurde als Waffenlager benutzt. Bei einem Bombenangriff wurde das Haus am Ring beschädigt und brannte am 12. April 1945 völlig aus. Zuschauerraum und Bühne wurden unbrauchbar, nur die Stahlkonstruktion blieb erhalten. Die Deckengemälde und Teile des Foyers waren beinahe unbeschädigt. + +Die sowjetische Besatzungsmacht erwartete vom Wiener Kulturstadtrat Viktor Matejka, Wiens Kulturleben so rasch wie möglich wieder in Gang zu bringen. Der Stadtrat berief daher für den 23. April (eine Staatsregierung bestand noch nicht) eine Versammlung aller Wiener Kulturschaffenden ins Rathaus ein. Resultat der Besprechungen war, dass Ende April 1945 acht Kinos und vier Theater den Betrieb wieder aufnahmen, darunter das Burgtheater.[6] Das Haus übernahm das Etablissement Ronacher, das von vielen Burgschauspielern als „Exil“ verstanden wurde, als Ausweichquartier (und blieb dort bis 1955). Diesen Spielort wählte der neu ernannte Direktor Raoul Aslan aus, der sich besonders engagiert einsetzte. + +Die erste Vorstellung nach dem Zweiten Weltkrieg war am 30. April 1945 Sappho von Franz Grillparzer in der Inszenierung von Adolf Rott aus dem Jahre 1943 mit Maria Eis in der Titelrolle. Auch andere Produktionen aus der NS-Zeit wurden wieder aufgenommen. Mit Paul Hörbiger, als NS-Häftling wenige Tage vorher noch in Lebensgefahr, wurde Nestroys Stück Das Mädl aus der Vorstadt gezeigt. Das Akademietheater konnte bespielt werden (die erste Aufführung war am 19. April 1945 Hedda Gabler, eine Inszenierung von Rott aus dem Jahre 1941) und auch im Redoutensaal in der Hofburg fanden Vorstellungen statt. Aslan ließ das Ronacher im Sommer umbauen, weil die Bühne für klassische Aufführungen zu klein war. Am 25. September 1945 konnte auf der vergrößerten Bühne Schillers Jungfrau von Orleans gespielt werden. + +Die ersten Neuinszenierungen sind mit dem Namen von Lothar Müthel verbunden: Jedermann und Nathan der Weise, in beiden spielte Raoul Aslan die Hauptrolle. Die Inszenierung des Kaufmanns von Venedig von Müthel zu NS-Zeiten schien in Vergessenheit geraten zu sein. + + +Das Burgtheater auf der Rückseite der 50-Schilling-Banknote (1970) +Große Freude bereitete dem Publikum die Rückkehr der 1938 aus dem Ensemble vertriebenen Else Wohlgemuth auf die Bühne. Sie trat nach sieben Jahren Exil im Dezember 1945 in Klara Biharys Die andere Mutter im Akademietheater auf. 1951 öffnete das Burgtheater das erste Mal seine Pforten, allerdings nur den linken Seitenflügel, wo die Feierlichkeiten zum 175-jährigen Bestehen des Theaters stattfanden. + +1948 wurde für den Wiederaufbau ein Wettbewerb ausgeschrieben: Josef Gielen, der damals Direktor war, tendierte zuerst dazu, den ex aequo erstgereihten Entwurf von Otto Niedermoser zu unterstützen, nach dem das Haus in ein modernes Rangtheater hätte umgebaut werden sollen. Schließlich stimmte er dann aber doch für das Projekt von Michel Engelhart, dessen Plan konservativer, aber auch kostengünstiger war. Der Charakter des Logentheaters wurde weitgehend berücksichtigt und beibehalten, die zentrale Hofloge wurde allerdings durch zwei Ränge ersetzt, und durch eine neue, schräge Deckenkonstruktion im Zuschauerraum wurde die Akustik, die Schwäche des Hause, deutlich verbessert. + +Am 14. Oktober 1955 kam es unter dem neuen Direktor Adolf Rott zur Wiedereröffnung des restaurierten Hauses am Ring. Aus diesem Anlass wurde Mozarts Eine kleine Nachtmusik gespielt. Am 15. und am 16. Oktober folgte die erste Aufführung (aus Platzgründen als Doppelpremiere) im wiederhergestellten Theater: König Ottokars Glück und Ende von Franz Grillparzer, inszeniert von Adolf Rott. Wenige Monate nach Unterzeichnung des Österreichischen Staatsvertrags war die Wahl dieses Stückes, das den Beginn der Habsburgerherrschaft in Österreich thematisiert und Ottokar von Hornecks Lobspruch auf Österreich (… es ist ein gutes Land, / Wohl wert, dass sich ein Fürst sein unterwinde! / Wo habt Ihr dessengleichen schon gesehn? …) enthält, äußerst symbolträchtig. Unter Rott und seinen Nachfolgern Ernst Haeusserman und Gerhard Klingenberg wurden der klassische Burgtheaterstil und das Burgtheaterdeutsch für die deutschen Bühnen endgültig richtungweisend. + +In den 1950er und 1960er Jahren beteiligte sich das Burgtheater (mit anderen namhaften Wiener Theatern) am sogenannten Brecht-Boykott. + +Das Burgtheater heute[Bearbeiten] + +Die Kaiserstiege im südlichen Seitenflügel des Burgtheaters + +Der heutige Zuschauerraum mit Blick auf die Festloge im 1. Rang rechts +Gerhard Klingenberg internationalisierte das Burgtheater, er lud bedeutende Regisseure wie Dieter Dorn, Peter Hall, Luca Ronconi, Giorgio Strehler, Roberto Guicciardini und Otomar Krejča ein. Klingenberg ermöglichte auch die Burg-Debüts von Claus Peymann und Thomas Bernhard (1974 Uraufführung von Die Jagdgesellschaft). Bernhard war als Nachfolger Klingenbergs im Gespräch, schließlich wurde aber Achim Benning ernannt, worauf der Schriftsteller mit dem Text „Die theatralische Bruchbude auf dem Ring (Wie ich Burgtheaterdirektor werden sollte)“ antwortete. + +Benning, der erste Ensemblevertreter des Burgtheaters, der zum Direktor ernannt wurde, setzte Klingenbergs Weg der Europäisierung mit anderen Mitteln fort, brachte Regisseure wie Adolf Dresen, Manfred Wekwerth oder Thomas Langhoff nach Wien, blickte mit Aufführungen von Stücken Václav Havels in den damals politisch abgetrennten Osten und nahm stärker Rücksicht auf den Publikumsgeschmack. + +Direktion Claus Peymann 1986–1999[Bearbeiten] +Unter dem von Kurzzeit-Unterrichtsminister Helmut Zilk nach Wien geholten Claus Peymann, Direktor 1986 bis 1999, kam es zu weiterer Modernisierung des Spielplans und der Inszenierungsstile. Außerdem war Peymann nie um kritische Wortmeldungen in der Öffentlichkeit verlegen; eine bis dahin für Burgtheaterdirektoren unübliche Haltung. Er und sein Programm stießen daher bei Teilen des Publikums auf Ablehnung. Den größten Wiener Theaterskandal seit 1945 gab es 1988 um die von konservativen Politikern und Eiferern heftig bekämpfte Uraufführung von Thomas Bernhards Drama Heldenplatz. Das Stück setzt sich mit der Vergangenheitsbewältigung Österreichs auseinander und beleuchtet die Gegenwart – mit Attacken auf die damals regierende SPÖ – kritisch. Gemeinsam mit Claus Peymann stellte sich Bernhard nach der Premiere auf der Bühne Applaus und Buhrufen. + +Bernhard, seinem Heimatland in Hassliebe verbunden, verbot vor seinem Tod 1989 die Aufführung seiner Stücke in Österreich testamentarisch. Peymann, der Bernhard in schwieriger Freundschaft verbunden war (siehe Bernhards Stück Claus Peymann kauft sich eine Hose und geht mit mir essen), befürchtete Schaden für das Werk des Autors, sollten seine Stücke ausgerechnet in seiner Heimat nicht gezeigt werden. Zunächst war es durch eine Erlaubnis des Testamentsvollstreckers Peter Fabjan – Bernhards Halbbruder – immerhin möglich, die bereits im Spielplan des Burgtheaters befindlichen Produktionen weiterzuspielen. Kurz vor Bernhards zehntem Todestag kam es schließlich zur Neuinszenierung des Bernhard-Stückes Vor dem Ruhestand durch den Uraufführungsregisseur Peymann. Die Stücke von Bernhard stehen seither weiter auf dem Spielplan des Burgtheaters und werden regelmäßig neu herausgebracht. + +1993 wurde die Probebühne des Burgtheaters im Arsenal eröffnet (Architekt: Gustav Peichl). Seit 1999 hat das Burgtheater die Betriebsform einer Ges.m.b.H. + +Direktion Klaus Bachler 1999–2009[Bearbeiten] +Auf Peymann folgte 1999 Klaus Bachler als Direktor. Er ist ausgebildeter Schauspieler, war aber zumeist als Kulturmanager (Intendant der Wiener Festwochen) tätig. Bachler rückte das Theater als kulturelles Ereignis in den Vordergrund und engagierte dazu Regisseure wie Luc Bondy, Andrea Breth, Nicolas Brieger, Peter Zadek und Martin Kušej. Ihm wird vorgeworfen, dass er die Burg „nicht gestaltet, sondern nur verwaltet“ hätte und wahllos große Namen im internationalen Theaterbetrieb zu Inszenierungen eingeladen habe. „Am Ende der Ära Bachler war die Burg nicht Fisch, nicht Fleisch“.[7] + +Zu den ungewöhnlichen „Events“ der Direktion Bachler zählten das Orgien-Mysterien-Theater von Hermann Nitsch mit der Performance 122. Aktion (2005), die Aufzeichnung des MTV-Unplugged-Konzerts mit den Toten Hosen für den Musiksender MTV (2005; unter dem Titel Nur zu Besuch erhältlich), John Irvings Lesung im Burgtheater aus seinem Buch Bis ich dich finde (2006), Die 431. animatographische Expedition von Christoph Schlingensief und eine große Veranstaltung von ihm unter dem Titel Area 7 – Sadochrist Matthäus – Eine Expedition von Christoph Schlingensief (2006). Daniel Hoevels schnitt sich in Schillers Maria Stuart versehentlich die Kehle auf (Dezember 2008). Ambulante Versorgung reichte aus.[8] + +Im Oktober 2005 feierte das Burgtheater den 50. Jahrestag seiner Wiedereröffnung mit einem Galaabend und mit der Aufführung von Grillparzers König Ottokars Glück und Ende in der Inszenierung von Martin Kušej, die im August 2005 bei den Salzburger Festspielen mit großem Erfolg aufgeführt worden war. Michael Maertens (in der Rolle von Rudolf von Habsburg) erhielt den Nestroy-Theaterpreis als bester Schauspieler für seine Rolle in diesem Stück. Hauptdarsteller Tobias Moretti wurde 2006 für diese Rolle mit dem Gertrud-Eysoldt-Ring ausgezeichnet. Weiters gab es am 16. Oktober 2005 einen Tag der offenen Tür, an dem der 82-minütige Film „burg / privat. 82 miniaturen“ von Sepp Dreissinger zum ersten Mal gezeigt wurde. Der Film enthält einminütige filmische „Standportraits“ von Burgschauspielern und Gastschauspielern, die, ohne ein Wort zu sagen, versuchen, sich mit einem möglichst natürlichen Gesichtsausdruck zu präsentieren. Klaus Dermutz schrieb ein Werk über die Geschichte des Burgtheaters. Als Motto dieser Spielzeit diente ein Zitat aus Lessings Minna von Barnhelm: „Es ist so traurig, sich allein zu freuen.“ + +Auch des Mozart-Jahres 2006 wurde im Burgtheater gedacht. Da Mozarts Singspiel Die Entführung aus dem Serail 1782 im Hof-Burgtheater uraufgeführt worden war, kam in Zusammenarbeit mit der Wiener Staatsoper zu den Wiener Festwochen im Mai 2006 eine Neuinszenierung (Regie: Karin Beier) dieser Oper auf die Bühne. + +Im April 2014 wollte ein von der Zeitschrift NEWS in Auftrag gegebenes Attest des unabhängigen Juristen Karl Newole den Vorgang der Untreue nicht ausschließen, weil Bachler 2009 in Wien volles Gehalt bezogen habe, obwohl er schon als Intendant der Münchner Oper tätig war. + +Direktion Matthias Hartmann 2009–2014[Bearbeiten] + +Kleists Prinz von Homburg, Kopro­duktion mit den Salzburger Festspielen, Peter Simonischek und August Diehl, inszeniert von Andrea Breth, 2012 +Von September 2009 bis zum 11. März 2014 war Matthias Hartmann künstlerischer Geschäftsführer des Burgtheaters. Der aus Osnabrück stammende Regisseur leitete zuvor die Schauspielhäuser Bochum und Zürich. Mit ihm kamen Regisseure wie Alvis Hermanis, Roland Schimmelpfennig, David Bösch, Stefan Bachmann, Stefan Pucher, Michael Thalheimer und Schauspieler wie Dörte Lyssewski, Katharina Lorenz, Sarah Viktoria Frick, Mavie Hörbiger, Lucas Gregorowicz, August Diehl und Martin Wuttke fest an die Burg. Selber inszeniert Matthias Hartmann rund drei Premieren pro Saison, etwa einmal im Jahr inszeniert er an den großen Opernhäusern. Für mehr Internationalität und „cross-over“ konnte er den belgischen Künstler Jan Lauwers und seine Needcompany als „Artists in Residence“ für die Burg gewinnen, die New Yorker Gruppe Nature Theater of Oklahoma zeigen ihr großes Episodendrama Live and Times als jährliche Fortsetzung. Für das neue Erscheinungsbild – das Burgtheater präsentiert sich ohne festes Logo mit Wortspielen rund um die BURG – wurde das Burgtheater 2011 mit der Kulturmarke des Jahres ausgezeichnet.[9] + +Hartmann konnte die Auslastung des Theaters gegenüber der Ära Bachler um 15 Prozent steigern (Vergleich der Spielzeiten 2008/09 und 2011/12). In den nur viereinhalb Jahren seiner Amtszeit wurde das Burgtheater siebenmal zum Berliner Theatertreffen eingeladen. Vorgänger Klaus Bachler brachte es in 10 Jahren auf neun Einladungen. Hartmann wurde jedoch auch vom Schriftsteller Peter Truschner vorgeworfen, dass er nach der Ära Bachler „in seiner Selbstbesoffenheit und der damit einhergehenden Selbstbereicherung dem Burgtheater erst recht nicht gedient“ habe.[7] + +Hartmann wurde im Auftrag von Kulturminister Josef Ostermayer am 11. März 2014 von der Bundestheater-Holding fristlos entlassen, weil vom Minister eingeholte Rechtsgutachten die grobe Missachtung von Geschäftsführerpflichten durch Hartmann festgestellt haben.[10][11] Der Abberufung Hartmanns war am 18. November 2013 die fristlose Entlassung der früheren kaufmännischen Leiterin des Hauses, Silvia Stantejsky, vorausgegangen.[12] Im Zusammenhang mit diesen Vorgängen wurde auch heftige Kritik am Aufsichtsrat des Burgtheaters und an Holding-Geschäftsführer Georg Springer geübt. + +Finanzskandal 2013/2014[Bearbeiten] +Das Burgtheater kam im Winter 2013 / 2014 in die Medien, als Unregelmäßigkeiten in der Buchführung zur Entlassung der Stellvertreterin des künstlerischen Direktors, Silvia Stantejsky führten. Wirtschaftsprüfer stellten in einer Gebarungsprüfung der von Stantejsky als kaufmännische Geschäftsführerin verantworteten Geschäftsjahre Ungereimtheiten fest, die nicht geklärt werden können.[13] Finanzielle Probleme des Burgtheaters kamen zuvor schon an die Öffentlichkeit: So weist beispielsweise der Geschäftsbericht zur Spielsaison 2011/2012 einen nicht liquiditätswirksamen Jahresfehlbetrag von 3,705 Millionen Euro und als Konsequenz eine Kapitalherabsetzung in gleicher Höhe aus.[14] + +Die fristlose Entlassung Stantejskys fand am 18. November 2013 statt.[15] Am 3. Jänner 2014 berichtet das Magazin News erstmals von der Entlassung.[16] Am 9. Jänner erhielt das Wirtschaftsprüfungsunternehmen KPMG den Auftrag zu einer forensischen Untersuchung der gegen Stantejsky vorliegenden Verdachtsmomente. In einem Interview der ZiB 2 vom 22. Jänner erhob Georg Springer, der Geschäftsführer der Bundestheater Holding, schwere Vorwürfe gegen Stantejsky: Diese habe eine sehr intelligente Schattenorganisation aufgebaut und dolose Handlungen gesetzt. Sechs Tage später bestritt Stantejsky in einem Interview mit dem Radiosender Ö1 sämtliche Vorwürfe energisch: Sowohl Dr. Springer als auch der Aufsichtsrat wird von sämtlichen buchhalterischen Entscheidungen informiert. Parallel kann gar nichts geschehen. + +Am 10. Februar 2014 veröffentlichte KPMG einen Zwischenbericht: Dieser ortete deutliche Indizien für gefälschte Belege und die Vorspiegelung falscher Tatsachen durch Silvia Stantejsky. Laut Aufsichtsrat des Burgtheaters sei daher für das Jahr 2012/13 mit einem Bilanzverlust von voraussichtlich 8,3 Millionen Euro zu rechnen. Dazu könnten 5 Millionen Euro Steuernachzahlungen kommen. + +Am 24. Februar brachte die Nationalratsfraktion NEOS eine 72 Fragen umfassende dringliche Anfrage zur Causa Burgtheater ein.[17] Zugleich kündigte der (damals noch designierte) Kulturminister Josef Ostermayer an, den österreichischen Rechnungshof um eine Prüfung der Finanzgebarung des Burgtheaters zu ersuchen.[15] + +Am 27. Februar 2014 wurde bekannt, dass eine anonyme Anzeige gegen Silvia Stantejsky bei der Korruptions-Staatswanwaltschaft eingegangen sei.[18] Am selben Tag legte KPMG den forensischen Untersuchungsbericht vor. Darin heißt es, die kaufmännische Direktion von Stantejsky sei sehr zentralisiert gesteuert und wie eine Containerorganisation geführt worden. Weiters habe sich der Verdacht auf Urkunden-, Beweismittel- und Bilanzfälschung, Geldwäsche sowie Untreue ergeben.[19] + +In einem Presse-Interview vom Vortag übte Martin Wagner, Senior-Partner der KPMG, Kritik an Hartmann und Springer: An der Entwicklung der Bankschulden konnte man leicht erkennen, dass das Haus verlustträchtig ist. Wenn man mehr ausgibt, als man hat, dann steigen die Schulden. Wenn dann trotzdem ein ausgeglichenes Ergebnis vorliegt, sagt einem der Hausverstand, dass da etwas nicht zusammenpasst.[20] + +Die finanziellen Verluste wurden im Prüfbericht auf über 8 Millionen Euro beziffert, dazu kommen laut Medienberichten noch rund 5 Millionen Euro an Steuernachzahlungen.[21] Die Verluste seien verschleiert worden, indem Einnahmen fälschlich ins Vorjahr und Ausgaben ins Folgejahr gebucht wurden. Die dazu erstellten Belege hätten die Unterschrift Hartmanns benötigt, diese fehlt aber auf den Buchungsbelegen. Daher wurde seit Anfang März 2014 ein von ihm zu verantwortender „Systemfehler“ diskutiert.[22] Am 10. März versuchte Hartmann noch sein Amt ruhend zu stellen[23], er wurde aber am Folgetag von Kulturminister Josef Ostermayer entlassen.[24][25] + +Hartmann selbst machte geltend, das Haus wäre nach 14 Jahren ohne Inflationsabgeltung bei massiv gestiegenen Personalkosten nicht mehr dem Gesetzesauftrag entsprechend betreibbar gewesen. Er habe auf diesen Umstand mehrfach hingewiesen und auch das „System Stantejsky“ frühzeitig beim Eigentümer, der Bundestheater-Holding, hinterfragt. Selbst die Beiziehung des Berliner Theaterexperten Peter F. Raddatz im Jahr 2011 habe die Holding allerdings nicht zum Handeln bewogen. + +Hartmann hat gegen die Entlassung gerichtliche Schritte ergriffen. Über seine Anwälte erklärte er den Vorgang für rechtswidrig und unwirksam und klagt knapp zwei Millionen Euro ein. Diese Summe errechnet sich aus den Jahresgehältern für seine Direktorenfunktion bis zum ursprünglichen Vertragsende im Sommer 2019 und aus Honoraren für seine Tätigkeit als Regisseur. + +Direktion Karin Bergmann 2014 bis voraussichtlich 2019[Bearbeiten] +→ Hauptartikel: Burgtheaterbesetzungen ab 2014 + +Die Schutzbefohlenen + +Das Reich der Tiere, 2015 + +Die letzten Tage der Menschheit, 2014 +Am 19. März 2014 wurde Karin Bergmann von Josef Ostermayer zur interimistischen Direktorin des Burgtheaters bestellt. Sie sollte dieses Amt bis zum 30. August 2016 innehaben.[26] Im April 2014 wurde bekannt, dass der damalige Burgtheaterdirektor Klaus Bachler Frau Bergmann 2009 insgesamt 32.400 Euro als steuerfreie „Schenkung“ überwiesen hat. Bergmann wäre der behördlichen Meldepflicht für Schenkungen aber nicht nachgekommen. Bachler war 2008/09 bereits Intendant der Münchner Oper, bezog aber auch in Wien volles Gehalt, während seine Vizedirektorin Bergmann die Geschäfte führte. Deshalb wurde angezweifelt, dass es sich tatsächlich um eine Schenkung gehandelt habe. Ein von der Zeitschrift NEWS in Auftrag gegebenes Attest des unabhängigen Juristen Karl Newole von einer „verunglückten Hilfskonstruktion“. Die Schenkung wäre „entweder eine direkte steuerpflichtige Honorzahlung oder eine Art indirekte Gehaltserhöhung“ gewesen. Die Zeitung Die Presse meldete außerdem, dass Bergmanns Ehemann, dem Architekten Luigi Blau, in der Ära Bachler ohne Ausschreibung sieben Aufträge des Burgtheaters zugegangen sind. Bergmann habe diesen Vorgang allerdings nicht goutiert. + +Am 24. Oktober 2014 wurde Karin Bergmann definitiv zur Direktorin des Burgtheaters bis zum Jahr 2019 bestellt. Es hatte einen Findungsprozess mit insgesamt 29 Interessenten gegeben, die sich entweder beworben hätten oder angesprochen worden seien. Mit den zwei Kandidaten des Vorschlages der Findungskommission hat es laut Kulturminister Josef Ostermayer ausführliche Gespräche gegeben. Der Aufsichtsrat hat dann einstimmig den Vorschlag des Ministers gutgeheißen. Für Bergmann habe nicht nur der Vorschlag der Findungskommission, sondern auch „ihre bisherige Performance“ bei der interimistischen Leitung gesprochen, so Ostermayer bei der Präsentation der Entscheidung vor der Presse. + +Hinter den Kulissen[Bearbeiten] +Bühnentechnik und andere technische Besonderheiten[Bearbeiten] + +Umbauarbeiten auf der Bühne des Burgtheaters +Der Zuschauerraum bietet etwa 1.340 Zuschauern Platz (1.175 Sitzplätze) und ist damit einer der größten unter Europas Schauspielhäusern. Das Bühnen-Portal ist 12 m breit und am höchsten Punkt 9 m hoch. Die Schnürböden und Beleuchtungsbrücken befinden sich in 28 m Höhe. Die Bühnenfläche beträgt rund 780 m², bei einer Breite von 31 Metern und einer Tiefe von etwa 25 Metern – sie kann allerdings durch eine Erweiterung im Cercle-Bereich erweitert werden.[27] Die Bühne selbst befindet sich im 1. Stock des Theatergebäudes, für die Schauspieler gibt es auf beiden Seiten der Bühne je zwei Eingänge. Im Hinterbühnenbereich existiert ein großer Aufzug, der 20 Meter breit und 1,5 Meter tief ist und somit zur Beförderung von Dekorationen geeignet ist, die zwar sehr breit und bis zu 3,5 Meter hoch, aber nur knapp 1,25 Meter tief sein dürfen. Größere Kulissenteile können nur gekippt transportiert werden. + +Die Bühne wurde 1954 von der österreichischen Firma Waagner Biro, die auch bei anderen Bühnen- und Opernhäusern Erfahrung besitzt, neu errichtet. Die Hauptbühne ist mit einer Drehzylinderbühne ausgestattet, die einen Durchmesser von 21 m und vier Versenkungen hat, die bis 8,8 m abgefahren werden können. Sie wurde nach den Plänen von Sepp Nordegg errichtet und ist insgesamt 5 Stockwerke tief, so können die Bühnenbilder im Paternoster-Prinzip getauscht werden. Die im Jahr 1994 begonnene Bühnenrenovierung wurde 2004 beendet, dabei wurde unter anderem die alte Steuerung durch eine Computersteuerung ersetzt. Die Drehzylinderbühne hat zwei Bühnenwagen und vier Versenkungen, eine Drehbühne mit 21 Meter Durchmesser und sechs Orchesterversenkungen, die für 70 Musiker Platz bieten. + +Der Eiserne Vorhang des Burgtheaters wiegt 16,8 Tonnen und kann im Notfall – zum Beispiel bei einem Brand – den Bühnenraum vom Zuschauerraum innerhalb von 28 Sekunden trennen und hält die Flammen mindestens 20 Minuten auf.[28] Nach dem Großbrand des Ringtheaters wurden alle Wiener Theater verpflichtet, die Bühnen mit einem Eisernen Vorhang zu versehen. Der alte wurde allerdings nach dem Zweiten Weltkrieg durch den heutigen ausgetauscht. Das Burgtheater hat eine hauseigene Betriebsfeuerwehr, die unter anderem prüfen muss, ob im Fall einer Alarmmeldung durch einen der besonders sensiblen Rauchmelder möglicherweise ein kontrolliertes Feuer auf der Bühne die Ursache ist, aber natürlich auch im Ernstfall die ersten Löscharbeiten durchführen muss. + + +Plan des Belüftungssystems (Ignaz Gridl) + +Die „Schwammerl“ genannte Luftansaughütte im Volksgarten + +Das Innenleben der Luftansaughütte +Architektonisch einzigartig und patentiert ist die riesige Luftschleuse, das Belüftungssystem des Theaters, das sich unter dem runden Dach der Luftansaughütte, von den Wienern einfach Schwammerl genannt, auf der Seite des Volksgartens verbirgt und nach den Plänen des Architektenbüros von Ignaz Gridl konstruiert wurde. Die Luft wird durch Filter geblasen, gereinigt und temperiert. Die verbrauchte Luft wird aus dem Zuschauerraum durch das Messinggitter des Kristalllusterkranzes im Zentrum der Saaldecke aus dem Raum ins Freie abgezogen. Den Sog dafür erzeugt der „Blasengel“, eine grüne Engelsfigur mit einem Blasinstrument, die als Wetterfahne auf der Kuppel steht. Diesen Teil des Belüftungssystems kann man am besten im Dachboden („Lusterboden“) und auf dem Kuppeldach des Theaters betrachten. Früher standen auf dem steilen Dach des Burgtheaters sogar Duschen und Toiletten (sie wurden gleich beim Wiederaufbau des Theaters nach dem Zweiten Weltkrieg errichtet), damit die Schauspieler in den Pausen zwischen den Proben ein Sonnenbad nehmen können. Seit 1977 ist dies allerdings verboten, die Anlagen wurden wieder abgetragen. Auf dem Dach ist eine Wetterkamera installiert, die bei der Sendung Wetterpanorama Österreich im Fernsehen einen Blick auf die Ringstraße ermöglicht. + +Die Souffleure am Burgtheater arbeiten mit Funktechnik, es gibt keinen Souffleurkasten mehr. + +Führungen durchs Haus finden jeden Tag um 15 Uhr auf Deutsch und an Freitagen, Samstagen, Sonntagen und Feiertagen auch auf Englisch statt. Während der Monate Juli und August finden an allen Wochentagen deutsche und englische Führungen statt. Außerdem finden auch an Freitagen, Samstagen, Sonntagen und Feiertagen jeweils um 14.00 Klimt-Führungen durch das Burgtheater statt. + +Kostüme, Requisiten[Bearbeiten] + +Eine Perücke aus der Burgtheater-Werkstatt +Die Kostümwerkstätten sind im 1. Bezirk, im Hanuschhof, untergebracht, Requisiten und Bühnenbildwerkstätten des Burgtheaters befinden sich im Arsenal. Sie versorgen nicht nur Burgproduktionen, sondern sie arbeiten auch auf Bestellung für andere österreichische und internationale Bühnen. Die Werkstätten arbeiten in Form einer GmbH (Art for Art – Theaterservice GmbH). Außerdem ist es Privatpersonen möglich, Kostüme aus dem Fundus auszuleihen. Die Kostüme und die Requisiten werden sorgfältig ausgesucht und aufwändig hergestellt, es werden meist nur Perücken aus echten Haaren verwendet, deren Herstellung oft zwei bis drei Wochen dauern kann. Ein Großlager für gerade nicht verwendete Kostüme befindet sich in der Montleartstraße im 14. Bezirk. + +Weitere Spielstätten und Probebühnen des Burgtheaters[Bearbeiten] + +Der Lusterboden des Hauses + +Anatomischer Saal der Akademie der Bildenden Künste + +Die Probebühne in den Werkstätten im Arsenal +Das Akademietheater, in den Jahren 1911 bis 1913 von den Architekten Fellner und Helmer und Ludwig Baumann erbaut, ist seit 1922 die zweite Spielstätte des Burgtheaters. Es wurde seither umgebaut und bühnentechnisch erneuert. +Das Kasino am Schwarzenbergplatz gilt als Spielstätte für Gegenwartsstücke und Spezialprojekte. Es wurde unter Direktor Benning am 26. April 1981 als 3. Raum am Schwarzenbergplatz eröffnet und wird seither mit Unterbrechungen (siehe Lusterboden) bespielt. Der gegenwärtige Name stammt aus der Direktion Peymann, die den Raum zunächst nur als Probebühne nutzte.[29] +Das Vestibül ist die Studiobühne des Burgtheaters und befindet sich unter der dem Café Landtmann zugewandten Feststiege. Das Vestibül wurde in den 1990er Jahren für Aufführungen hergerichtet. +Der Lusterboden ist eine im Dachgeschoss des Burgtheaters in einer Höhe von 43 Metern befindliche Probebühne. Diesen Raum im Dachboden gibt es seit 1955, er wird unter anderem als Requisitenlager verwendet. Ab dem 16. September 1979 wurde er auch immer wieder für Aufführungen herangezogen (zuerst als 3. Raum – Lusterboden, später nur Lusterboden), dann durch den Raum am Schwarzenbergplatz ersetzt. Als dieser wieder für Proben verwendet wurde, griff man unter Peymann erneut auf den Lusterboden als Aufführungsstätte zurück, von 1986 bis 1993 fungierte der Lusterboden als regelmäßige Spielstätte des Hauses. Seit 1993 verbietet das Veranstaltungsgesetz das öffentliche Bespielen von Theaterräumen, die sich höher als 8 Meter über dem Straßenniveau befinden. Schließlich wurde der Lusterboden nach der Wiedereröffnung des Raums am Schwarzenbergplatz erneut als Probebühne verwendet, wozu er auch gegenwärtig dient. +Eine ungewöhnliche Spielstätte hatte die Inszenierung Der Anatom von Klaus Pohl mit Ignaz Kirchner. Sie fand im Anatomischen Saal der Akademie der Bildenden Künste am Schillerplatz statt (2005–2006). +Die außergewöhnliche Inszenierung von Letzter Aufruf von Albert Ostermaier, der ersten Zusammenarbeit von Andrea Breth mit dem Bühnenbildner von Martin Kušej, Martin Zehetgruber, wurde auf der Probebühne 1 im Arsenal im 3. Bezirk gespielt, die kurzfristig zu einem Theaterraum umgebaut wurde (2002). Auf derselben Probebühne fanden im Juni 2010 drei Vorstellungen von Christoph Schlingensiefs letztem Theaterstück Via Intolleranza II statt. +Eine besondere Spielstätte bot die Feststiege des Burgtheaters Christian Nickels Inszenierung Die Wand nach dem Roman von Marlen Haushofer im Dezember 2012. +Eine weitere Probebühne befindet sich in der Turnergasse im 15. Gemeindebezirk. +Die Wiener und „ihre“ Burg[Bearbeiten] + +Burgtheater, Hauptportal am Universitätsring + +Seitenansicht von Süden; in der ehemaligen Vorfahrt zur Kaiserstiege befindet sich das Restaurant „Vestibül“ + +Rückseite des Burgtheaters an der Löwelstraße + +Blick vom Dach über den südlichen Seitenflügel auf Volksgarten und Museen +Das Burgtheater stand für die Wiener schon immer im Rampenlicht. Es galt stets als besonders vornehm, „in die Burg“ zu gehen. Bereits im 19. Jahrhundert gehörten die Gerüchte und die Skandale um die Burgschauspieler zu den beliebtesten Themen der Wiener. In der Burg konnten sich die verschiedenen gesellschaftlichen Schichten (Bürgertum und Adel) treffen, obwohl ihre Plätze strikt voneinander getrennt waren. Die Burgschauspieler „verbanden“ die zwei Stände und genossen dadurch in Wien einen besonders hohen sozialen Status (siehe später Vorhangverbot). + +Die Schauspielerin Charlotte Wolter beispielsweise wurde geradezu hysterisch gefeiert, ihre Stimme – der kräftige „Wolter-Schrei“ – war legendär. Später, in den 1940er Jahren, war natürlich das Ehepaar Paula Wessely und Attila Hörbiger Publikumsliebling Nummer 1. „Die Wessely“ wurde von den Damen gern nachgeahmt, ihre Frisur, der „Wessely-Scheitel“, machte Mode.[30] Die Popularität der beiden wurde aber auch zu Propagandazwecken missbraucht, als sie sich für den Vollzug des Anschlusses einsetzten. Die Töchter des Ehepaares, die später alle Schauspieler geworden sind, litten oft unter dem Ruhm ihrer Eltern. + +„Die Burg“ geriet manchmal allerdings auch in negative Schlagzeilen. Im Vorfeld der Uraufführung von Thomas Bernhards Heldenplatz 1988 fühlten sich viele Österreicher in ihrer Ehre gekränkt, weshalb die als Protest dagegen gedachte Großaktion von Martin Humer, der Kuhmist vor dem Burgtheater ablud, bei vielen Gefallen fand. Ebenfalls wenig beeindruckt waren viele Wiener von der Performance von Hermann Nitsch im Jahr 2005. + +Beerdigungen berühmter Burgschauspieler sind nach wie vor ein gern besuchtes Ereignis, die Wiener lieben „die schöne Leich“. Diese Eigenschaft der Wiener wurde allerdings im musikalischen Stück Pompes Funèbres von Franz Wittenbrink auf der Bühne karikiert. Besonders große Ereignisse waren 1981 das Begräbnis von Paul Hörbiger und 1996 die Verabschiedung von Josef Meinrad, zu denen Tausende aus ganz Österreich angereist sind. Meinrad war so beliebt, dass der Platz zwischen dem Burgtheater und dem Volksgarten nach ihm benannt wurde.[31] + +Während heutzutage andere Theater nicht selten ums Überleben kämpfen müssen, scheint die Lust der Wiener, in die Burg zu gehen, ungebrochen zu sein. Die Auslastung des Hauses betrug in der Saison 2005/06 bei 313.000 Besuchern 84 Prozent. Das Einnahmen-Soll wurde um 380.000 € übertroffen, insgesamt wurden sechs Millionen Euro eingespielt. + +Ein großes Gesprächsthema der Wiener ist immer die Ernennung eines neuen Intendanten – meistens beginnt die Spekulation über die Person des möglichen Direktors schon Monate vor der Entscheidung, die vom jeweiligen Staatssekretär für Kultur und Medien bekanntgegeben wird und in der Regel noch monatelang für weiteren Gesprächsstoff sorgt. + +Im Hauptgebäude des Theaters befinden sich zwei Unternehmen, die zwar nicht zum Burgtheater gehören, aber mittlerweile zu „Institutionen“ geworden sind. Das Buchgeschäft Leporello befindet sich auf der linken Seite der Eingangshalle und führt nebst Büchern auch Geschenksgegenstände des Burgtheaters sowie signierte Szenenphotos. Es sperrt in der Regel eine Stunde vor Vorstellungsbeginn auf und bleibt bis Vorstellungsende geöffnet. Im rechten, südlichen Flügel des Theaters ist das Nobelrestaurant Vestibül untergebracht, das ein architektonisches „Spiegelbild“ der gleichnamigen Spielstätte im linken Flügel des Gebäudes und vor allem für sein Weinangebot bekannt ist. Im Restaurant sind, ähnlich wie im Café Landtmann, vor und nach der Vorstellung oft Schauspieler und Theaterleute anzutreffen. + +Das Burgtheater ist auf der Rückseite der 50-Schilling-Banknote von 1970 zu sehen. Es ist auch wiederholt Motiv österreichischer Münzen und Briefmarken. + +Das „Burgtheaterdeutsch“[Bearbeiten] +Das Burgtheaterdeutsch, die Sprache, die auf der Bühne des Burgtheaters gesprochen wird, empfinden die meisten Wiener wie Musik in den Ohren, auf jeden Fall wird diese Bezeichnung für eine besonders schön gesprochene Variante der (im süddeutschen Sprachraum gebräuchlichen) deutschen Sprache verwendet. Diese Variante war eigentlich eine Kunstsprache und diente dazu, dass die Zuschauer die Schauspieler, die aus unterschiedlichen Regionen des deutschen Sprachraums kamen, auch unter den nicht idealen akustischen Umständen des Burgtheaters verstehen konnten. Als klassisches Beispiel für Burgtheaterdeutsch nennen viele die Bühnensprache, die Paula Wessely verwendete. + +Die großen Namen und ihre Wirkung[Bearbeiten] +Mittlerweile stammt ein großer Teil der Schauspieler und Theatermitarbeiter nicht aus Österreich, sondern aus Deutschland oder anderen Ländern; dennoch werden viele Burgschauspieler von den Wienern als Wiener akzeptiert und als „ihre Schauspieler“ verehrt. Der Deutsche Michael Heltau ist einer der größten Wienerlied-Interpreten, und Robert Meyer, der aus dem Grenzgebiet von Deutschland zu Österreich stammt, ist einer der beliebtesten Nestroy-Darsteller. Die Schweizerin Annemarie Düringer gehört ebenso zu den Lieblingen des Publikums wie der Deutsche Ignaz Kirchner, und die gebürtige Deutsche Susi Nicoletti galt als die österreichische Schauspielerin schlechthin. Auch die von Claus Peymann nach Wien geholten und anfangs angefeindeten Schauspieler wie Gert Voss und Kirsten Dene wurden bald Publikumslieblinge. Eine gute Besetzung (manchmal mit Gastschauspielern) kann bewirken, dass es so gut wie unmöglich ist, für eine Produktion Karten zu bekommen. Die Namen der schon erwähnten Hörbigers und „der Wessely“ wirkten wie ein Magnet auf das Publikum, es war praktisch alles restlos ausverkauft, wo sie auftraten. Aber auch heute gibt es „Dauerbrenner“: für die Ottokar-Inszenierung mit „der Orth“, „dem Maertens“, „dem Merkatz“ und „dem Moretti“ oder den Nathan mit „dem Brandauer“ war es monatelang sehr schwer, ohne Abo Karten zu kaufen. Die Beiträge im Gästebuch der Burgtheater-Homepage zeugen davon, dass manche Inszenierungen eine richtige Fan-Gemeinde haben. Dennoch kommt es immer wieder vor, dass die konservativeren Burgbesucher bei einer modernen Inszenierung ihren Unmut während der Vorstellung lautstark ausdrücken. + +Ein Haus mit Tradition[Bearbeiten] +Der natürlich auch anderswo verbreitete spezielle Theater-Aberglaube ist auch im Burgtheater, wo auf Tradition ein besonders großer Wert gelegt wird, anzutreffen, und daraus resultierende Bräuche und Rituale werden stets eingehalten. Viele Schauspieler glauben sogar – mit einem gewissen Augenzwinkern –, dass das Haus einen „Hausgeist“ hat. + +Es gibt strikte Hausregeln, zum Beispiel solche, die die Verbeugung regeln. Die Verbeugungsordnung schreibt vor, wer, wann und mit wem sich verbeugen soll, mit Sonderregeln für die Premiere. Eine solche Regel ist angeblich zum Beispiel, dass alle, die im zweiten Akt spielen, sich verbeugen müssen, diejenigen, die nur im ersten Akt auftreten, können es natürlich auch tun, müssen aber nicht. Bei der Premiere verbeugen sich in der Regel alle Mitwirkenden, auch die Komparsen und Kinderdarsteller. Für besondere Ensemblemitglieder galten manchmal andere Regeln, so musste sich zum Beispiel der alte Paul Hörbiger nicht immer mit den anderen verbeugen, weil er sonst seinen Zug verpasst hätte.[32] + +Das sogenannte Vorhangverbot war ein ungeschriebenes Gesetz, das fast 200 Jahre eingehalten wurde. Es geht auf eine polizeiliche Theaterordnung vom 19. August 1798 zurück, die vorschrieb, dass sich vor dem Vorhang nur Gäste und Debütanten, aber keine Ensemblemitglieder verbeugen durften. Der Grund war das hohe Ansehen der Schauspieler, sie galten als „Schauspieler Seiner Majestät“ und als solche wäre es für sie unmöglich gewesen, sich vor dem gemeinen Volk zu verbeugen.[33] Das Vorhangverbot, dessen Abschaffung im Lauf der Zeit immer wieder diskutiert wurde, das auch nicht lückenlos eingehalten wurde – etwa bei Aufführungen für Kinder – und nur für das Haupthaus (also nie für das Akademietheater) galt, wurde mit Beginn der Saison 1983/1984 vom damaligen Unterrichtsminister Helmut Zilk aufgehoben. Die erste Premiere ohne Vorhangverbot war Nestroys Höllenangst in der Inszenierung von Leopold Lindtberg. + +Repertoire, Programm und Publikum[Bearbeiten] +Das Burgtheater arbeitet im Repertoiresystem, das heißt in jeder Saison werden mindestens 30 Stücke abwechselnd gespielt. Jährlich gibt es im Burgtheater, Akademietheater und in den kleinen Spielstätten etwa Premieren. + +In den ersten Jahrzehnten war das Repertoire des Burgtheaters, also der Umfang der gespielten Stücke, sehr groß. In der Direktion Laube konnten zum Teil bis zu 160 verschiedene Stücke pro Saison gesehen werden, und noch zu Anfang der Saison 1918/1919 waren es 107 Stücke. Einige Inszenierungen hielten sich oft 10 Jahre oder länger, manche sogar über Jahrzehnte hinweg, sowohl im alten als auch im neuen Burgtheater auf dem Spielplan. Dafür wurde sie pro Saison höchstens 4 bis 6 Mal gezeigt. Somit mussten Abonnenten nicht allzu oft dasselbe Stück sehen, sondern waren mit einer außergewöhnlich abwechslungsreichen Vielfalt konfrontiert. Dieses System endete nach dem Ersten Weltkrieg. Plötzlich standen im Repertoire der Saison 1919/20 nur noch 20 Stücke zur Verfügung. Als die Regie in den Vordergrund rückte sowie Inszenierungen durch wechselnde Moden schneller veralteten, wurden Stücke pro Jahr mindestens so oft angesetzt wie früher in 10 oder 20 Jahren. + + +Ein typisches Burgtheaterplakat +Das aktuelle Programm des Burgtheaters wird auf Plakaten und einer roten Tafel links vom Haupteingang angekündigt. Früher – vor Bachlers Zeit – war es üblich, das Programm an die Fassade über dem Haupteingang zu hängen. Die Plakate werden jeden Tag mit der aktuellen Besetzung gedruckt und während der Vorstellung am Vorabend aufgehängt; sie enthalten auch die Namen der Komparsen (bei Mehrfachbesetzungen immer die aktuelle Besetzung). + +Die Programmhefte besaßen nach dem Zweiten Weltkrieg ein einheitliches Aussehen, das für alle Bundestheater galt: sie waren auf der Vorderseite durch eine Reihe eng gesetzter, brauner, senkrechter Linien gekennzeichnet, auf der unteren Mitte war in kursiver Schrift der Name des Theaters angegeben. In die Programmhefte wurden die Theaterzettel eingelegt. Sie wurden täglich für die jeweiligen Vorstellungen gedruckt und enthielten u. a. Angaben zu Stück, Autor, Beginn, Ende und Besetzung. Nach dem Aufkommen der Programmhefte wurden sie in diese eingelegt. Dieses System wurde bis 1986 beibehalten. Mit dem Beginn der Direktion Peymann wurde dieses System nach und nach abgeschafft, die früher üblichen häufigen Umbesetzungen waren im Lauf der Jahre durch konstante Besetzungen abgelöst worden. Nunmehr war die jeweilige Besetzung fix im Programmheft abgedruckt. Eventuelle Änderungen werden durch eingelegte, kleinere Zettel bekanntgegeben („In der heutigen Vorstellung spielt N. N. die Rolle XY“). + +Die Programmhefte enthielten neben (kultur)historischen Texten und Informationen über Werk und Autor seit der Direktion Peymann auch zumeist den gesamten Text des aufgeführten Stückes, in dem die Änderungen, Kürzungen und Regieanweisungen markiert waren. Gelegentlich wurden auch Fotos von den Bühnenbildentwürfen oder -modellen sowie den Kostümfigurinen abgedruckt. + +Ältere Programmhefte haben einen Sammelwert und können an besonderen Tagen (wie am Tag der offenen Tür) im Burgtheater käuflich erworben werden. In der Peymann-Direktion wurde das Aussehen der Programmhefte von Karl-Ernst Herrmann neu gestaltet. Sie waren für das Burgtheater und das Akademietheater grundsätzlich ähnlich gehalten und unterschieden sich in den ersten Jahren nur durch eine andere Farbgebung (hellgrau für das Akademietheater, helles Beige für die Burg). Später wurde mit anderen Farbgebungen sowie unterschiedlichen Formaten experimentiert. Lediglich das Programmheft für André Hellers Sein und Schein unterschied sich komplett von den übrigen Programmheften und enthielt Illustrationen der an der Ausstattung beteiligten Künstler (Roy Lichtenstein oder Mimmo Paladino). Unter Bachlers Direktion erhielten die Programmhefte grundsätzlich individuelle Outfits und sie enthalten meistens assoziative Texte und Bilder zum Stück, nur ganz selten den Text des Stückes. Die Plakate und die Programmhefte des Burgtheaters werden in der Druckerei agensketterl in Mauerbach (NÖ) hergestellt. + +Das Repertorium war die Sammlung der seit 1821 angefertigten Dokumentation der Aufführungen am Burgtheater. In große Bücher wurden täglich die Aufführungen und bei Premieren die Besetzungen eingetragen. Alle Schauspieler, die später eine Rolle in dem jeweiligen Stück übernahmen, wurden ergänzt. Für Inszenierungen, die vor 1821 auf dem Spielplan standen und noch gespielt wurden, trug man die Besetzungen seit 1776 nach. Jene Stücke, die bis 1821 vom Spielplan verschwanden, blieben hingegen unberücksichtigt. Quellen hierzu sind Theater- und Programmzettel oder alte Theaterzeitschriften. Die Führung des Repertoriums wurde 1958 aufgegeben. Ihnen folgten die Vorstellungsplatten nach. Sie wurden von der Regiekanzlei für jede Inszenierung eines Stücks angelegt und enthielten die Aufführungsdaten, die Besetzungen sowie allfällige Umbesetzungen. + +Im jährlichen Geschäftsbericht des Burgtheaters kann man die genauen Besetzungs- und Umbesetzungslisten, weiters Statistiken über die Auslastung des Hauses bei diversten Produktionen finden. + +Um einen direkten Kontakt mit dem Publikum aufrechtzuerhalten, veranstaltet das Burgtheater regelmäßig öffentliche Publikumsgespräche – teils allgemeine, bei denen die Zuschauer die Möglichkeit bekommen, der Direktion Fragen zu stellen, teils spezielle Publikumsgespräche zu einzelnen Inszenierungen, die immer im Anschluss einer Vorstellung stattfinden, und bei denen das Publikum die Dramaturgie, den Regisseur und die Schauspieler treffen kann. Matinées und Lesungen bereiten wichtige Ereignisse vor, wie Premieren, und auf Einträge im Gästebuch der Homepage wird auch geantwortet. + +Abonnements, Kartenvorverkauf, Spielzeiten[Bearbeiten] +Von 1776 bis in die Zeit nach dem Ersten Weltkrieg existierte kein Abonnementsystem im heutigen Sinne. Es gab die sogenannten Stammsitze in allen Teilen des Zuschauerraums. Sie berechtigten zum täglichen Besuch des Burgtheaters und zur Benutzung eines bestimmten Sitzes. Aufgrund der Stammsitze musste der Spielplan besonders abwechslungsreich sein. Oft wurden in einer einzigen Saison mehr als 100 verschiedene Werke gezeigt. Beliebte Stücke konnten Jahr für Jahr immer wieder einige Male angesetzt werden, teilweise über Jahrzehnte hinweg in derselben Inszenierung. Die Regie spielte jedoch damals eine deutlich untergeordnete Rolle. Um das Interesse an den Aufführungen zu erhöhen, fanden zudem häufige Umbesetzungen statt. Ein ähnliches System bietet das Burgtheater zurzeit im Rahmen des Festabonnements an. Neben diesen Vollabonnements gab es auch Halbabonnements (sie berechtigten zum Besuch an geraden oder ungeraden Tagen) sowie Viertelabonnements (Besuch an jedem vierten Tag). Durch die radikale Einschränkung des bis 1919/20 zahlenmäßig großen Repertoires wurde dieses System obsolet. Ab 25. November 1919 wurde das Vollabonnement auf Galeriesitze aufgelassen, außerdem auch das Viertelabonnement auf Parkettsitze. Den Halbabonnenten wurde das Besuchsrecht für einen Tag entzogen, womit mehr Karten in den freien Verkauf gelangten. + +Zurzeit gibt es 30 verschiedene Abonnements und diverse Zyklen (wie der Zyklus Nach der Premiere). Das Wahlabonnement berechtigt den Inhaber, zu günstigeren Preisen schon vor dem offiziellen Vorverkaufsbeginn Karten einer gewissen Kategorie zu erwerben. Viele Vorstellungen werden auch im Jugendabo „Theater der Jugend“ angeboten. Für Senioren gibt es ein eigenes Abonnement, bei dem einmal im Monat eine Vorstellung angeboten wird, die früher (um 16 oder 17 Uhr) beginnt. Das Festabonnement berechtigt den Inhaber, fünf Vorstellungen seiner Wahl an einem vorher festgelegten Tag der Woche (außer Samstag kann man jeden beliebigen Tag wählen) mit großer Preisermäßigung zu besuchen – der Aboinhaber hat bei diesem Abonnement einen fixen Sitzplatz. + +An jedem 20. des Monats beginnt der Kartenvorverkauf für die Vorstellungen des nächsten Monats, Wahlaboinhaber können bereits ab dem 15. des Monats Karten reservieren. Gäste aus anderen Bundesländern und aus dem Ausland können auch schriftlich oder per Fax Karten bestellen. Es gibt Kontingente für Pädagogen und Jugendliche, seit Jänner 2007 ist es sogar möglich, für gewisse Vorstellungen mit anschließendem Gespräch Gratiskarten für ganze Schulklassen zu bekommen.[34] Eine Stunde vor Vorstellungsbeginn kann man Restkarten zum halben Preis kaufen, und es werden immer Stehplatzkarten zurückgehalten, die ebenfalls vor Vorstellungsbeginn angeboten werden, bei sehr begehrten Vorstellungen kann man jedoch nur jeweils eine Stehplatzkarte pro Person kaufen. + + +Sitzplan +Das Burgtheater und seine Nebenbühnen werden von Mitte September bis zum 30. Juni theoretisch jeden Tag bespielt. Bis zur Direktion Bachler begann das Burgtheater jährlich am 1. September mit dem Spielbetrieb (nach einer zweimonatigen Sommerpause). Generell spielfrei sind nur der Karfreitag und der Heilige Abend, probebedingt kann es vorkommen, dass an einigen Abenden auf der einen oder der anderen Bühne keine Vorstellung stattfindet. Diese sogenannten Schließtage zur ganztägigen Abhaltung von Bühnen-, Dekorations- und Beleuchtungsproben im Burgtheater sowie im Akademietheater wurden erstmals unter der Direktion Peymann eingeführt und sorgten einige Jahre lang für heftige Kontroversen, die zum Teil auf den Kultur- und Leserbriefseiten österreichischer Zeitungen ausgetragen wurden. Peymann wurde vorgeworfen, durch Schließtage die Einnahmen des Burgtheaters zu reduzieren. + +Eine große Debatte löste im Februar 2007 die Nachricht aus, dass im Juni 2008 eine Fan-Meile zur 13. Fußball-Europameisterschaft vor dem Rathaus – und somit auch vor dem Burgtheater – errichtet werden soll, und das Burgtheater fordert eventuelle Ersatzspielstätten, damit der Spielbetrieb nicht beeinträchtigt wird und keine Schließtage erforderlich sind.[35] + +Die Gesellschaft der Freunde des Burgtheaters[Bearbeiten] +Die Gesellschaft der Freunde des Burgtheaters ist eine als eingetragener Verein wirkende Publikumsorganisation, die 1956 von ungefähr 200 Theaterliebhabern gegründet wurde und in der Goethegasse im 1. Bezirk Wiens ihren Sitz hat. Unter den Gründern waren auch berühmte Persönlichkeiten wie Friedrich Heer und Clemens Holzmeister. Seitdem erhöhte sich die Mitgliedschaft auf etwa 700 Personen. Der Gesellschaft steht ein eigenes Theaterkontingent mit fixen Plätzen für die erste Vorstellung nach jeder Premiere auf allen Spielstätten des Theaters zur Verfügung. Der Verein pflegt einen intensiven Kontakt zum Haus und zu den Schauspielern. Er organisiert Publikumstreffen, Lesungen, Buchpräsentationen und Informationsabende für seine Mitglieder, weiters versucht er auch das junge Publikum anzusprechen, indem er versucht, beliebte Nachwuchskünstler für diese Veranstaltungen zu engagieren. Die Gesellschaft unterstützt auch das Studium talentierter Jungschauspieler am Max Reinhardt Seminar und schreibt manchmal Schreibwettbewerbe aus. + +Mitarbeiter am Burgtheater[Bearbeiten] +Das Ensemble[Bearbeiten] +Zu den kaiserlichen Zeiten genossen die Schauspieler einen hohen gesellschaftlichen Rang und ein großes Ansehen. Sie wurden meistens auf Lebenszeit angestellt. Heute gibt es für Ensemblemitglieder meistens Jahresverträge. Seit Oktober 1971 gibt es die Richtlinien für die Tätigkeit der Ensemblevertretung des Burgtheaters.[36] Ein von Ensemble gewählter Vertrauensmann hat gegenüber der Direktion das Recht, unter anderem bei Besetzungen und bei der Spielplangestaltung mitzusprechen. Der derzeitige Ensemblesprecher ist Roland Koch.[37] Das Burgtheater ist grundsätzlich ein typisches Ensembletheater, die meisten Schauspieler sind mit längerfristigen Verträgen gebunden, Gäste kommen nur für einzelne Rollen. Zugleich lebte das Burgtheater immer schon von herausragenden Schauspielern, die nur für eine einzige oder ein paar Rollen ans Burgtheater kamen und sich mit ihrer Persönlichkeit einbrachten. + +Derzeitige Ensemblemitglieder[Bearbeiten] +In der Saison 2014/2015 sind am Burgtheater 74 Schauspieler als Ensemblemitglieder und weitere 38 als Gäste engagiert. Zu den aktuell 112 Schauspielern (44 Damen, 68 Herren) gehören als Ensemblemitglieder:[38] + +Damen: Liliane Amuat, Elisabeth Augustin, Jasna Fritzi Bauer, Andrea Clausen, Kirsten Dene, Stefanie Dvorak, Sarah Viktoria Frick, Alina Fritsch, Regina Fritsch, Brigitta Furgler, Frida-Lovisa Hamann, Maria Happel, Dorothee Hartinger, Sabine Haupt, Alexandra Henkel, Mavie Hörbiger, Katharina Lorenz, Dörte Lyssewski, Petra Morzé, Elisabeth Orth, Caroline Peters, Barbara Petritsch, Christiane von Poelnitz, Stefanie Reinsperger, Sylvie Rohrer, Aenne Schwarz, Dunja Sowinetz, Catrin Striebeck, Adina Vetter, Johanna Wokalek. +Herren: Bernd Birkhahn, Klaus Maria Brandauer, Franz J. Csencsits, Sven Dolinski, Detlev Eckstein, Lucas Gregorowicz, Philipp Hauß, Michael Heltau, Tino Hillebrand, Daniel Jesch, Marcus Kiepe, Ignaz Kirchner, Peter Knaack, Hans Dieter Knebel, Roland Koch, Dietmar König, Johannes Krisch, Fabian Krüger, Michael Maertens, Oliver Masucci, Michael Masula, Peter Matić, Rudolf Melichar, André Meyer, Markus Meyer, Joachim Meyerhoff, Tilo Nest, Dirk Nocker, Johann Adam Oest, Nicholas Ofczarek, Klaus Pohl, Robert Reinagl, Martin Reinke, Falk Rockstroh, Laurence Rupp, Branko Samarovski, Hermann Scheidleder, Martin Schwab, Peter Simonischek, Oliver Stokowski, Daniel Sträßer, Stefan Wieland, Peter Wolfsberger, Martin Wuttke. +Zu den Gästen der Spielzeit 2014/15 zählen: + +Damen: Inge van Bruystegem, Edith Clever, Gertraud Jesserer, Sung Im Her, Corinna Kirchhoff, Pauline Knof, Melanie Kretschmann, Sona MacDonald, Nadia Migdal, Birgit Minichmayr, Wiebke Mollenhauer, Sophie Rois, Yohanna Schwertfeger, Bibiana Zeller. +Herren: Gundars Āboliņs, Sven-Eric Bechtolf, Joachim Bißmeier, Gregor Bloéb, August Diehl, Marc Hosemann, Gerrit Jansen, Simon Kirsch, Gerhard König, Michael König, Christoph Krutzler, Christoph Luser, Matthias Matschke, Cornelius Obonya, Sven Philipp, Hanno Pöschl, Hans-Michael Rehberg, Thomas Reisinger, Michael Rotschopf, Udo Samel, Albrecht Abraham Schuch, Maik Solbach, Florian Teichtmeister, Moritz Vierboom. +Saison 2011/2012[Bearbeiten] +In der Saison 2011/2012 waren am Burgtheater 80 Schauspieler als Ensemblemitglieder und weitere 40 als Gäste engagiert. Zu den 120 Schauspielern (44 Damen, 76 Herren) gehörten als Ensemblemitglieder: Liliane Amuat, Elisabeth Augustin, Sven-Eric Bechtolf, Bernd Birkhahn, Klaus Maria Brandauer, Andrea Clausen, Franz J. Csencsits, Kirsten Dene, Sven Dolinski, Annemarie Düringer, Stefanie Dvorak, Detlev Eckstein, Sarah Viktoria Frick, Regina Fritsch, Brigitta Furgler, Lucas Gregorowicz, Maria Happel, Dorothee Hartinger, Sabine Haupt, Philipp Hauß, Michael Heltau, Alexandra Henkel, Mavie Hörbiger, Gerrit Jansen, Daniel Jesch, Marcus Kiepe, Corinna Kirchhoff, Ignaz Kirchner, Simon Kirsch, Peter Knaack, Hans Dieter Knebel, Roland Koch, Dietmar König, Michael König, Johannes Krisch, Fabian Krüger, Katharina Lorenz, Dörte Lyssewski, Michael Maertens, Oliver Masucci, Michael Masula, Peter Matić, Juergen Maurer, Rudolf Melichar, André Meyer, Markus Meyer, Joachim Meyerhoff, Peter Miklusz, Birgit Minichmayr, Blanka Modra, Petra Morzé, Tilo Nest, Dirk Nocker, Johann Adam Oest, Nicholas Ofczarek, Elisabeth Orth, Caroline Peters, Barbara Petritsch, Christiane von Poelnitz, Klaus Pohl, Robert Reinagl, Martin Reinke, Falk Rockstroh, Sylvie Rohrer, Branko Samarovski, Udo Samel, Hermann Scheidleder, Martin Schwab, Yohanna Schwertfeger, Peter Simonischek, Dunja Sowinetz, Daniel Sträßer, Catrin Striebeck, Adina Vetter, Moritz Vierboom, Stefan Wieland, Johanna Wokalek, Peter Wolfsberger, Martin Wuttke, Bibiana Zeller. + +Zu den Gästen der Spielzeit 2011/12 zählen: Therese Affolter, Elizabeth Conner, Anne Gridley, Jana Horst, Melanie Kretschmann, Julie LaMendola, Sandra Lipp, Sunnyi Melles, Karin Pfammatter, Elisa Plüss, Katharina Schmalenberg, Anna Starzinger, Bettina Stucky, Merle Wasmuth; Gundars Āboliņs, Juris Baratinskis, Marcus Bluhm, Markus Hering, Marc Hosemann, Robert Hunger-Bühler, Robert M. Johanson, Manfred Karge, Roland Kenda, Christoph Luser, Paulus Manker, Matthias Matschke, Tobias Moretti, Jacques Palminger, Kaveh Parmas, Hanno Pöschl, Jörg Ratjen, Hans-Michael Rehberg, Thomas Reisinger, Veit Schubert, Edgar Selge, Maik Solbach, Volker Spengler, Oliver Stokowski, Ernst Stötzner, Gerd Wameling. + +Ehemalige Ensemblemitglieder[Bearbeiten] + +Max Devrient als Zawisch in Franz Grillparzers „König Ottokars Glück und Ende“ nach 1891. +Berühmte ehemalige Ensemblemitglieder: Erich Aberle, Trude Ackermann, Rosa Albach-Retty, Wolf Albach-Retty, Ernst Anders, Raoul Aslan, Blanche Aubry, Erich Auer, Ewald Balser, Günther Georg Bauer, Bernhard Baumeister, Patrick O. Beck, Maria Becker, Klaus Behrendt, Ulrike Beimpold, Anne Bennent, Achim Benning, Joachim Bissmeier, Hedwig Bleibtreu, Monica Bleibtreu, Karl Blühm, Marcus Bluhm, Karl Böhm, Uwe Bohm, Otto Bolesch, Markus Boysen, Rolf Boysen, Viktor Braun, Marion Breckwoldt, Inge Brücklmeier, Traugott Buhre, Gandolf Buschbeck, Horst Caspar, Karim Chérif, Bruno Dallansky, Theodor Danegger, Ernst Deutsch, Max Devrient, Birgit Doll, Käthe Dorsch, Lona Dubois, Margarethe Dux, Thomas Egg, Hartmut Ehler, Heinz Ehrenfreund, Karl Eidlitz, Maria Eis, Christine Enghaus, Richard Eybner, Ulli Fessl, O. W. Fischer, Sebastian Fischer, Georg Filser, Peter Fitz, Ludwig Gabillon, Zerline Gabillon, Bruno Ganz, Wolfgang Gasser, Helma Gautier, Gerhard Geisler, Adrienne Gessner, Siegmund Giesecke, Alexander Girardi, Boy Gobert, Käthe Gold, Hugo Gottschlich, Fritz Grieb, Ingeborg Gruber, Karlheinz Hackl, Günther Haenel, Carla Hagen, Amalie Haizinger, Fritz Hakl, Konrad Adolf Hallenstein, Paul Hartmann, Heidemarie Hatheyer, Angelika Hauff, Wolfgang Hebenstreith, Urs Hefti, Fred Hennings, Jürgen Hentsch, Miguel Herz-Kestranek, Philipp Hochmair, Elisabeth Höbarth, Attila Hörbiger, Christiane Hörbiger, Paul Hörbiger, Frank Hoffmann, Paul Hoffmann, Stella von Hohenfels-Berger, Thomas Holtzmann, Judith Holzmeister, Gusti Huber, Wolfgang Hübsch, Manfred Inger, Helmut Janatsch, Antonie Janisch, Michael Janisch, Julia Janssen, Hanns-Ernst Jäger, Gertraud Jesserer, Peter P. Jost, Curd Jürgens, Josef Kainz, Elisabeth Kallina, Lilly Karoly, Otto Kerry, Lisl Kinast, Pauline Knof, Inge Konradi, Willi Kowalj, Hilde Krahl, Friedrich Krastel, Josef Krastel, Tom Krinzinger, Ida Krottendorf, Karl Friedrich Krüger, Jutta Lampe, Pavel Landovský, Jenny Lattermann, Inge Leddihn, Fritz Lehmann, Lotte Ledl, Joseph Lewinsky, Florian Liewehr, Fred Liewehr, Hugo Lindinger, Robert Lindner, Theo Lingen, Paola Loew, Else Ludwig, Sylvia Lukan, Ferdinand Maierhofer, Leslie Malton, Paulus Manker, Sigrid Marquardt, Johanna Matz, Josef Meinrad, Kurt Meisel, Karl Wilhelm Meixner, Rudolf Melichar, Robert Meyer, Wolfgang Michael, Erna Michall, Karl Mittner, Nick Monu, Heinz Moog, Hans Moser, Ulrich Mühe, Hans Günther Müller, Fritz Muliar, Alfred Neugebauer, Dorothea Neff, Susi Nicoletti, Ruth Niehaus, Cornelius Obonya, Hanns Obonya, Joseph Offenbach, Max Ophüls, Elfriede Ott, Dorothea Parton, Karl Paryla, Romuald Pekny, Denis Petkovic, Max Pfeiler, Hedwig Pistorius, Erika Pluhar, Ernst Princz, Ulla Purr, Will Quadflieg, Charles Regnier, Emerich Reimers, Heinz Reincke, Ulrich Reinthaller, Veit Relin, Walther Reyer, Tonio Riedl, Hilde Rom, Otto Rub, Albert Rueprecht, Heinz Rühmann, Johanna Sacco, Adele Sandrock, Johannes Schauer, Fritz Schediwy, Erich Schellow, Hannes Schiel, Aglaja Schmid, Otto Schmöle, Wenzel Scholz, Hermann Schöne, Peter Schratt, Lieselotte Schreiner, Joseph Schreyvogel, Heinrich Schweiger, Alma Seidler, Albin Skoda, Stefan Skodler, Adolf von Sonnenthal, Edd Stavjanik, Sigfrit Steiner, Wolfgang Stendar, Lilly Stepanek, Lena Stolze, Peter Striebeck, Walter Stumvoll, Sonja Sutter, Michael Tellering, Johannes Terne, Hans Thimig, Helene Thimig, Hermann Thimig, Hugo Thimig, Curth Anatol Tichy, Jane Tilden, Lotte Tobisch, Heinz Trixner, Alexander Trojan, Eckart Uhlmann, Gertrud Ukena, Gert Voss, Hilde Wagener, Rudolf von Waldenfels, Martha Wallner, Brigitte Walzl Peter Weck, Antje Weisgerber, Angelika Welzl Oskar Werner, Paula Wessely, Josef Wichart, Jürgen Wilke, Heinz Woester, Gusti Wolf, Paul Wolf-Plottegg, Charlotte Wolter, Klausjürgen Wussow, Philipp Zeska, Eleonore Zetzsche, Eva Zilcher, Kurt Zips, Heinz Zuber. + +Gastschauspieler[Bearbeiten] +Als Gäste, für einzelne Rollen engagiert, traten auf Meriam Abbas, Therese Affolter, Susanne Almassy, Axel von Ambesser, Leon Askin, Barbara Auer, Bibiana Beglau, Senta Berger, Josef Bierbichler, Hans-Christian Blech, Pinkas Braun, Ella Büchi, Margit Carstensen, Ingrid Caven, Edith Clever, August Diehl, Ralf Dittrich, Karoline Eichhorn, Veronika Fitz, Cornelia Froboess, Helmut Griem, Olivia Grigolli, Matthias Habich, Corinna Harfouch, O. E. Hasse, Hannelore Hoger, Marianne Hoppe, Christine Kaufmann, Hermann Killmeyer, Klaus Kinski, Wolfram Koch, Jutta Lampe, Hermann Lause, Helmuth Lohner, Susanne Lothar, Eva Mattes, Sunnyi Melles, Kurt Meisel, Karl Merkatz, Bernhard Minetti, Tobias Moretti, Richard Münch, Dierk Prawdzik, Hans Michael Rehberg, Martin Reinke, Hans Christian Rudolph, Ilse Ritter, Sophie Rois, Otto Sander, Maximilian Schell, Otto Schenk, Christoph Schlingensief, Walter Schmidinger, Robert Stadlober, Oliver Stokowski, Tilda Swinton, Franziska Tilden, Susanne Tremper, Ulrich Tukur, Angela Winkler, Ulrich Wildgruber, Werner Wölbern, Gisela Uhlen, Walker Wyatt, Hans Dieter Zeidler. + +Ehrenmitglieder[Bearbeiten] +Ehrenmitglieder sind: Max Devrient 1922, Hugo Thimig 1922, Georg Reimers 1922, Auguste Wilbrandt-Baudius 1922, Hedwig Bleibtreu 1924, Rosa Albach-Retty 1928, Tiny Senders 1929, Anton Wildgans 1932, Else Wohlgemuth 1935, Raoul Aslan 1946, Erhard Buschbeck 1949, Werner Krauß 1959, Alma Seidler 1960, Adolf Rott 1962, Franz Salmhofer 1963, Fred Hennings 1963, Ewald Balser 1963, Ernst Lothar 1963, Eduard Volters 1964, Hermann Thimig 1965, Paula Wessely 1967, Käthe Gold 1967, Ernst Haeusserman 1968, Fred Liewehr 1969, Attila Hörbiger 1971, Josef Meinrad 1973, Leopold Lindtberg 1974, Rudolf Steinboeck 1978, Heinz Moog 1978, Susi Nicoletti 1983, Erich Auer 1986, Gusti Wolf 1987, Fritz Muliar 1995, Wolfgang Gasser 1997, Judith Holzmeister 2000, Heinrich Schweiger 2000, Annemarie Düringer 2001, Michael Heltau 2003, Klaus Maria Brandauer 2008, Gerhard Blasche 2008, Klaus Bachler 2009, Martin Schwab 2009, Gert Voss 2009, Sylvia Lukan 2010, Claus Peymann 2012, Elisabeth Orth 2014, Achim Benning, Gerhard Klingenberg. + +Regisseure am Burgtheater[Bearbeiten] +Direktion Röbbeling: Josef Gielen, Ernst Lothar, Otto Preminger, Herbert Waniek +Direktion Gielen: Axel von Ambesser, Raoul Aslan, Ewald Balser, Ulrich Bettac, Walter Davy, Leon Epp, Walter Felsenstein, Josef Gielen, Joseph Glücksmann, Curd Jürgens, Leopold Lindtberg, Theo Lingen, Ernst Lothar, Gustav Manker, Charles Regnier, Adolf Rott, Oscar Fritz Schuh, Hans Thimig, Berthold Viertel, Eduard Volters, Herbert Waniek, Oskar Wälterlin, Philipp Zeska +Direktion Rott: Josef Gielen, Adolf Rott +Direktion Haeusserman: Axel von Ambesser, Fritz Kortner, Gustav Rudolf Sellner, Rudolf Steinboeck, Gustav Manker, Leopold Lindtberg, Ernst Lothar +Direktion Paul Hoffmann: Boy Gobert,Gerhard Klingenberg, Kurt Meisel, Leopold Lindtberg, Adolf Rott, Otto Schenk, Rudolf Steinboeck +Direktion Klingenberg: Peter Arens, Erich Auer, Erwin Axer, Jean-Louis Barrault, Achim Benning, Edward Bond, Pinkas Braun, Kazimierz Dejmek, Dieter Dorn, Jaroslav Dudek, Bruno Felix, Walter Felsenstein, Karl Fruchtmann, Wolfgang Glück, Roberto Guicciardini, Peter Hall, Dietrich Haugk, Gerd Heinz, Gerhard F. Hering, Michael Kehlmann, Gerhard Klingenberg, Otomar Krejča, Wolfgang Liebeneiner, Leopold Lindtberg, Peter Lotschak, Conny Hannes Meyer, Mario Missiroli, Claus Peymann, Jean-Pierre Ponnelle, Adolf Rott, Jean-Paul Roussillon, Luca Ronconi, Otto Schenk, Hans Schweikart, Rudolf Steinboeck, Giorgio Strehler, Konrad Swinarski, Otto Tausig, Ernst Wendt, Rudolf Wessely, Herbert Wochinz, Peter Wood, Fritz Zecha +Direktion Benning: Erwin Axer, Achim Benning, Dieter Berner, Benno Besson, Adolf Dresen, Karl Fruchtmann, Armand Gatti, Dieter Giesing, Terry Hands, Hans Hollmann, Angelika Hurwicz, Gerhard Klingenberg, Thomas Langhoff, Leopold Lindtberg, Juri Ljubimow, Jonathan Miller, Jérôme Savary, Johannes Schaaf, Otto Schenk, Peter Wood, Horst Zankl +Direktion Peymann: Einar Schleef, Ruth Berghaus, Luc Bondy, Dieter Giesing, Michael Haneke, Matthias Hartmann, Leander Haußmann, Karin Henkel, Uwe Jens Jensen, Manfred Karge, Alfred Kirchner, Konstanze Lauterbach, Cesare Lievi, Paulus Manker, Wilfried Minks, Claus Peymann, Niels-Peter Rudolph, Giorgio Strehler, Philip Tiedemann, Peter Zadek, Tamás Ascher +Direktion Bachler: Stefan Bachmann, Igor Bauersima, Karin Beier, Theu Boermans, Andrea Breth, Nicolas Brieger, Barbara Frey, Klaus Michael Grüber, Sebastian Hartmann, Niklaus Helbling, Friederike Heller, Grzegorz Jarzyna, Stephan Kimmig, Martin Kušej, Thomas Langhoff, Kurt Palm, Carolin Pienkos, Christiane Pohle, Silviu Purcarete, Rimini Protokoll, Árpád Schilling, Christoph Schlingensief, Nicolas Stemann, Peter Zadek +Direktion Hartmann: Stefan Bachmann, Anna Bergmann, David Bösch, Jan Bosse, Luc Bondy, Andrea Breth, Kelly Copper und Pavol Liska, Barbara Frey, Dieter Giesing, Dimiter Gotscheff, Matthias Hartmann, Niklaus Helbling, Alvis Hermanis, Stephan Kimmig, Michael Laub, Jan Lauwers, Alexandra Liedtke, David Marton, Bastian Kraft, René Pollesch, Stefan Pucher, Annette Raffalt, Peter Raffalt, Carina Riedl, Roland Schimmelpfennig, Michael Thalheimer, Thomas Vinterberg, Alexander Wiegold, Sarantos Zervoulakos +Direktion Bergmann: Robert Borgmann, Jan Bosse, Barbara Frey, Dusan David Parizek, Felix Prader, Annette Raffalt, Georg Schmiedleitner, Christian Stückl +Andere Mitarbeiter[Bearbeiten] +Außer den Schauspielern arbeiten ungefähr 300 Burgtheater-Mitarbeiter am Gelingen der Theaterabende. Im Kollektivvertrag werden die Rechte und Pflichten der Theatermitarbeiter (natürlich auch die der Schauspieler) genau geregelt. Ein wichtiger Punkt ist, dass man nicht länger als bis 23 Uhr spielen darf, was natürlich bei längeren Vorstellungen ein Problem darstellen kann, so musste für die Aufführung des Sportstücks eine unüblich frühe Beginnzeit angesetzt werden. Es gibt etwa fünfzig Billeteure, die aber von einer anderen Firma angestellt sind. Als besonders wichtige Funktion gilt der Posten des Nachtfeuerwehrmannes. Für die Technische Gesamtleitung ist Johann Bugnar, seit der Spielzeit 2009/2010, zuständig. Als Technischer Leiter des Burgtheaters ist Ernst Meissl berufen worden; er ist der Nachfolger von Heinz Filar. Insgesamt (zusammen mit den Mitarbeitern der anderen Firmen, etwa der Kostümwerkstätten) unterstützen etwa 600 Leute die Arbeit an allen Spielstätten. + +Die Arbeit einer gut funktionierenden Komparserie ist für das Gelingen der Inszenierungen auch von großer Wichtigkeit. Beim Burgtheater arbeiten keine Statisten – die Laiendarsteller werden ausschließlich als Komparsen bezeichnet. Der Leiter der Komparserie ist seit 1986 Wolfgang Janich, der selbst eine Schauspielausbildung absolvierte und im Notfall auch einspringt.[39] In manchen Stücken tritt sogar der Chefinspizient, Klaus von Schwerin, der früher jahrelang an der Berliner Schaubühne arbeitete, als Komparse auf. Die Komparsen werden durch mehrstufige Castingverfahren sehr sorgfältig ausgewählt, und sogar die Begleiter der mitwirkenden Tiere werden Komparsen genannt und für die Betreuung der Tiere bezahlt. Claus Peymann wollte die Komparserie abschaffen und die Komparsenrollen mit Schauspielern besetzen, was diese allerdings verweigerten.[40] + +Anerkennung der schauspielerischen Leistung[Bearbeiten] +Anerkennung der Schauspieler im Ensemble[Bearbeiten] +Ensemblemitglied des Burgtheaters zu sein ist wohl der Traum für viele Schauspieler und gehört zu den Höhenpunkten der Karriere im Leben eines Künstlers auf der Bühne. Für besondere Leistungen werden die Ensemblemitglieder des Burgtheater noch zusätzlich geehrt. + +Eine Form der Ehrung ist der Burgtheater-Ring (der Concordia). Der Ring wurde von Jakob Lippowitz, dem Herausgeber des Neuen Wiener Journals, gestiftet und zwischen 1926 – dem Jahr des 150-jährigen Jubiläums der Burgtheater-Gründung – und 1934 jährlich an ein Mitglied des Burgtheaters oder an einen Bühnenautor für besondere Verdienste verliehen. Er wurde erstmals Arthur Schnitzler und Auguste Wilbrandt-Baudius zuerkannt und in den Folgejahren an die Dramatiker Hermann Bahr, Gerhart Hauptmann, Karl Schönherr, Ludwig Fulda sowie die Schauspieler Max Devrient, Georg Reimers, Hedwig Bleibtreu und Else Wohlgemuth verliehen. +Der Ehrenring des Burgtheaters wird seit 1. Oktober 1955 in unregelmäßigen Abständen von der Kollegenschaft des Burgtheaters an Ensemblemitglieder verliehen. Diese Auszeichnung wird vom Betriebsrat vorschlagen. +Ehrenmitglieder werden besonders verdiente Künstler seit 1922. Anlass war damals das vierzigjährige Bühnenjubiläum von Max Devrient. Zusammen mit ihm wurden Hugo Thimig, Georg Reimers sowie Auguste Wilbrandt-Baudius zu Ehrenmitgliedern ernannt. Die Ernennung erfolgte im Einverständnis mit der Bundestheaterverwaltung über Vorschlag der Burgtheater-Direktion. Diesen ersten Ernennungen folgten 1926, anlässlich des 150. Geburtstags des Burgtheaters, die offiziellen Richtlinien. Ihnen zufolge dürfen nur sehr prominente, verdienstvolle Ensemblemitglieder und auch diese erst nach langjähriger Zugehörigkeit zum Haus dieser Ehre teilhaftig werden. Die Zahl der lebenden Ehrenmitglieder sollte ein Zehntel des gesamten Mitgliederstandes nicht überschreiten. Der Titel darf in Wort oder Schrift nicht verwendet werden, wenn die Träger bei Veranstaltungen mitwirken, die mit der Würde des Titels unvereinbar wären, also wenn sie etwa in Operetten, Varietés oder Kabaretts auftreten. Die Namen der Ehrenmitglieder werden am Fuß der Feststiege auf der Volksgartenseite in Marmor gemeißelt und für die Ewigkeit festgehalten. Auch für das Begräbnis der Ehrenmitglieder gelten besondere Gepflogenheiten. Der Sarg des verstorbenen Schauspielers oder der verstorbenen Schauspielerin wird auf der Feststiege aufgebahrt und anschließend einmal rund um das Theater getragen. Den Ehrenmitgliedern wird seit dem 20. Oktober 2010 auch ein sog. Ehrenring der Direktion (nicht identisch mit dem Ehrenring des Burgtheaters, der vom Betriebsrat verliehen wird) überreicht (bis dahin wurde ihnen eine Urkunde überreicht). Der erste Träger des Ringes ist Michael Heltau. Der Ring wurde vom Juweilier Wagner entworfen und zeigt die Fassade des Burgtheaters. +Der Doyen (derzeit Michael Heltau) oder die Doyenne (seit 2015 Elisabeth Orth[41]) des Burgtheaters (von 2001 bis 2014 war dies Annemarie Düringer[42] und von 1987 bis 2000 Paula Wessely[43]) bleibt lebenslang mit der Bühne des Hauses verbunden, genießt also ein Engagement bis zum Tode, das heißt, er oder sie kann nicht in den Ruhestand versetzt werden. Diesen Ehrentitel bekommen zwei von den älteren Ehrenmitgliedern des Hauses – meistens sind das die dienstältesten, die schon am längsten Ensemblemitglieder sind –, jeweils eine Dame und ein Herr, die dann die Aufgabe haben, das Haus nach außen zu vertreten. Eine Voraussetzung für diese Auszeichnung ist, dass man zuvor zur Kammerschauspielerin beziehungsweise zum Kammerschauspieler ernannt worden sein muss. Die Auszeichnung wird erst nach dem Tod ihres Trägers weitergegeben. +Die Burgtheater-Galerie ist eine Sammlung von Künstlerporträts im Foyer, eine Art „Ehrengalerie“, die zum Teil noch aus dem alten Gebäude am Michaelerplatz stammt. Seit dem 23. Oktober 2006 zieren nicht nur alte Gemälde die Wände. Zeitgenössische Künstler porträtierten 2006 einige der heutigen Publikumslieblinge: Christy Astuy verewigte Annemarie Düringer und Branko Samarovski, Franz Graf zeichnete Elisabeth Orth und Ignaz Kirchner, die Fotografin Ilse Haider inszenierte fotografisch Andrea Clausen und Michael Heltau, Josef Kern malte Karlheinz Hackl und Martin Schwab, Klaus Maria Brandauer und Kirsten Dene wurden von Elke Krystufek porträtiert. Kitty Speiser und Gert Voss wurden in eigens dafür geschaffenen Rauminstallationen von Gregor Zivic dargestellt.[44] +Auf Anregung von Burg-Direktor Franz Herterich schuf Bundespräsident Michael Hainisch 1926 den Kammer-Titel für darstellende und ausübende Künstler auf dem Gebiet der Musik und darstellenden Kunst. Seit dieser Zeit gibt es in Österreich Kammerschauspieler und Kammersänger. Die ersten österreichischen Kammerschauspieler waren Maria Mayen, Maria Mayer, Raoul Aslan und Willi Thaller. Sie alle erhielten den Titel noch im Jubiläumsjahr 1926. In späteren Jahren blieb der Titel nicht mehr auf Mitglieder des Burgtheaters beschränkt, sondern wurde, wenngleich selten, auch an solche des Theaters in der Josefstadt und anderer österreichischer Bühnen vergeben. +Der Nestroy-Theaterpreis[Bearbeiten] + +Die Ausgezeichneten des Nestroy-Theaterpreises 2010, darunter Direktor Matthias Hartmann, auf der Bühne des Burgtheaters +Das Burgtheater (inkl. Akademietheater) ist mit seinen Produktionen seit 2000 das erfolgreichste Theater beim Nestroy-Theaterpreis. + +Nestroy-Theaterpreis 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 +Nominierungen/Siege 10/5 15/6 10/3 9/4 13/5 12/2 12/6 10/4 8/1 6/4 10/6 8/3 11/3 10/3 6/2 +Das Burgtheater im In- und Ausland[Bearbeiten] +Jedes Jahr gibt es Inszenierungen für die Salzburger Festspiele und für die Wiener Festwochen in Koproduktion mit dem Burgtheater, bei letzteren sind die Burg- und Akademietheater auch eine wichtige Spielstätte. Diese Produktionen werden in der Regel nach der Festspielzeit ins Repertoire aufgenommen. + +Produktionen des Burgtheaters werden regelmäßig zum Berliner Theatertreffen zu anderen internationalen Theaterfestivals eingeladen und die Burg wird auch im Ausland in Gastspielen gern gesehen, vor allem natürlich im deutschsprachigen Raum, aber auch in anderen Ländern. In den letzten Jahrzehnten gastierten Burg-Produktionen unter anderem in Amsterdam, Avignon, Berlin, Bogotá, Edinburgh, Meran, Moskau, Mülheim an der Ruhr, Prag, Venedig, Rom, Warschau und Zagreb. Erwähnenswert sind große Tourneen durch Japan und die Sowjetunion sowie Gastspiele in Israel und New York.[45] Ins Burgtheater werden auch oft Inszenierungen aus bedeutenden europäischen Häusern eingeladen, wie aus dem Thalia Theater in Hamburg. + +Das Burgtheater dient im Sommer als eine Spielstätte für das Tanzfestival ImPulsTanz, das mittlerweile als größtes europäisches Tanzereignis gilt. + +Von der Theaterzeitschrift Theater heute wurde das Burgtheater 2015 und 1995 zum Theater des Jahres gewählt.[46][47] + +Die Leitung des Burgtheaters[Bearbeiten] +Als Kaiser Joseph II. das Theater nächst der Burg zum Hof- und Nationaltheater erhob, schuf er jene Dreier-Konstruktion aus oberster Leitung, Verwaltung und künstlerischer Leitung, die im Wesentlichen bis heute erhalten geblieben ist. In der Monarchie lag die oberste Leitung in den Händen des Obersthofmeisteramtes oder des Oberstkämmereramtes. Die Verwaltung hatte wechselnde Titel, Theateroberdirektion oder Generalintendanz. Zudem war das Burgtheater einige Jahre lang verpachtet, wobei die Pächter es nur mit der obersten Instanz zu tun hatten, in Finanzverwaltung und Personalangelegenheiten aber selbständig waren. Für Stückauswahl und Spielpläne waren sie nur gegenüber der Zensur verantwortlich. Die künstlerische Leitung wiederum lag einmal in den Händen eines Regiekollegiums, dann wieder wurde sie einem artistischen Sekretär oder einem artistischen Direktor anvertraut, wurde aber auch interimistisch vom Generalintendanten oder von einem Schauspielerkollegium ausgeübt. + +Laut Gesetz vom 3. April 1919, dem sogenannten Habsburger-Gesetz, ging nach dem Ende der Monarchie das sogenannte hofärarische Vermögen des Hauses Habsburg-Lothringen in das Eigentum der Republik Österreich über. Dazu gehörten auch die Hoftheater, also das k. k. Hofburgtheater, die Hofoper sowie das Schönbrunner Schlosstheater. Dieses Gesetz wurde 1920 Teil der Bundesverfassung. + +Am 21. Mai 1920 unterstellte die österreichische Staatsregierung die „österreichischen Staatstheater“ (so die damalige offizielle Bezeichnung) dem Staatsamt für Inneres und Unterricht. Als Verwaltungsinstanz wurde die Staatstheaterverwaltung eingesetzt. Hier begann erneut eine Dreiteilung. Die oberste Leitung lag beim Unterrichtsminister, die Verwaltung bei der Staatstheaterverwaltung (später: Bundestheaterverwaltung; kurzzeitig auch: Generalintendanz; schließlich Bundestheaterverband; heute Bundestheater-Holding). Die künstlerische Leitung lag beim Direktor (mit unterschiedlich geregelten Befugnissen). + +Direktionen und künstlerische Leitungen des Burgtheaters[Bearbeiten] + +Heinrich Laube, 1849-1867 + +Franz von Dingelstedt, 1870-1881 + +Adolf von Wilbrandt, 1881-1887 + +Alfred von Berger, 1910-1912 + +Claus Peymann, 1986-1999 +→ Hauptartikel: Direktionen des Wiener Burgtheaters +Name Beginn Ende +Künstlerrepublik 1776 1789 +Johann Franz Brockmann 1790 1790 +Regiekollegium 1790 1794 +Peter von Braun 1794 1806 +Kavaliersdirektion 1807 1817 +Joseph Schreyvogel 1814 1832 +Johann Ludwig Deinhardstein 1832 1841 +Franz Ignaz von Holbein 1841 1849 +Heinrich Laube 1849 1867 +Eligius Freiherr von Münch-Bellinghausen; Pseudonym: Friedrich Halm 1867 1868 +August Wolff 1868 1870 +Franz Freiherr von Dingelstedt 1870 1881 +Adolf von Wilbrandt 1881 1887 +Adolf von Sonnenthal 1887 1888 +August Förster 1888 1889 +Adolf von Sonnenthal 1889 1890 +Max Burckhard 1890 1898 +Paul Schlenther 1898 1910 +Alfred Freiherr von Berger 1910 1912 +Hugo Thimig 1912 1917 +Max von Millenkovich 1917 1918 +Dreierkollegium 1918 1918 +Albert Heine 1918 1921 +Anton Wildgans 1921 1922 +Max Paulsen 1922 1923 +Franz Herterich 1923 1930 +Anton Wildgans 1930 1931 +Hermann Röbbeling 1932 1938 +Mirko Jelusich 1938 1938 +Ulrich Bettac 1938 1939 +Lothar Müthel 1939 1945 +Raoul Aslan 1945 1948 +Erhard Buschbeck 1948 1948 +Josef Gielen 1948 1954 +Adolf Rott 1954 1959 +Ernst Haeusserman 1959 1968 +Paul Hoffmann 1968 1971 +Gerhard Klingenberg 1971 1976 +Achim Benning 1976 1986 +Claus Peymann 1986 1999 +Klaus Bachler 1999 2009 +Matthias Hartmann 2009 2014 +Karin Bergmann 2014 2019 +Das Burgtheater in Film und Literatur[Bearbeiten] +Bis 1918 gab es ein Verbot, nach dem Schauspielern des Burgtheaters das Mitwirken in Filmen in jeder Form untersagt war. Das Kino galt als Bedrohung für den Fortbestand der Schauspielbühnen, und so wollte man den Filmproduzenten nicht in die Hand spielen. Eine Entspannung dieser Situation begann erst ab 1913 mit den Produktionen des Theaterintendanten Max Reinhardt. + +Siehe auch: Geschichte des österreichischen Stummfilms +1936 drehte Willi Forst den Spielfilm Burgtheater, der von einem alternden, sich noch einmal verliebenden Burgschauspieler – dargestellt von Werner Krauß – erzählt. Die Zeit der Handlung war 1897, die Personen – wie der „Burgdirektor“ Franz Herterich oder der Schauspieler Friedrich Mitterer, den Krauß verkörpert – waren fiktive Figuren, obwohl der Name Mitterer ein Anspielung auf den Namen des einst tatsächlich gefeierten Schauspielers Friedrich Mitterwurzer ist. Im Film werden Inszenierungen aus den letzten Jahren des 19. Jahrhunderts aufgegriffen, die es tatsächlich gab, Szenen aus Don Carlos, Faust und Kabale und Liebe werden nachgespielt.[48] + +Ab 1956 finanzierte das Unterrichtsministerium eine Reihe von Aufzeichnungen von Theaterstücken des Burgtheaters. Die Aufnahmen sollten im Kino gezeigt werden, weshalb mit Alfred Stöger ein Filmregisseur beauftragt wurde. Auf Filmmaterial wurden interessant besetzte Inszenierungen wie „Wilhelm Tell“ (1956, mit Ewald Balser als „Tell“ und Albin Skoda als „Gessler“), „Einen Jux will er sich machen“ (1957, mit Josef Meinrad und Inge Konradi), „Don Carlos“ (1960, mit Walther Reyer) und „Der Bauer als Millionär“ (1961, abermals mit Josef Meinrad) gebannt. Dennoch blieben die Kinoaufführungen spärlich besucht. Lediglich eine Generation von Schulkindern wurde damit zwangsbeglückt. + +1982 thematisierte Elfriede Jelinek in ihrem Stück Burgtheater die Zeit der NS-Diktatur in der Geschichte des Burgtheaters. Das Stück wurde 1982 in Bonn uraufgeführt sowie in Österreich im Grazer Theater am Bahnhof erstaufgeführt und stieß auf kontroverse Kritiken, weil es ein negatives Bild der damaligen legendären Ensemblemitglieder zeichnete, so von Paula Wessely und Attila Hörbiger, die in den Hauptfiguren klar erkennbar sind. + +Thomas Bernhard beschäftigte sich in seinen Werken immer wieder mit dem Thema Burgtheater, etwa in seinem Roman Holzfällen (1984) – dort lässt er einen saturierten Burgschauspieler auftreten – oder Alte Meister (1985), wo einzelne, zwischen Bewunderung und Verachtung pendelnde Bemerkungen dem Burgtheater gelten. Er schrieb drei Dramolette, in denen Claus Peymann persönlich vorkommt, Claus Peymann verlässt Bochum und geht als Burgtheaterdirektor nach Wien, Claus Peymann kauft sich eine Hose und geht mit mir essen und Claus Peymann und Hermann Beil auf der Sulzwiese, die 1990 erschienen. + +Heute dient das Burgtheater oft als beliebte Kulisse für Fernsehfilme. In der Krimikomödie Wiener Blut von Dirk Regel mit Ottfried Fischer in der Hauptrolle wird ein Burgschauspieler (dargestellt von Markus Hering) ermordet. Die letzten zwanzig Minuten des österreichischen Kinderfilms Die drei Posträuber (Regie: Andreas Prochaska), der 1998 nach einem Buch von Christine Nöstlinger gedreht wurde, spielen auf der Bühne und im Requisitenlager des Burgtheaters. + +Das Burgtheater im Fernsehen[Bearbeiten] +Die interessantesten Produktionen des Burgtheaters werden vom ORF aufgezeichnet und sind in dessen Programm, aber auch auf 3sat oder dem ZDFtheaterkanal zu sehen. Der Fernsehregisseur Peter Schönhofer macht statt bloßer Aufzeichnungen Theaterfilme der etwas anderen Art. Seine zwei bekanntesten sind die Verfilmungen von Don Carlos und von König Ottokars Glück und Ende. Die Dreharbeiten zu seinen Filmen dauern mehrere Tage und umfassen sowohl reguläre Aufführungen als auch Proben, bei denen die Kamerateams die Möglichkeit haben, Szenen direkt auf der Bühne zu drehen. Der Theaterfilm wird somit aus mehreren Vorstellungen zusammengeschnitten und beinhaltet auch Nahaufnahmen und auf der Bühne gefilmte Szenen, die bei herkömmlichen Live-Aufzeichnungen nicht machbar sind. Beim Berliner Theatertreffen 2005 wurde statt der Aufführung von Don Carlos der Theaterfilm von Schönhofer gezeigt, da kein Berliner Theater die technischen Voraussetzungen, die vom Burgtheater für die Inszenierung von Andrea Breth verlangt wurden, erfüllen konnte. Einige Produktionen sind auch auf DVD erhältlich, wenn das Stück nicht mehr gespielt wird. Durch Dokumentarfilme kann der Fernsehzuschauer ab und zu Einblick hinter die Kulissen gewinnen, wie im Film der ORF-Moderatorin Erna Cuesta (2005), Das Wiener Burgtheater – Vor und hinter den Kulissen einer Weltbühne. + +Publikationen[Bearbeiten] +Das Burgtheater hat ein Magazin, das vorspiel, das fünfmal jährlich erscheint. Es wird als Beilage der Tageszeitung Der Standard verbreitet, kann aber in den Spielstätten des Burgtheaters und in den größeren Theaterkassen gratis mitgenommen werden. Auf der Homepage findet man jedes vorspiel zum Herunterladen. Beim Deuticke Verlag erscheinen regelmäßig Bücher über wichtige Regisseure, Schauspieler und das Theater als „edition burgtheater“. Bis jetzt sind insgesamt acht Bände erschienen. \ No newline at end of file diff --git a/ch.eitchnet.utils/src/test/resources/crypto_test_middle.txt b/ch.eitchnet.utils/src/test/resources/crypto_test_middle.txt new file mode 100644 index 000000000..cf069255c --- /dev/null +++ b/ch.eitchnet.utils/src/test/resources/crypto_test_middle.txt @@ -0,0 +1,3 @@ +Sodeli jetzt wöi mirs aber mau + + wüsse. © copyright \ No newline at end of file diff --git a/ch.eitchnet.utils/src/test/resources/crypto_test_short.txt b/ch.eitchnet.utils/src/test/resources/crypto_test_short.txt new file mode 100644 index 000000000..cf3e2e345 --- /dev/null +++ b/ch.eitchnet.utils/src/test/resources/crypto_test_short.txt @@ -0,0 +1 @@ +Short \ No newline at end of file diff --git a/ch.eitchnet.utils/src/test/resources/log4j.xml b/ch.eitchnet.utils/src/test/resources/log4j.xml new file mode 100644 index 000000000..0a2a73d06 --- /dev/null +++ b/ch.eitchnet.utils/src/test/resources/log4j.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ch.eitchnet.utils/src/test/resources/test.jks b/ch.eitchnet.utils/src/test/resources/test.jks new file mode 100644 index 000000000..dfb55f343 Binary files /dev/null and b/ch.eitchnet.utils/src/test/resources/test.jks differ diff --git a/ch.eitchnet.utils/src/test/resources/test_data.csv b/ch.eitchnet.utils/src/test/resources/test_data.csv new file mode 100644 index 000000000..d6b101a1e --- /dev/null +++ b/ch.eitchnet.utils/src/test/resources/test_data.csv @@ -0,0 +1,6 @@ +#Comment +title;description;nrOfPages +A;a;1 +# Another comment +B;b;2 +C;c;3