Merge remote-tracking branch 'utils/develop' into develop

This commit is contained in:
Robert von Burg 2016-06-24 17:39:44 +02:00
commit 978afb6ba9
119 changed files with 18158 additions and 0 deletions

202
ch.eitchnet.utils/LICENSE Normal file
View File

@ -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.

View File

@ -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

72
ch.eitchnet.utils/pom.xml Normal file
View File

@ -0,0 +1,72 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.eitchnet</groupId>
<artifactId>ch.eitchnet.parent</artifactId>
<version>1.1.0-SNAPSHOT</version>
<relativePath>../ch.eitchnet.parent/pom.xml</relativePath>
</parent>
<artifactId>ch.eitchnet.utils</artifactId>
<packaging>jar</packaging>
<name>ch.eitchnet.utils</name>
<description>These utils contain project independent helper classes and utilities for reuse</description>
<url>https://github.com/eitchnet/ch.eitchnet.utils</url>
<inceptionYear>2011</inceptionYear>
<issueManagement>
<system>Github Issues</system>
<url>https://github.com/eitchnet/ch.eitchnet.utils/issues</url>
</issueManagement>
<scm>
<connection>scm:git:https://github.com/eitchnet/ch.eitchnet.utils.git</connection>
<developerConnection>scm:git:git@github.com:eitchnet/ch.eitchnet.utils.git</developerConnection>
<url>https://github.com/eitchnet/ch.eitchnet.utils</url>
<tag>HEAD</tag>
</scm>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
</plugin>
<plugin>
<!-- explicitly define maven-deploy-plugin after other to force exec order -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,68 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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;
}
}

View File

@ -0,0 +1,405 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class CommunicationConnection implements Runnable {
protected static final Logger logger = LoggerFactory.getLogger(CommunicationConnection.class);
private String id;
private ConnectionMode mode;
private Map<String, String> parameters;
private ConnectionState state;
private String stateMsg;
private BlockingDeque<IoMessage> messageQueue;
private Thread queueThread;
private volatile boolean run;
private MapOfLists<CommandKey, ConnectionObserver> connectionObservers;
private List<ConnectionStateObserver> connectionStateObservers;
private CommunicationEndpoint endpoint;
private IoMessageVisitor messageVisitor;
private IoMessageArchive archive;
public CommunicationConnection(String id, ConnectionMode mode, Map<String, String> 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<String, String> 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<ConnectionStateObserver> 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<ConnectionObserver> observers;
synchronized (this.connectionObservers) {
List<ConnectionObserver> 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);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}

View File

@ -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
* <http://www.gnu.org/licenses/>.
*/
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 <eitch@eitchnet.ch>
*/
@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;
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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<String, String> 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<? extends CommunicationEndpoint> endpoint,
Class<? extends IoMessageVisitor> 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);
}
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* 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
* </p>
* The modes have the following semantics:
* <ul>
* <li>OFF - the connection can only have states {@link ConnectionState#CREATED} and {@link ConnectionState#INITIALIZED}
* . Trying to use the connection will throw an exception</li>
* <li>ON - the connection can be used normally</li>
* <li>SIMULATION - the same as ON, with the difference that the connection should silently drop any work</li>
* </ul>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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();
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public interface ConnectionObserver {
public void notify(CommandKey key, IoMessage message);
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* a {@link CommunicationConnection} undergoes a serious of state changes. These states can be viewed by a client to
* monitor the state of the connection
* </p>
* The states have the following semantics:
* <ul>
* <li>CREATED - initial state</li>
* <li>INITIALIZED - the appropriate connection parameters are found</li>
* <li>CONNECTING - the connection is trying to build up a connection</li>
* <li>WAITING - the connection is waiting before retrying to connect</li>
* <li>CONNECTED - the connection has just been established</li>
* <li>IDLE - the connection is connected, but waiting for work</li>
* <li>WORKING - the connection is working</li>
* <li>BROKEN - the connection has lost the connection and is waiting before reconnecting, or another unknown failure
* occurred</li>
* <li>DISCONNECTED - the connection has been disconnected</li>
* </ul>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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;
}

View File

@ -0,0 +1,6 @@
package ch.eitchnet.communication;
public interface ConnectionStateObserver {
public void notify(ConnectionState oldState, String oldStateMsg, ConnectionState newState, String newStateMsg);
}

View File

@ -0,0 +1,215 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* 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.
* </p>
*
* <p>
* This class also contains a {@link Map} to store transient meta data to the actual payload
* </p>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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<String, Object> 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> 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> T removeParam(String key) {
return (T) this.parameters.remove(key);
}
/**
* @return the set of parameter keys
*/
public Set<String> 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
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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<IoMessage> getAll();
public List<IoMessage> getBy(String connectionId);
public List<IoMessage> getBy(String connectionId, CommandKey key);
public void clearArchive();
public void archive(IoMessage message);
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* 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}.
* </p>
*
* <p>
* Concrete implementations must be thread safe!
* </p>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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<IoMessage> messageArchive;
public SimpleMessageArchive() {
this(1000, 100);
}
public SimpleMessageArchive(int maxSize, int trimSize) {
this.maxSize = maxSize;
this.trimSize = trimSize;
this.messageArchive = new TreeSet<>(new Comparator<IoMessage>() {
@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<IoMessage> getAll() {
List<IoMessage> all = new ArrayList<>(this.messageArchive);
return all;
}
@Override
public synchronized List<IoMessage> getBy(String connectionId) {
List<IoMessage> all = new ArrayList<>();
for (IoMessage msg : this.messageArchive) {
if (msg.getConnectionId().equals(connectionId))
all.add(msg);
}
return all;
}
@Override
public synchronized List<IoMessage> getBy(String connectionId, CommandKey key) {
List<IoMessage> 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<IoMessage> iter = this.messageArchive.iterator();
for (int i = 0; i <= this.trimSize; i++) {
if (iter.hasNext()) {
iter.next();
iter.remove();
}
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* {@link IoMessageVisitor} to read or write using IO Streams.
* </p>
*
* <p>
* Concrete implementations must be thread safe!
* </p>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public abstract class StreamMessageVisitor extends IoMessageVisitor {
public abstract void visit(OutputStream outputStream, IoMessage message) throws Exception;
public abstract IoMessage visit(InputStream inputStream) throws Exception;
}

View File

@ -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<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); interfaces
.hasMoreElements();) {
NetworkInterface iface = interfaces.nextElement();
for (Enumeration<InetAddress> 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 <local_address> <port> <username>"); //$NON-NLS-1$
System.err.println("Usage: java ...Chat client <server> <port> <username>"); //$NON-NLS-1$
System.exit(1);
}
}

View File

@ -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<String, String> 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();
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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<String, String> 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();
}
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
}

View File

@ -0,0 +1,251 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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
* <ul>
* <li>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</li>
* </ul>
*/
@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<String, String> 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);
}
}
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
}
}

