Merge remote-tracking branch 'utils/develop' into develop
This commit is contained in:
commit
978afb6ba9
|
@ -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.
|
|
@ -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
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package ch.eitchnet.communication;
|
||||||
|
|
||||||
|
public interface ConnectionStateObserver {
|
||||||
|
|
||||||
|
public void notify(ConnectionState oldState, String oldStateMsg, ConnectionState newState, String newStateMsg);
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, Map&lgt;String, MyObject>> mapOfMaps = new HashMap<>;
|
||||||
|
* </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<String, String, MyObject> mapOfMaps = new MapOfMaps<>();
|
||||||
|
* </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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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$
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
Loading…
Reference in New Issue