View File

@ -0,0 +1,538 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* 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
* </p>
* <p>
* This endpoint is maintained as a client connection. This means that this endpoint opens the {@link Socket} to the
* remote server
* </p>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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)));
}
}
/**
* <p>
* Configures this {@link ClientSocketEndpoint}
* </p>
* gets the parameter map from the connection and reads the following parameters from the map:
* <ul>
* <li>remoteInputAddress - the IP or Hostname of the remote server</li>
* <li>remoteInputPort - the port to which the socket should be established</li>
* <li>localOutputAddress - the IP or Hostname of the local server (if null, then the network layer will decide)</li>
* <li>localOutputPort - the local port from which the socket should go out of (if null, then the network layer will
* decide)</li>
* <li>retry - a configured retry wait time. Default is {@link SocketEndpointConstants#RETRY}</li>
* <li>timeout - the timeout after which an idle socket is deemed dead. Default is
* {@link SocketEndpointConstants#TIMEOUT}</li>
* <li>useTimeout - if true, then the timeout is activated, otherwise it is. default is
* {@link SocketEndpointConstants#USE_TIMEOUT}</li>
* <li>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}</li>
* <li>connectOnStart - if true, then when the connection is started, the connection to the remote address is
* attempted. default is {@link SocketEndpointConstants#CONNECT_ON_START}
* </ul>
*
* @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<String, String> 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();
}
}
}
}
}

View File

@ -0,0 +1,594 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* 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
* </p>
* <p>
* This end point only allows a single connection at a time and implements all exception handling for opening and
* closing a {@link Socket} connection
* </p>
*
* @see ServerSocketEndpoint#configure(CommunicationConnection, IoMessageVisitor) for details on configuring the end
* point
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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)));
}
}
/**
* <p>
* Configures this {@link ServerSocketEndpoint}
* </p>
* gets the parameter map from the connection and reads the following parameters from the map:
* <ul>
* <li>localInputAddress - the local IP or Hostname to bind to for incoming connections</li>
* <li>localInputPort - the local port on which to listen for incoming connections</li>
* <li>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</li>
* <li>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</li>
* <li>retry - a configured retry wait time. Default is {@link SocketEndpointConstants#RETRY}</li>
* <li>timeout - the timeout after which an idle socket is deemed dead. Default is
* {@link SocketEndpointConstants#TIMEOUT}</li>
* <li>useTimeout - if true, then the timeout is activated. default is {@link SocketEndpointConstants#USE_TIMEOUT}</li>
* <li>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}</li>
* </ul>
*
* @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<String, String> 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()));
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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;
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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;
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class DbConnectionCheck {
private static final Logger logger = LoggerFactory.getLogger(DbConnectionCheck.class);
private Map<String, DataSource> dsMap;
/**
* @param dsMap
*/
public DbConnectionCheck(Map<String, DataSource> dsMap) {
this.dsMap = dsMap;
}
/**
* Checks the connectivity to each of the configured {@link DbConnectionInfo}
*
* @throws DbException
*/
public void checkConnections() throws DbException {
Collection<DataSource> 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");
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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";
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public interface DbDataSourceBuilder {
public DataSource build(String realm, String url, String username, String password, Properties properties);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public enum DbMigrationState {
NOTHING, CREATED, MIGRATED, DROPPED_CREATED;
}

View File

@ -0,0 +1,363 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
@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<String, DbMigrationState> 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<String, DbMigrationState> getDbMigrationStates() {
return this.dbMigrationStates;
}
public void checkSchemaVersion(Map<String, DataSource> dsMap) throws DbException {
for (Entry<String, DataSource> 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));
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*
*/
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;
}

View File

@ -0,0 +1,238 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*
*/
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);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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;
}
}

View File

@ -0,0 +1,307 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}
}

View File

@ -0,0 +1,161 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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;
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
/**
* <p>
* Returns {@link StringMatchMode#EQUALS_CASE_SENSITIVE}
* </p>
*
* <p>
* Short method, useful for static imports, or simply for shorter code
* </p>
*
* @return {@link StringMatchMode#EQUALS_CASE_SENSITIVE}
*/
public static StringMatchMode es() {
return EQUALS_CASE_SENSITIVE;
}
/**
* <p>
* Returns {@link #EQUALS_CASE_INSENSITIVE}
* </p>
*
* <p>
* Short method, useful for static imports, or simply for shorter code
* </p>
*
* @return {@link #EQUALS_CASE_INSENSITIVE}
*/
public static StringMatchMode ei() {
return EQUALS_CASE_INSENSITIVE;
}
/**
* <p>
* Returns {@link #CONTAINS_CASE_SENSITIVE}
* </p>
*
* <p>
* Short method, useful for static imports, or simply for shorter code
* </p>
*
* @return {@link #CONTAINS_CASE_SENSITIVE}
*/
public static StringMatchMode cs() {
return CONTAINS_CASE_SENSITIVE;
}
/**
* <p>
* Returns {@link #CONTAINS_CASE_INSENSITIVE}
* </p>
*
* <p>
* Short method, useful for static imports, or simply for shorter code
* </p>
*
* @return {@link #CONTAINS_CASE_INSENSITIVE}
*/
public static StringMatchMode ci() {
return CONTAINS_CASE_INSENSITIVE;
}
}

View File

@ -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.
*
* <p>
* Version identifiers have four components.
* <ol>
* <li>Major version. A non-negative integer.</li>
* <li>Minor version. A non-negative integer.</li>
* <li>Micro version. A non-negative integer.</li>
* <li>Qualifier. A text string. See {@code Version(String)} for the format of the qualifier string.</li>
* </ol>
*
* <b>Note:</b> 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}.
*
* <p>
* The grammar for parsing version strings is as follows:
*
* <pre>
* version ::= major('.'minor('.'micro('.'qualifier)?)?)?
* major ::= digit+
* minor ::= digit+
* micro ::= digit+
* qualifier ::= (alpha|digit|'_'|'-')+
* digit ::= [0..9]
* alpha ::= [a..zA..Z]
* </pre>
*
* <b>Note:</b> There must be no whitespace in version.
* </p>
*
* <p>
* <b>Note:</b> {@code Version} objects are immutable and thus thread safe
* </p>
*/
public class Version implements Comparable<Version> {
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
*
* <p>
* 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();
}
/**
* <p>
* Creates a version identifier from the specified string.
* </p>
*
* @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.
*
* <p>
* 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.
*
* <p>
* A version is considered to be <b>equal to </b> 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.
*
* <p>
* A version is considered to be <b>equal to </b> 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}.
*
* <p>
* A version is considered to be <b>less than </b> 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}).
*
* <p>
* A version is considered to be <b>equal to</b> 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.
*
* <p>
* 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();
}
}

View File

@ -0,0 +1,133 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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)");
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2015 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class DefaultedHashMap<K, V> extends HashMap<K, V> {
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);
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class MapOfLists<T, U> {
private Map<T, List<U>> mapOfLists;
public MapOfLists() {
this.mapOfLists = new HashMap<>();
}
public Set<T> keySet() {
return this.mapOfLists.keySet();
}
public List<U> getList(T t) {
return this.mapOfLists.get(t);
}
public boolean addElement(T t, U u) {
List<U> 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> u) {
List<U> 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<U> 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<U> removeList(T t) {
return this.mapOfLists.remove(t);
}
public void clear() {
Set<Entry<T, List<U>>> entrySet = this.mapOfLists.entrySet();
Iterator<Entry<T, List<U>>> 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<U> 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<Entry<T, List<U>>> entrySet = this.mapOfLists.entrySet();
Iterator<Entry<T, List<U>>> iter = entrySet.iterator();
while (iter.hasNext()) {
size += iter.next().getValue().size();
}
return size;
}
public int size(T t) {
List<U> list = this.mapOfLists.get(t);
if (list.size() == 0)
return 0;
return list.size();
}
public boolean isEmpty() {
return this.mapOfLists.isEmpty();
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* Collection to store a tree with a depth of 3 elements. This solves having to always write the declaration:
* </p>
*
* <pre>
* Map&lt;String, Map&lgt;String, MyObject&gt;&gt; mapOfMaps = new HashMap&lt;&gt;;
* </pre>
*
* <p>
* As an example to persist a map of MyObject where the branches are String one would write it as follows:
* </p>
*
* <pre>
* MapOfMaps&lt;String, String, MyObject&gt; mapOfMaps = new MapOfMaps&lt;&gt;();
* </pre>
*
*
* @author Robert von Burg <eitch@eitchnet.ch>
*
* @param <T>
* The key to a map with U as the key and V as the value
* @param <U>
* The key to get a value (leaf)
* @param <V>
* The value stored in the tree (leaf)
*/
public class MapOfMaps<T, U, V> {
private Map<T, Map<U, V>> mapOfMaps;
public MapOfMaps() {
this.mapOfMaps = new HashMap<>();
}
public Set<T> keySet() {
return this.mapOfMaps.keySet();
}
public Map<U, V> getMap(T t) {
return this.mapOfMaps.get(t);
}
public V getElement(T t, U u) {
Map<U, V> map = this.mapOfMaps.get(t);
if (map == null)
return null;
return map.get(u);
}
public V addElement(T t, U u, V v) {
Map<U, V> map = this.mapOfMaps.get(t);
if (map == null) {
map = new HashMap<>();
this.mapOfMaps.put(t, map);
}
return map.put(u, v);
}
public List<V> getAllElements() {
List<V> all = new ArrayList<>();
for (Map<U, V> u : this.mapOfMaps.values()) {
all.addAll(u.values());
}
return all;
}
public List<V> getAllElements(T t) {
List<V> all = new ArrayList<>();
Map<U, V> map = this.mapOfMaps.get(t);
if (map != null) {
all.addAll(map.values());
}
return all;
}
public void addMap(T t, Map<U, V> u) {
Map<U, V> 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<U, V> 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<U, V> removeMap(T t) {
return this.mapOfMaps.remove(t);
}
public void clear() {
Set<Entry<T, Map<U, V>>> entrySet = this.mapOfMaps.entrySet();
Iterator<Entry<T, Map<U, V>>> 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<U, V> 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<Entry<T, Map<U, V>>> entrySet = this.mapOfMaps.entrySet();
Iterator<Entry<T, Map<U, V>>> iter = entrySet.iterator();
while (iter.hasNext()) {
size += iter.next().getValue().size();
}
return size;
}
public int size(T t) {
Map<U, V> map = this.mapOfMaps.get(t);
if (map == null)
return 0;
return map.size();
}
public boolean isEmpty() {
return this.mapOfMaps.isEmpty();
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2015 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class Paging<T> {
private int pageSize;
private int pageToReturn;
private int nrOfPages;
private int nrOfElements;
private List<T> input;
private List<T> 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<T> getInput() {
return this.input;
}
public List<T> 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 <T> Paging<T> asPage(List<T> list, int pageSize, int page) {
Paging<T> 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;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class Tuple {
private Object first;
private Object second;
public Tuple() {
//
}
public <T, U> Tuple(T first, U second) {
this.first = first;
this.second = second;
}
@SuppressWarnings("unchecked")
public <T> T getFirst() {
return (T) this.first;
}
public <T> void setFirst(T first) {
this.first = first;
}
@SuppressWarnings("unchecked")
public <U> U getSecond() {
return (U) this.second;
}
public <U> void setSecond(U second) {
this.second = second;
}
}

View File

@ -0,0 +1,166 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public enum DBC {
PRE, INTERIM, POST;
public <T> 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 <T> 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);
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}

View File

@ -0,0 +1,322 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class AsciiHelper {
/**
* ASCII Value 0, interpretation: NUL <br />
* Description: Null character
*/
public static final char NUL = (char) 0; // Null character
/**
* ASCII Value 1, interpretation: SOH<br />
* Description: Start of Header
*/
public static final char SOH = (char) 1; // Start of Header
/**
* ASCII Value 2, interpretation: STX<br />
* Description: Start of Text
*/
public static final char STX = (char) 2; // Start of Text
/**
* ASCII Value 3, interpretation: ETX<br />
* Description: End of Text
*/
public static final char ETX = (char) 3; // End of Text
/**
* ASCII Value 4, interpretation: EOT<br />
* Description: End of Transmission
*/
public static final char EOT = (char) 4; // End of Transmission
/**
* ASCII Value 5, interpretation: ENQ<br />
* Description: Enquiry
*/
public static final char ENQ = (char) 5; // Enquiry
/**
* ASCII Value 6, interpretation: ACK<br />
* Description: Acknowledgement
*/
public static final char ACK = (char) 6; // Acknowledgement
/**
* ASCII Value 7, interpretation: BEL<br />
* Description: Bell
*/
public static final char BEL = (char) 7; // Bell
/**
* ASCII Value 8, interpretation: BS<br />
* Description: Backspace
*/
public static final char BS = (char) 8; // Backspace
/**
* ASCII Value 9, interpretation: HT<br />
* Description: Horizontal Tab
*/
public static final char HT = (char) 9; // Horizontal Tab
/**
* ASCII Value 10, interpretation: LF<br />
* Description: Line Feed
*/
public static final char LF = (char) 10; // Line Feed
/**
* ASCII Value 11, interpretation: VT<br />
* Description: Vertical Tab
*/
public static final char VT = (char) 11; // Vertical Tab
/**
* ASCII Value 12, interpretation: FF<br />
* Description: Form Feed
*/
public static final char FF = (char) 12; // Form Feed
/**
* ASCII Value 13, interpretation: CR<br />
* Description: Carriage Return
*/
public static final char CR = (char) 13; // Carriage Return
/**
* ASCII Value 14, interpretation: SO<br />
* Description: Shift Out
*/
public static final char SO = (char) 14; // Shift Out
/**
* ASCII Value 15, interpretation: SI<br />
* Description: Shift In
*/
public static final char SI = (char) 15; // Shift In
/**
* ASCII Value 16, interpretation: DLE<br />
* Description: Data Link Escape
*/
public static final char DLE = (char) 16; // Data Link Escape
/**
* ASCII Value 17, interpretation: DC1<br />
* Description: (XON) Device Control 1
*/
public static final char DC1 = (char) 17; // (XON) Device Control 1
/**
* ASCII Value 18, interpretation: DC2<br />
* Description: Device Control 2
*/
public static final char DC2 = (char) 18; // Device Control 2
/**
* ASCII Value 19 interpretation: DC3<br />
* Description: (XOFF) Device Control 3
*/
public static final char DC3 = (char) 19; // (XOFF) Device Control 3
/**
* ASCII Value 20, interpretation: DC4<br />
* Description: Device Control 4
*/
public static final char DC4 = (char) 20; // Device Control 4
/**
* ASCII Value 21, interpretation: NAK<br />
* Description: Negative Acknowledgment
*/
public static final char NAK = (char) 21; // Negative Acknowledgment
/**
* ASCII Value 22, interpretation: SYN<br />
* Description: Synchronous Idle
*/
public static final char SYN = (char) 22; // Synchronous Idle
/**
* ASCII Value 23, interpretation: ETB<br />
* Description: End of Transmission Block
*/
public static final char ETB = (char) 23; // End of Transmission Block
/**
* ASCII Value 24, interpretation: CAN<br />
* Description: Cancel
*/
public static final char CAN = (char) 24; // Cancel
/**
* ASCII Value 25, interpretation: EM<br />
* Description: End of Medium
*/
public static final char EM = (char) 25; // End of Medium
/**
* ASCII Value 26, interpretation: SUB<br />
* Description: Substitute
*/
public static final char SUB = (char) 26; // Substitute
/**
* ASCII Value 27, interpretation: ESC<br />
* Description: Escape
*/
public static final char ESC = (char) 27; // Escape
/**
* ASCII Value 28, interpretation: FS<br />
* Description: File Separator
*/
public static final char FS = (char) 28; // File Separator
/**
* ASCII Value 29, interpretation: GS<br />
* Description: Group Separator
*/
public static final char GS = (char) 29; // Group Separator
/**
* ASCII Value 30, interpretation: RS<br />
* Description: Request to Send (Record Separator)
*/
public static final char RS = (char) 30; // Request to Send (Record Separator)
/**
* ASCII Value 31, interpretation: US<br />
* Description: Unit Separator
*/
public static final char US = (char) 31; // Unit Separator
/**
* ASCII Value 32, interpretation: SP<br />
* Description: Space
*/
public static final char SP = (char) 32; // Space
/**
* ASCII Value 127, interpretation: DEL<br />
* 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 + ")";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,267 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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();
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class ClassHelper {
/**
* Returns an instance of the class' name given by instantiating the class through an empty arguments constructor
*
* @param <T>
* 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> T instantiateClass(String className) throws IllegalArgumentException {
try {
Class<T> clazz = (Class<T>) 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 <T>
* 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> T instantiateClass(Class<T> 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 <T>
* 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 <T> Class<T> loadClass(String className) throws IllegalArgumentException {
try {
Class<T> clazz = (Class<T>) 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);
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright 2015 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class ExceptionHelper {
/**
* <p>
* Returns a message for the given {@link Throwable}
* </p>
*
* <p>
* A {@link NullPointerException} only has <code>null</code> as the message so this methods returns the class name
* in such a case
* </p>
*
* @param t
* @return
*/
public static String getExceptionMessage(Throwable t) {
return StringHelper.isEmpty(t.getMessage()) ? t.getClass().getName() : t.getMessage();
}
/**
* <p>
* Returns a message for the given {@link Throwable}
* </p>
*
* <p>
* A {@link NullPointerException} only has <code>null</code> as the message so this methods returns the class name
* in such a case
* </p>
*
* @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;
}
}

View File

@ -0,0 +1,577 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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;
}
/**
* <p>
* Copy a given list of {@link File Files}. Recursively copies the files and directories to the destination.
* </p>
*
* @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 <b>true</b> if and only if the copying succeeded; <b>false</b> 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 <b>true</b> if and only if the renaming succeeded; <b>false</b> 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 <b>true</b> if and only if the renaming succeeded; <b>false</b> 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<File> 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<File> 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<File> 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$
}
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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 <smock.martin@gmail.com>
* @author Michael Gatto <michael@gatto.ch>
*/
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.
* <p>
* 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.
* <p>
* 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);
}
/**
* <p>
* Rounds the given double value to the number of decimals
* </p>
*
* <p>
* <b>Warning:</b> Do not use the returned value for further calculations. Always finish calculates and use one of
* the following methods:
* <ul>
* <li>{@link #isEqualPrecision(double, double)},</li>
* <li>{@link #isGreaterPrecision(double, double)} or</li>
* <li>{@link #isSmallerEqualPrecision(double, double)}</li>
* </ul>
* to test on equality or greater than/ smaller than
* </p>
*
* @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);
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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));
}
}
}

View File

@ -0,0 +1,187 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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()));
}
}

View File

@ -0,0 +1,717 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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 <code>prefix</code>{...} 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 <code>$</code> to replace occurrences of <code>$</code>{...}
* @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();
}
/**
* <p>
* 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.
* </p>
*
* <p>
* This additional restriction is important where false should really be caught, not any random vaue for false
* </p>
*
* @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;
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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();
}
}

View File

@ -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<Transform> 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<Object> 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;
}
}

View File

@ -0,0 +1,284 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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$
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* 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
* </p>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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);
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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);
}
}

View File

@ -0,0 +1,161 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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;
/**
* <p>
* 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
* </p>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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 <smock.martin@gmail.com>
*/
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);
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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 <smock.martin@gmail.com>
*/
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);
}

View File

@ -0,0 +1,142 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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 <smock.martin@gmail.com>
*/
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);
}

View File

@ -0,0 +1,297 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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 <smock.martin@gmail.com>
*/
@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);
}
}

View File

@ -0,0 +1,264 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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;
/**
* <p>
* 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: <b>PnYnMnDnTnHnMnS</b>
* </p>
* <ul>
* <li>The "P" (period) is required</li>
* <li>"n" represents a positive number</li>
* <li>years is (Y)</li>
* <li>months is (M)</li>
* <li>days is (D)</li>
* <li>time separator is (T), required if any lower terms are given</li>
* <li>hours is (H)</li>
* <li>minutes is (M)</li>
* <li>seconds is (S)</li>
* </ul>
* <p>
* 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: <b><an_element duration="PT2H5M2.37S" /></b> is a 2 hour, 5 minute, and
* 2.37 second duration
* </p>
* <p>
* <b>Remark:</b> 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
* </p>
*
* @author Martin Smock <smock.martin@gmail.com>
* @author Michael Gatto <michael@gatto.ch> (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.
* <p>
* 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();
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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 <smock.martin@gmail.com>
*/
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);
}
}

View File

@ -0,0 +1,237 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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;
/**
* <p>
* 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: <b>PnYnMnDnTnHnMnS</b>
* </p>
* <ul>
* <li>The "P" (period) is required</li>
* <li>"n" represents a positive number</li>
* <li>years is (Y)</li>
* <li>months is (M)</li>
* <li>days is (D)</li>
* <li>time separator is (T), required if any lower terms are given</li>
* <li>hours is (H)</li>
* <li>minutes is (M)</li>
* <li>seconds is (S)</li>
* </ul>
* <p>
* 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: <b><an_element duration="PT2H5M2.37S" /></b> is a 2 hour, 5 minute, and
* 2.37 second duration
* </p>
* <p>
* <b>Remark:</b> 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
* </p>
*
* @author Martin Smock <smock.martin@gmail.com>
* @author Michael Gatto <michael@gatto.ch> (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.
* <p>
* 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();
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2013 Martin Smock <smock.martin@gmail.com>
*
* 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 <smock.martin@gmail.com>
*/
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);
}

View File

@ -0,0 +1,149 @@
/*
* Copyright 2013 Michael Gatto <michael@gatto.ch>
*
* 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.
* <p>
* Thus, this class keeps:
* <ul>
* <li>An ID of the object, such that it can be referenced externally.</li.>
* <li>A key for an object, which keeps the object's type.</li>
* <li>A reference to the current state of the object</li>
* <li>An identifier of the operation that needs to be performed on this</li>
* </ul>
* </p>
*
* @author Michael Gatto <michael@gatto.ch>
* @author Robert von Burg <eitch@eitchnet.ch>
*/
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;
}
}

View File

@ -0,0 +1,693 @@
/*
* Copyright 2013 Michael Gatto <michael@gatto.ch>
*
* 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.
* <p>
* In its current implementation, any instance of an object may "live" in the cache registered only under one single
* key.
* </p>
* <p>
* 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.
* </p>
* <table border="1">
* <tr>
* <td>Action \ State in Cache</td>
* <td>N/A</td>
* <td>Add(01)</td>
* <td>Update(O1)</td>
* <td>Remove(O1)</td>
* </tr>
* <tr>
* <td>Add (O2)</td>
* <td>Add(O2)</td>
* <td>Err!</td>
* <td>Err!</td>
* <td>Update(O2)</td>
* </tr>
* <tr>
* <td>Update (O2)</td>
* <td>Update(O2)</td>
* <td>Add(O2)</td>
* <td>Update(O2)</td>
* <td>Err!</td>
* </tr>
* <tr>
* <td>Remove (O2)</td>
* <td>Remove(O2)</td>
* <td>N/A</td>
* <td>Remove(O2)</td>
* <td>Err!</td>
* </tr>
* </table>
*
* @author Michael Gatto <michael@gatto.ch> (initial version)
* @author Robert von Burg <eitch@eitchnet.ch> (minor modifications, refactorings)
*/
public class ObjectFilter {
private final static Logger logger = LoggerFactory.getLogger(ObjectFilter.class);
private static long id = ObjectCache.UNSET;
private final Map<Object, ObjectCache> cache;
private final Set<String> 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.
* <p>
* This is the single point where the updating logic is applied for the cache in case of addition. The logic is:
* <table border="1" >
* <tr>
* <td>Action\State in Cache</td>
* <td>N/A</td>
* <td>Add(01)</td>
* <td>Update(O1)</td>
* <td>Remove(O1)</td>
* </tr>
* <tr>
* <td>Add (O2)</td>
* <td>Add(O2)</td>
* <td>Err!</td>
* <td>Err!</td>
* <td>Update(O2)</td>
* </tr>
* </table>
*
* @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. * </p>
* <table border="1">
* <tr>
* <td>Action \ State in Cache</td>
* <td>N/A</td>
* <td>Add(01)</td>
* <td>Update(O1)</td>
* <td>Remove(O1)</td>
* </tr>
* <tr>
* <td>Update (O2)</td>
* <td>Update(O2)</td>
* <td>Add(O2)</td>
* <td>Update(O2)</td>
* <td>Err!</td>
* </tr>
* </table>
*
* @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. * </p>
* <table border="1">
* <tr>
* <td>Action \ State in Cache</td>
* <td>N/A</td>
* <td>Add(01)</td>
* <td>Update(O1)</td>
* <td>Remove(O1)</td>
* </tr>
* <tr>
* <td>Remove (O2)</td>
* <td>Remove(O2)</td>
* <td>N/A</td>
* <td>Remove(O2)</td>
* <td>Err!</td>
* </tr>
* </table>
*
* @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<Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object> 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<Object> getAdded(String key) {
List<Object> addedObjects = new LinkedList<>();
Collection<ObjectCache> 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 <V extends Object> List<V> getAdded(Class<V> clazz, String key) {
List<V> addedObjects = new LinkedList<>();
Collection<ObjectCache> 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<Object> getUpdated(String key) {
List<Object> updatedObjects = new LinkedList<>();
Collection<ObjectCache> 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 <V extends Object> List<V> getUpdated(Class<V> clazz, String key) {
List<V> updatedObjects = new LinkedList<>();
Collection<ObjectCache> 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<Object> getRemoved(String key) {
List<Object> removedObjects = new LinkedList<>();
Collection<ObjectCache> 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 <V extends Object> List<V> getRemoved(Class<V> clazz, String key) {
List<V> removedObjects = new LinkedList<>();
Collection<ObjectCache> 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 <V extends Object> List<V> getAll(Class<V> clazz, String key) {
List<V> objects = new LinkedList<>();
Collection<ObjectCache> 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 <V extends Object> List<V> getAll(Class<V> clazz) {
List<V> objects = new LinkedList<>();
Collection<ObjectCache> 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<Object> getAll(String key) {
List<Object> allObjects = new LinkedList<>();
Collection<ObjectCache> 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<ObjectCache> getCache(String key) {
List<ObjectCache> allCache = new LinkedList<>();
Collection<ObjectCache> 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<String> 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;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2013 Michael Gatto <michael@gatto.ch>
*
* 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 <michael@gatto.ch>
*/
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;
}

View File

@ -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<XmlKeyValue> valueOf(Map<String, String> map) {
List<XmlKeyValue> keyValues = new ArrayList<>(map.size());
for (Entry<String, String> entry : map.entrySet()) {
keyValues.add(new XmlKeyValue(entry.getKey(), entry.getValue()));
}
return keyValues;
}
public static Map<String, String> toMap(List<XmlKeyValue> values) {
Map<String, String> propertyMap = new HashMap<>(values.size());
for (XmlKeyValue xmlKeyValue : values) {
propertyMap.put(xmlKeyValue.getKey(), xmlKeyValue.getValue());
}
return propertyMap;
}
}

View File

@ -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 <a
* href="http://www.w3.org/TR/xml11/#sec-line-ends">XML</a>.
*/
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();
}

View File

@ -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:
*
* <pre>
* {@link XMLStreamWriter} stream = ...
* stream = new {@link IndentingXMLStreamWriter}(stream);
* stream.writeStartDocument();
* ...
* </pre>
*
* <p>
* The filter inserts characters to format the document as an outline, with nested elements indented. Basically, it
* inserts a line break and whitespace before:
* <ul>
* <li>each DTD, processing instruction or comment that's not preceded by data</li>
* <li>each starting tag that's not preceded by data</li>
* <li>each ending tag that's preceded by nested elements but not data</li>
* </ul>
* 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.
* <p>
* 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 <a href="mailto:jk2006@engineer.com">John Kristian</a>
*/
@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);
}
}
}

View File

@ -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 <a href="mailto:jk2006@engineer.com">John Kristian</a>
*/
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();
}
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration PUBLIC
"-//APACHE//DTD LOG4J 1.2//EN" "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration debug="false" xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %5p [%t] %C{1} %M - %m%n" />
</layout>
</appender>
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="sample.log"/>
<param name="BufferedIO" value="true" />
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %5p [%t] %C{1} %M - %m%n" />
</layout>
</appender>
<logger name="ch.eitchnet">
<level value="info" />
</logger>
<root>
<priority value="info" />
<appender-ref ref="CONSOLE" />
<!-- appender-ref ref="FILE" / -->
</root>
</log4j:configuration>

View File

@ -0,0 +1,70 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*
* 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<String> 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);
}
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class ConsoleEndpointTest extends AbstractEndpointTest {
private static final String CONNECTION_ID = "Console"; //$NON-NLS-1$
private CommunicationConnection connection;
@Before
public void before() {
Map<String, String> 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);
}
}
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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<String, String> 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<String, String> 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<String> 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');
}
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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<IoMessage> all = archive.getAll();
Collections.sort(all, new Comparator<IoMessage>() {
@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());
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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<String, String> 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<String, String> 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<String> 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$
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
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$
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2014 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class TestIoMessage extends IoMessage {
private List<String> contents;
public TestIoMessage(String id, CommandKey key, String connectionId) {
super(id, key, connectionId);
}
public TestIoMessage(String id, CommandKey key, String connectionId, List<String> contents) {
super(id, key, connectionId);
this.contents = contents;
}
public List<String> getContents() {
return this.contents;
}
public void setContents(List<String> contents) {
this.contents = contents;
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*
*/
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"));
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,138 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* 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));
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2015 Robert von Burg <eitch@eitchnet.ch>
*
* 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 <eitch@eitchnet.ch>
*/
public class DefaultedHashMapTest {
private Map<String, String> 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"));
}
}

Some files were not shown because too many files have changed in this diff Show More