From 2fbf6e47992026011de275c5db2865f66dae4bd1 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 20 May 2010 19:36:16 +0000 Subject: [PATCH 002/457] --- .classpath | 6 + .project | 17 ++ .settings/org.eclipse.jdt.core.prefs | 12 ++ src/ch/eitchnet/privilege/base/Locator.java | 168 ++++++++++++++++++ .../privilege/base/PrivilegeException.java | 28 +++ .../privilege/base/PrivilegeHelper.java | 19 ++ .../privilege/handler/PrivilegeHandler.java | 19 ++ .../eitchnet/privilege/model/Certificate.java | 103 +++++++++++ .../eitchnet/privilege/model/Permission.java | 19 ++ src/ch/eitchnet/privilege/model/User.java | 137 ++++++++++++++ .../eitchnet/privilege/model/UserState.java | 22 +++ 11 files changed, 550 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 src/ch/eitchnet/privilege/base/Locator.java create mode 100644 src/ch/eitchnet/privilege/base/PrivilegeException.java create mode 100644 src/ch/eitchnet/privilege/base/PrivilegeHelper.java create mode 100644 src/ch/eitchnet/privilege/handler/PrivilegeHandler.java create mode 100644 src/ch/eitchnet/privilege/model/Certificate.java create mode 100644 src/ch/eitchnet/privilege/model/Permission.java create mode 100644 src/ch/eitchnet/privilege/model/User.java create mode 100644 src/ch/eitchnet/privilege/model/UserState.java diff --git a/.classpath b/.classpath new file mode 100644 index 000000000..18d70f02c --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.project b/.project new file mode 100644 index 000000000..2bbc43fc5 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Privilege + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..650ae7f9a --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Wed May 19 19:28:29 CEST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/src/ch/eitchnet/privilege/base/Locator.java b/src/ch/eitchnet/privilege/base/Locator.java new file mode 100644 index 000000000..c9184fea7 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/Locator.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +import java.io.Serializable; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +/** + * Locator to access RSP objects by a generalized path similar to locator used for accessing files in file systems or + * URL. + *

+ * The locator is made of the following elements: + *

  • A identifier of the map to take the top level object from (Resource, Order, Workflow or Script) + *
  • The id of the top level object + *
  • The sequence of id's of the elements to navigate to the object + *

    + * It's string representation is similar to a UNIX path. For example a string version if the Locator of a parameter + * (with id = p1) of the bounds of a resource takes the form: /Resource/Bounds/p1. + * + * @author msmock + */ +public class Locator implements Serializable { + + private static final char LOCATOR_SEPARATOR = '/'; + private static final String LOCATOR_SEPARATOR_S = "/"; + private static final long serialVersionUID = 1L; + + // the path with string entries + private List path; + + /** + * simple constructor + */ + public Locator() { + path = new ArrayList(); + } + + /** + * @param path + */ + public Locator(List path) { + this.path = path; + } + + /** + * @param s + */ + public Locator(String s) { + + // parse the key to the list of path elements + s.trim(); + String[] pathElements = s.split(LOCATOR_SEPARATOR_S); + path = new ArrayList(); + + // for the case that we received a non locator string, just set it as + // the first path, helps while debugging + if (pathElements.length == 1) { + path.add(s); + } else { + + for (int i = 1; i < pathElements.length; i++) { + path.add(pathElements[i]); + } + } + } + + /** + * @return the path + */ + public List getPath() { + return path; + } + + /** + * @param path + * the path to set + */ + public void setPath(List path) { + this.path = path; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringWriter sw = new StringWriter(); + for (String s : path) { + sw.append(LOCATOR_SEPARATOR); + sw.append(s); + } + return sw.toString(); + } + + /** + * @param s + */ + public void append(String s) { + path.add(s); + } + + /** + * @return ListIterator + */ + public ListIterator iterator() { + return path.listIterator(); + } + + /** + * get the number of path element strings + * + * @return int + */ + public int length() { + return path.size(); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object other) { + + if (this == other) + return true; + + if (!(other instanceof Locator)) + return false; + + final Locator that = (Locator) other; + + // return false, if the size does not match + if (path.size() != that.path.size()) + return false; + + // compare the path elements + for (int i = 0; i < path.size(); i++) { + String thisS = path.get(i); + String thatS = that.path.get(i); + if (!thisS.equals(thatS)) + return false; + } + + // all path elements match + return true; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return toString().hashCode(); + } + +} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeException.java b/src/ch/eitchnet/privilege/base/PrivilegeException.java new file mode 100644 index 000000000..74a7c2ce9 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/PrivilegeException.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +/** + * @author rvonburg + * + */ +public class PrivilegeException extends RuntimeException { + + /** + * @param string + */ + public PrivilegeException(String string) { + super(string); + } + + private static final long serialVersionUID = 1L; + +} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeHelper.java b/src/ch/eitchnet/privilege/base/PrivilegeHelper.java new file mode 100644 index 000000000..a546a8aa9 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/PrivilegeHelper.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +/** + * @author rvonburg + * + */ +public class PrivilegeHelper { + +} diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java new file mode 100644 index 000000000..95bd41707 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +/** + * @author rvonburg + * + */ +public interface PrivilegeHandler { + +} diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java new file mode 100644 index 000000000..841693abd --- /dev/null +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.Locale; + +import ch.eitchnet.privilege.base.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class Certificate implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String sessionId; + private final String username; + private final String authToken; + private final String authPassword; + + private Locale locale; + + /** + * @param sessionId + * @param username + * @param authToken + * @param authPassword + * @param locale + */ + public Certificate(String sessionId, String username, String authToken, String authPassword, Locale locale) { + + // validate arguments are not null + if (sessionId == null || username == null || authToken == null || authPassword == null) { + throw new PrivilegeException("One of the arguments is null!"); + } + + this.sessionId = sessionId; + this.username = username; + this.authToken = authToken; + this.authPassword = authPassword; + + // if no locale is given, set default + if (locale == null) + this.locale = Locale.getDefault(); + else + this.locale = locale; + } + + /** + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** + * @param locale + * the locale to set + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * @return the sessionId + */ + public String getSessionId() { + return sessionId; + } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * Returns the authToken if the given authPassword is corret, null otherwise + * + * @param authPassword + * the auth password with which this certificate was created + * + * @return the authToken if the given authPassword is corret, null otherwise + */ + public String getAuthToken(String authPassword) { + if (this.authPassword.equals(authPassword)) { + return authToken; + } else { + return null; + } + } +} diff --git a/src/ch/eitchnet/privilege/model/Permission.java b/src/ch/eitchnet/privilege/model/Permission.java new file mode 100644 index 000000000..31b66c48d --- /dev/null +++ b/src/ch/eitchnet/privilege/model/Permission.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +/** + * @author rvonburg + * + */ +public interface Permission { + +} diff --git a/src/ch/eitchnet/privilege/model/User.java b/src/ch/eitchnet/privilege/model/User.java new file mode 100644 index 000000000..31fa54940 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/User.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.util.List; + +/** + * @author rvonburg + * + */ +public class User { + + private final String sessionId; + private final String authToken; + private final String authPassword; + + private final String username; + private final String firstname; + private final String surname; + + private final UserState userState; + + private List roleList; + + /** + * The {@link User} constructor is private to ensure no unauthorized creation of {@link User} objects + * + * @param sessionId + * @param username + * @param firstname + * @param surname + * @param userState + * @param validated + * @param roleList + */ + private User(String sessionId, String authToken, String authPassword, String username, String firstname, + String surname, UserState userState, List roleList) { + + this.sessionId = sessionId; + this.authToken = authToken; + this.authPassword = authPassword; + + this.username = username; + this.userState = userState; + + this.firstname = firstname; + this.surname = surname; + + this.roleList = roleList; + } + + /** + * @return the sessionId + */ + public String getSessionId() { + return sessionId; + } + + /** + * @return the authToken + */ + public String getAuthToken() { + return authToken; + } + + /** + * @return the authPassword + */ + public String getAuthPassword() { + return authPassword; + } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * @return the firstname + */ + public String getFirstname() { + return firstname; + } + + /** + * @return the surname + */ + public String getSurname() { + return surname; + } + + /** + * @return the userState + */ + public UserState getUserState() { + return userState; + } + + /** + * @return the roleList + */ + public List getRoleList() { + return roleList; + } + + /** + * @param sessionId + * @param authToken + * @param authPassword + * @param username + * @param firstname + * @param surname + * @param userState + * @param roleList + * + * @return a new {@link User} object which is authenticated on the current Java Virtual Machine + */ + public static User buildUser(String sessionId, String authToken, String authPassword, String username, + String firstname, String surname, UserState userState, List roleList) { + + // TODO validate who is creating this User object + + User user = new User(sessionId, authToken, authPassword, username, firstname, surname, userState, roleList); + + return user; + } +} diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java new file mode 100644 index 000000000..24cb3128e --- /dev/null +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +/** + * @author rvonburg + * + */ +public enum UserState { + NEW, + ENABLED, + DISABLED, + DEACTIVATED; +} From 78c894caaff911f540fba1ea981f191ddc8e1344 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 19:21:46 +0000 Subject: [PATCH 003/457] --- src/ch/eitchnet/privilege/base/Locator.java | 168 ------------------ .../privilege/base/PrivilegeContainer.java | 63 +++++++ .../privilege/base/PrivilegeHelper.java | 19 -- .../handler/DefaultPolicyHandler.java | 44 +++++ .../handler/DefaultPrivilegeHandler.java | 143 +++++++++++++++ .../privilege/handler/PolicyHandler.java | 23 +++ .../privilege/handler/PrivilegeHandler.java | 25 +++ .../privilege/i18n/AccessDeniedException.java | 29 +++ .../{base => i18n}/PrivilegeException.java | 6 +- .../eitchnet/privilege/model/Certificate.java | 65 ++++++- .../{Permission.java => Restrictable.java} | 5 +- .../privilege/model/RestrictionPolicy.java | 20 +++ src/ch/eitchnet/privilege/model/Session.java | 130 ++++++++++++++ src/ch/eitchnet/privilege/model/User.java | 86 ++++----- 14 files changed, 582 insertions(+), 244 deletions(-) delete mode 100644 src/ch/eitchnet/privilege/base/Locator.java create mode 100644 src/ch/eitchnet/privilege/base/PrivilegeContainer.java delete mode 100644 src/ch/eitchnet/privilege/base/PrivilegeHelper.java create mode 100644 src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java create mode 100644 src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java create mode 100644 src/ch/eitchnet/privilege/handler/PolicyHandler.java create mode 100644 src/ch/eitchnet/privilege/i18n/AccessDeniedException.java rename src/ch/eitchnet/privilege/{base => i18n}/PrivilegeException.java (90%) rename src/ch/eitchnet/privilege/model/{Permission.java => Restrictable.java} (67%) create mode 100644 src/ch/eitchnet/privilege/model/RestrictionPolicy.java create mode 100644 src/ch/eitchnet/privilege/model/Session.java diff --git a/src/ch/eitchnet/privilege/base/Locator.java b/src/ch/eitchnet/privilege/base/Locator.java deleted file mode 100644 index c9184fea7..000000000 --- a/src/ch/eitchnet/privilege/base/Locator.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.base; - -import java.io.Serializable; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - -/** - * Locator to access RSP objects by a generalized path similar to locator used for accessing files in file systems or - * URL. - *

    - * The locator is made of the following elements: - *

  • A identifier of the map to take the top level object from (Resource, Order, Workflow or Script) - *
  • The id of the top level object - *
  • The sequence of id's of the elements to navigate to the object - *

    - * It's string representation is similar to a UNIX path. For example a string version if the Locator of a parameter - * (with id = p1) of the bounds of a resource takes the form: /Resource/Bounds/p1. - * - * @author msmock - */ -public class Locator implements Serializable { - - private static final char LOCATOR_SEPARATOR = '/'; - private static final String LOCATOR_SEPARATOR_S = "/"; - private static final long serialVersionUID = 1L; - - // the path with string entries - private List path; - - /** - * simple constructor - */ - public Locator() { - path = new ArrayList(); - } - - /** - * @param path - */ - public Locator(List path) { - this.path = path; - } - - /** - * @param s - */ - public Locator(String s) { - - // parse the key to the list of path elements - s.trim(); - String[] pathElements = s.split(LOCATOR_SEPARATOR_S); - path = new ArrayList(); - - // for the case that we received a non locator string, just set it as - // the first path, helps while debugging - if (pathElements.length == 1) { - path.add(s); - } else { - - for (int i = 1; i < pathElements.length; i++) { - path.add(pathElements[i]); - } - } - } - - /** - * @return the path - */ - public List getPath() { - return path; - } - - /** - * @param path - * the path to set - */ - public void setPath(List path) { - this.path = path; - } - - /** - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - - StringWriter sw = new StringWriter(); - for (String s : path) { - sw.append(LOCATOR_SEPARATOR); - sw.append(s); - } - return sw.toString(); - } - - /** - * @param s - */ - public void append(String s) { - path.add(s); - } - - /** - * @return ListIterator - */ - public ListIterator iterator() { - return path.listIterator(); - } - - /** - * get the number of path element strings - * - * @return int - */ - public int length() { - return path.size(); - } - - /** - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object other) { - - if (this == other) - return true; - - if (!(other instanceof Locator)) - return false; - - final Locator that = (Locator) other; - - // return false, if the size does not match - if (path.size() != that.path.size()) - return false; - - // compare the path elements - for (int i = 0; i < path.size(); i++) { - String thisS = path.get(i); - String thatS = that.path.get(i); - if (!thisS.equals(thatS)) - return false; - } - - // all path elements match - return true; - } - - /** - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return toString().hashCode(); - } - -} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java new file mode 100644 index 000000000..3e99d76c0 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +import java.io.File; + +import ch.eitchnet.privilege.handler.PolicyHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; + +/** + * + * + * @author rvonburg + */ +public class PrivilegeContainer { + + private static final PrivilegeContainer instance; + + static { + instance = new PrivilegeContainer(); + } + + private PrivilegeHandler privilegeHandler; + private PolicyHandler policyHandler; + + public static PrivilegeContainer getInstance() { + return instance; + } + + /** + * private constructor to force singleton + */ + private PrivilegeContainer() { + // private constructor + } + + /** + * @return the privilegeHandler + */ + public PrivilegeHandler getPrivilegeHandler() { + return privilegeHandler; + } + + /** + * @return the policyHandler + */ + public PolicyHandler getPolicyHandler() { + return policyHandler; + } + + public void initialize(File privilegeContainerXml) { + // TODO implement + } + +} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeHelper.java b/src/ch/eitchnet/privilege/base/PrivilegeHelper.java deleted file mode 100644 index a546a8aa9..000000000 --- a/src/ch/eitchnet/privilege/base/PrivilegeHelper.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.base; - -/** - * @author rvonburg - * - */ -public class PrivilegeHelper { - -} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java new file mode 100644 index 000000000..e9e5176c2 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.io.File; +import java.util.Map; + +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.RestrictionPolicy; +import ch.eitchnet.privilege.model.User; + +/** + * @author rvonburg + * + */ +public class DefaultPolicyHandler implements PolicyHandler { + + private Map policyMap; + + /** + * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.User, + * ch.eitchnet.privilege.model.Restrictable) + */ + @Override + public boolean actionAllowed(User user, Restrictable restrictable) { + + // TODO auth user + + // TODO Auto-generated method stub + return false; + } + + public void initialize(File policyXml) { + // TODO implement + } +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java new file mode 100644 index 000000000..e71292b22 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.io.File; +import java.util.Map; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.Session; +import ch.eitchnet.privilege.model.User; +import ch.eitchnet.privilege.model.UserState; + +/** + * @author rvonburg + * + */ +public class DefaultPrivilegeHandler implements PrivilegeHandler { + + private Map userMap; + + private Map sessionMap; + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.Restrictable) + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation + */ + @Override + public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + + if (certificate == null) + throw new PrivilegeException("Certificate may not be null!"); + else if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + // first see if a session exists for this certificate + CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); + if (certificateSessionPair == null) + throw new AccessDeniedException("There is no session information for " + certificate.toString()); + + Certificate sessionCertificate = certificateSessionPair.certificate; + if (!sessionCertificate.equals(certificate)) + throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); + + // get authtoken from certificate using the sessions password + String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); + if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) + throw new PrivilegeException("Received illegal certificate data for session id " + + certificate.getSessionId()); + + // get user object + User user = userMap.get(certificateSessionPair.session.getUsername()); + if (user == null) { + throw new PrivilegeException( + "Oh now, how did this happen: No User in user map although certificate is valid!"); + } + + // now validate on policy handler + PrivilegeContainer.getInstance().getPolicyHandler().actionAllowed(user, restrictable); + + // TODO Auto-generated method stub + return false; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, java.lang.String) + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + @Override + public Certificate authenticate(String username, String password) { + + // both username and password must at least have 3 characters in length + if (username == null || username.length() < 3) + throw new PrivilegeException("The given username is shorter than 3 characters"); + else if (password == null || password.length() < 3) + throw new PrivilegeException("The given password is shorter than 3 characters"); + + // we only work with hashed passwords + String passwordHash = password; // TODO hash password + + // get user object + User user = userMap.get(username); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); + + // validate password + if (!user.getPassword().equals(passwordHash)) + throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + + // validate if user is allowed to login + if (user.getState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + + // get 2 auth tokens + String authToken = ""; // TODO get auth token + String authPassword = ""; // TODO get auth password + + String sessionId = ""; // TODO get next session id + + // create certificate + Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + + // create and save a new session + Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System + .currentTimeMillis()); + sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + + // return the certificate + return certificate; + } + + public void initialize(File userFile) { + // TODO implement + } + + private class CertificateSessionPair { + private Session session; + private Certificate certificate; + + public CertificateSessionPair(Session session, Certificate certificate) { + this.session = session; + this.certificate = certificate; + } + } +} diff --git a/src/ch/eitchnet/privilege/handler/PolicyHandler.java b/src/ch/eitchnet/privilege/handler/PolicyHandler.java new file mode 100644 index 000000000..992a45f16 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/PolicyHandler.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.User; + +/** + * @author rvonburg + * + */ +public interface PolicyHandler { + + public boolean actionAllowed(User user, Restrictable restrictable); +} diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 95bd41707..b62df401f 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -10,10 +10,35 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.User; + /** * @author rvonburg * */ public interface PrivilegeHandler { + /** + * @param certificate + * @param restrictable + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation + */ + public boolean actionAllowed(Certificate certificate, Restrictable restrictable); + + /** + * @param user + * @param password + * @return + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + public Certificate authenticate(String user, String password); } diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java new file mode 100644 index 000000000..1ff487f4b --- /dev/null +++ b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.i18n; + +/** + * @author rvonburg + * + */ +public class AccessDeniedException extends PrivilegeException { + + /** + * @param string + */ + public AccessDeniedException(String string) { + super(string); + // TODO Auto-generated constructor stub + } + + private static final long serialVersionUID = 1L; + +} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeException.java b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java similarity index 90% rename from src/ch/eitchnet/privilege/base/PrivilegeException.java rename to src/ch/eitchnet/privilege/i18n/PrivilegeException.java index 74a7c2ce9..11ba8dc4f 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeException.java +++ b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -8,7 +8,7 @@ * */ -package ch.eitchnet.privilege.base; +package ch.eitchnet.privilege.i18n; /** * @author rvonburg @@ -16,6 +16,8 @@ package ch.eitchnet.privilege.base; */ public class PrivilegeException extends RuntimeException { + private static final long serialVersionUID = 1L; + /** * @param string */ @@ -23,6 +25,4 @@ public class PrivilegeException extends RuntimeException { super(string); } - private static final long serialVersionUID = 1L; - } diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index 841693abd..42b473ab4 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -13,7 +13,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Locale; -import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeException; /** * @author rvonburg @@ -100,4 +100,67 @@ public class Certificate implements Serializable { return null; } } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((authPassword == null) ? 0 : authPassword.hashCode()); + result = prime * result + ((authToken == null) ? 0 : authToken.hashCode()); + result = prime * result + ((locale == null) ? 0 : locale.hashCode()); + result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Certificate)) + return false; + Certificate other = (Certificate) obj; + if (authPassword == null) { + if (other.authPassword != null) + return false; + } else if (!authPassword.equals(other.authPassword)) + return false; + if (authToken == null) { + if (other.authToken != null) + return false; + } else if (!authToken.equals(other.authToken)) + return false; + if (locale == null) { + if (other.locale != null) + return false; + } else if (!locale.equals(other.locale)) + return false; + if (sessionId == null) { + if (other.sessionId != null) + return false; + } else if (!sessionId.equals(other.sessionId)) + return false; + if (username == null) { + if (other.username != null) + return false; + } else if (!username.equals(other.username)) + return false; + return true; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Certificate [locale=" + locale + ", sessionId=" + sessionId + ", username=" + username + "]"; + } } diff --git a/src/ch/eitchnet/privilege/model/Permission.java b/src/ch/eitchnet/privilege/model/Restrictable.java similarity index 67% rename from src/ch/eitchnet/privilege/model/Permission.java rename to src/ch/eitchnet/privilege/model/Restrictable.java index 31b66c48d..38a989f9b 100644 --- a/src/ch/eitchnet/privilege/model/Permission.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -14,6 +14,9 @@ package ch.eitchnet.privilege.model; * @author rvonburg * */ -public interface Permission { +public interface Restrictable { + public String getType(); + + public Object getValue(); } diff --git a/src/ch/eitchnet/privilege/model/RestrictionPolicy.java b/src/ch/eitchnet/privilege/model/RestrictionPolicy.java new file mode 100644 index 000000000..7bcccea64 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/RestrictionPolicy.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +/** + * @author rvonburg + * + */ +public interface RestrictionPolicy { + + public boolean actionAllowed(User user, Restrictable restrictable); +} diff --git a/src/ch/eitchnet/privilege/model/Session.java b/src/ch/eitchnet/privilege/model/Session.java new file mode 100644 index 000000000..b9dd1ea83 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/Session.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +/** + * @author rvonburg + * + */ +public class Session { + + private final String sessionId; + private final String username; + private final long loginTime; + + private final String authToken; + private final String authPassword; + + /** + */ + public Session(String sessionId, String authToken, String authPassword, String username, long loginTime) { + this.sessionId = sessionId; + this.authToken = authToken; + this.authPassword = authPassword; + this.username = username; + this.loginTime = loginTime; + } + + /** + * @return the sessionId + */ + public String getSessionId() { + return sessionId; + } + + /** + * @return the authToken + */ + public String getAuthToken() { + return authToken; + } + + /** + * @return the authPassword + */ + public String getAuthPassword() { + return authPassword; + } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * @return the loginTime + */ + public long getLoginTime() { + return loginTime; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((authPassword == null) ? 0 : authPassword.hashCode()); + result = prime * result + ((authToken == null) ? 0 : authToken.hashCode()); + result = prime * result + (int) (loginTime ^ (loginTime >>> 32)); + result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Session)) + return false; + Session other = (Session) obj; + if (authPassword == null) { + if (other.authPassword != null) + return false; + } else if (!authPassword.equals(other.authPassword)) + return false; + if (authToken == null) { + if (other.authToken != null) + return false; + } else if (!authToken.equals(other.authToken)) + return false; + if (loginTime != other.loginTime) + return false; + if (sessionId == null) { + if (other.sessionId != null) + return false; + } else if (!sessionId.equals(other.sessionId)) + return false; + if (username == null) { + if (other.username != null) + return false; + } else if (!username.equals(other.username)) + return false; + return true; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Session [loginTime=" + loginTime + ", sessionId=" + sessionId + ", username=" + username + "]"; + } +} diff --git a/src/ch/eitchnet/privilege/model/User.java b/src/ch/eitchnet/privilege/model/User.java index 31fa54940..9cf9d58c4 100644 --- a/src/ch/eitchnet/privilege/model/User.java +++ b/src/ch/eitchnet/privilege/model/User.java @@ -10,7 +10,9 @@ package ch.eitchnet.privilege.model; +import java.util.Collections; import java.util.List; +import java.util.Locale; /** * @author rvonburg @@ -18,64 +20,34 @@ import java.util.List; */ public class User { - private final String sessionId; - private final String authToken; - private final String authPassword; - private final String username; + private final String password; + private final String firstname; private final String surname; private final UserState userState; - private List roleList; + private final List roleList; + + private final Locale locale; /** * The {@link User} constructor is private to ensure no unauthorized creation of {@link User} objects - * - * @param sessionId - * @param username - * @param firstname - * @param surname - * @param userState - * @param validated - * @param roleList */ - private User(String sessionId, String authToken, String authPassword, String username, String firstname, - String surname, UserState userState, List roleList) { - - this.sessionId = sessionId; - this.authToken = authToken; - this.authPassword = authPassword; + private User(String username, String password, String firstname, String surname, UserState userState, + List roleList, Locale locale) { this.username = username; + this.password = password; this.userState = userState; this.firstname = firstname; this.surname = surname; this.roleList = roleList; - } - /** - * @return the sessionId - */ - public String getSessionId() { - return sessionId; - } - - /** - * @return the authToken - */ - public String getAuthToken() { - return authToken; - } - - /** - * @return the authPassword - */ - public String getAuthPassword() { - return authPassword; + this.locale = locale; } /** @@ -85,6 +57,13 @@ public class User { return username; } + /** + * @return the password + */ + public String getPassword() { + return password; + } + /** * @return the firstname */ @@ -102,7 +81,7 @@ public class User { /** * @return the userState */ - public UserState getUserState() { + public UserState getState() { return userState; } @@ -114,23 +93,26 @@ public class User { } /** - * @param sessionId - * @param authToken - * @param authPassword - * @param username - * @param firstname - * @param surname - * @param userState - * @param roleList - * + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** * @return a new {@link User} object which is authenticated on the current Java Virtual Machine */ - public static User buildUser(String sessionId, String authToken, String authPassword, String username, - String firstname, String surname, UserState userState, List roleList) { + public static User buildUser(String username, String password, String firstname, String surname, + UserState userState, List roleList, Locale locale) { + + // set a default locale + if (locale == null) + locale = Locale.getDefault(); // TODO validate who is creating this User object - User user = new User(sessionId, authToken, authPassword, username, firstname, surname, userState, roleList); + User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableList(roleList), + locale); return user; } From cd9097e5da1954c65c766cf4b7acc6eca87bdd8f Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 19:23:50 +0000 Subject: [PATCH 004/457] --- .../eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index e71292b22..b548a13cf 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -53,11 +53,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (certificateSessionPair == null) throw new AccessDeniedException("There is no session information for " + certificate.toString()); + // validate certificate has not been tampered with Certificate sessionCertificate = certificateSessionPair.certificate; if (!sessionCertificate.equals(certificate)) throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - // get authtoken from certificate using the sessions password + // TODO is this maybe overkill? + // validate authToken from certificate using the sessions authPassword String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) throw new PrivilegeException("Received illegal certificate data for session id " From 1cbe3cfca81e7754c1a311fd315974a268e2f308 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 20:16:08 +0000 Subject: [PATCH 005/457] --- src/ch/eitchnet/privilege/model/Restrictable.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index 38a989f9b..c80f4598c 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -16,7 +16,7 @@ package ch.eitchnet.privilege.model; */ public interface Restrictable { - public String getType(); + public String getRestrictionName(); - public Object getValue(); + public Object getRestrictionValue(); } From a960410b6959b24e7ff8de8ad46bdd136016c92a Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 22:55:58 +0000 Subject: [PATCH 006/457] --- .../privilege/model/DefaultRestriction.java | 131 ++++++++++++++++++ .../privilege/model/Restrictable.java | 2 +- 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/ch/eitchnet/privilege/model/DefaultRestriction.java diff --git a/src/ch/eitchnet/privilege/model/DefaultRestriction.java b/src/ch/eitchnet/privilege/model/DefaultRestriction.java new file mode 100644 index 000000000..a24a622f5 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/DefaultRestriction.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.util.List; +import java.util.Map; + +import org.dom4j.Element; + +import ch.eitchnet.privilege.i18n.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class DefaultRestriction implements RestrictionPolicy { + + private String restrictionKey; + + private Map roleRestrictionMap; + + /** + * @see ch.eitchnet.privilege.model.RestrictionPolicy#actionAllowed(ch.eitchnet.privilege.model.User, + * ch.eitchnet.privilege.model.Restrictable) + */ + @Override + public boolean actionAllowed(User user, Restrictable restrictable) { + + // validate user is not null + if (user == null) + throw new PrivilegeException("User may not be null!"); + + // validate Restrictable is set for this RestrictionPolicy + if (!restrictionKey.equals(restrictable.getRestrictionKey())) { + throw new PrivilegeException(RestrictionPolicy.class.getSimpleName() + " " + + DefaultRestriction.class.getSimpleName() + " with restriction key " + restrictionKey + + " can not validate " + Restrictable.class.getSimpleName() + " with key " + + restrictable.getRestrictionKey()); + } + + // default is that user does not have privilege + boolean hasPrivilege = false; + + // iterate user roles and validate role has privilege + for (String role : user.getRoleList()) { + + hasPrivilege = internalActionAllowed(role, restrictable); + + // if privilege is found, then stop iterating + if (hasPrivilege) + break; + } + + return hasPrivilege; + } + + /** + * @param role + * @param restrictable + * @return + */ + private boolean internalActionAllowed(String role, Restrictable restrictable) { + + // get restriction object for users role + Restriction restriction = roleRestrictionMap.get(role); + + // no restriction object means no privilege + if (restriction == null) + return false; + + // does this role have privilege for any values? + if (restriction.anyValue) + return true; + + // get the value on which the action is to be performed + Object object = restrictable.getRestrictionValue(); + + // DefaultRestriction policy expects the restriction value to be a string + if (!(object instanceof String)) { + throw new PrivilegeException(Restrictable.class.getName() + " " + restrictable.getClass().getSimpleName() + + " has returned a non-string restriction value!"); + } + + String restrictionValue = (String) object; + + // first check values not allowed + for (String notAllowed : restriction.valuesNotAllowed) { + if (notAllowed.equals(restrictionValue)) + return false; + } + + // now check values allowed + for (String allowed : restriction.valuesAllowed) { + if (allowed.equals(restrictionValue)) + return true; + } + + // default is not allowed + return false; + } + + public void initialize(Element element) { + + // TODO implement + } + + private class Restriction { + private boolean anyValue; + private List valuesAllowed; + private List valuesNotAllowed; + + /** + * @param allAllowed + * @param valuesAllowed + * @param valuesNotAllowed + */ + public Restriction(boolean anyValue, List valuesAllowed, List valuesNotAllowed) { + this.anyValue = anyValue; + this.valuesAllowed = valuesAllowed; + this.valuesNotAllowed = valuesNotAllowed; + } + } +} diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index c80f4598c..9a2952fac 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -16,7 +16,7 @@ package ch.eitchnet.privilege.model; */ public interface Restrictable { - public String getRestrictionName(); + public String getRestrictionKey(); public Object getRestrictionValue(); } From 36e29f4597db858159cd74581d543dd1433c767b Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 22:56:21 +0000 Subject: [PATCH 007/457] --- .classpath | 1 + lib/dom4j-1.6.1-src.zip | Bin 0 -> 440064 bytes lib/dom4j-1.6.1.jar | Bin 0 -> 313898 bytes 3 files changed, 1 insertion(+) create mode 100644 lib/dom4j-1.6.1-src.zip create mode 100644 lib/dom4j-1.6.1.jar diff --git a/.classpath b/.classpath index 18d70f02c..63421de7a 100644 --- a/.classpath +++ b/.classpath @@ -2,5 +2,6 @@ + diff --git a/lib/dom4j-1.6.1-src.zip b/lib/dom4j-1.6.1-src.zip new file mode 100644 index 0000000000000000000000000000000000000000..39b4137d350e6bad0405b9b3aca2ea6143ea79de GIT binary patch literal 440064 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%0ArZNyr#a_uiccET<7nN;-(B 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 Z!Q0dci2%c+Q1#&NE-;_kWe5G|{{hteC$azl literal 0 HcmV?d00001 diff --git a/lib/dom4j-1.6.1.jar b/lib/dom4j-1.6.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..c8c4dbb92d6c23a7fbb2813eb721eb4cce91750c GIT binary patch literal 313898 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

    ^<7W@(9yr9mUm2tK?C|&v@%Rm35X`o{%fo+w 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%0A?GZzYp7)bLPx}z&<;hZaX^DZ#RA|ACLXD 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%0Aok&dtGbv;D|BgjT@f(sv0`S4#(FudZ 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 literal 0 HcmV?d00001 From 06569dead31c06a5f315cab23eeab0045e106c81 Mon Sep 17 00:00:00 2001 From: eitch Date: Tue, 25 May 2010 19:41:34 +0000 Subject: [PATCH 008/457] --- .../privilege/base/PrivilegeContainer.java | 15 +++- .../handler/DefaultEncryptionHandler.java | 79 +++++++++++++++++++ ...andler.java => DefaultSessionHandler.java} | 38 ++++++--- .../privilege/handler/EncryptionHandler.java | 22 ++++++ ...vilegeHandler.java => SessionHandler.java} | 2 +- .../privilege/i18n/PrivilegeException.java | 8 ++ .../eitchnet/privilege/model/Certificate.java | 2 +- src/ch/eitchnet/privilege/model/Session.java | 2 +- 8 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java rename src/ch/eitchnet/privilege/handler/{DefaultPrivilegeHandler.java => DefaultSessionHandler.java} (81%) create mode 100644 src/ch/eitchnet/privilege/handler/EncryptionHandler.java rename src/ch/eitchnet/privilege/handler/{PrivilegeHandler.java => SessionHandler.java} (96%) diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 3e99d76c0..9a1560f03 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -13,7 +13,8 @@ package ch.eitchnet.privilege.base; import java.io.File; import ch.eitchnet.privilege.handler.PolicyHandler; -import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.handler.SessionHandler; +import ch.eitchnet.privilege.handler.EncryptionHandler; /** * @@ -28,8 +29,9 @@ public class PrivilegeContainer { instance = new PrivilegeContainer(); } - private PrivilegeHandler privilegeHandler; + private SessionHandler privilegeHandler; private PolicyHandler policyHandler; + private EncryptionHandler encryptionHandler; public static PrivilegeContainer getInstance() { return instance; @@ -45,7 +47,7 @@ public class PrivilegeContainer { /** * @return the privilegeHandler */ - public PrivilegeHandler getPrivilegeHandler() { + public SessionHandler getPrivilegeHandler() { return privilegeHandler; } @@ -56,6 +58,13 @@ public class PrivilegeContainer { return policyHandler; } + /** + * @return the encryptionHandler + */ + public EncryptionHandler getEncryptionHandler() { + return encryptionHandler; + } + public void initialize(File privilegeContainerXml) { // TODO implement } diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java new file mode 100644 index 000000000..8bdde3da3 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import org.dom4j.Element; + +import ch.eitchnet.privilege.i18n.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class DefaultEncryptionHandler implements EncryptionHandler { + + public static final String HASH_ALGORITHM = "SHA-1"; + + /** + * Hex char table for fast calculating of hex value + */ + 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' }; + + /** + * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) + */ + @Override + public String convertToHash(String string) { + try { + + MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); + byte[] hashArray = digest.digest(string.getBytes()); + + byte[] hex = new byte[2 * hashArray.length]; + int index = 0; + + for (byte b : hashArray) { + int v = b & 0xFF; + hex[index++] = HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = HEX_CHAR_TABLE[v & 0xF]; + } + + return new String(hex, "ASCII"); + + } catch (NoSuchAlgorithmException e) { + throw new PrivilegeException("Algorithm " + HASH_ALGORITHM + " was not found!", e); + } catch (UnsupportedEncodingException e) { + throw new PrivilegeException("Charset ASCII is not supported!", e); + } + } + + /** + * @see ch.eitchnet.privilege.handler.EncryptionHandler#nextToken() + */ + @Override + public String nextToken() { + SecureRandom secureRandom = new SecureRandom(); + String randomString = new BigInteger(130, secureRandom).toString(32); + return randomString; + } + + public void initialize(Element element) { + // TODO implement + } +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java similarity index 81% rename from src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java rename to src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index b548a13cf..9e3cf646b 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -13,6 +13,8 @@ package ch.eitchnet.privilege.handler; import java.io.File; import java.util.Map; +import org.apache.log4j.Logger; + import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -26,14 +28,17 @@ import ch.eitchnet.privilege.model.UserState; * @author rvonburg * */ -public class DefaultPrivilegeHandler implements PrivilegeHandler { +public class DefaultSessionHandler implements SessionHandler { + + private static final Logger logger = Logger.getLogger(DefaultSessionHandler.class); + + private static long lastSessionId; private Map userMap; - private Map sessionMap; /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.Restrictable) * * @throws AccessDeniedException @@ -58,7 +63,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (!sessionCertificate.equals(certificate)) throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - // TODO is this maybe overkill? + // TODO is this overkill? // validate authToken from certificate using the sessions authPassword String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) @@ -73,14 +78,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // now validate on policy handler - PrivilegeContainer.getInstance().getPolicyHandler().actionAllowed(user, restrictable); - - // TODO Auto-generated method stub - return false; + return PrivilegeContainer.getInstance().getPolicyHandler().actionAllowed(user, restrictable); } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, java.lang.String) + * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) * * @throws AccessDeniedException * if the user credentials are not valid @@ -94,8 +96,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { else if (password == null || password.length() < 3) throw new PrivilegeException("The given password is shorter than 3 characters"); + EncryptionHandler encryptionHandler = PrivilegeContainer.getInstance().getEncryptionHandler(); + // we only work with hashed passwords - String passwordHash = password; // TODO hash password + String passwordHash = encryptionHandler.convertToHash(password); // get user object User user = userMap.get(username); @@ -112,10 +116,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); // get 2 auth tokens - String authToken = ""; // TODO get auth token - String authPassword = ""; // TODO get auth password + String authToken = encryptionHandler.nextToken(); + String authPassword = encryptionHandler.nextToken(); - String sessionId = ""; // TODO get next session id + // get next session id + String sessionId = nextSessionId(); // create certificate Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); @@ -125,10 +130,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .currentTimeMillis()); sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + // log + logger.info("Authenticated: " + session); + // return the certificate return certificate; } + private String nextSessionId() { + return Long.toString(++lastSessionId % Long.MAX_VALUE); + } + public void initialize(File userFile) { // TODO implement } diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java new file mode 100644 index 000000000..bda572e0c --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +/** + * @author rvonburg + * + */ +public interface EncryptionHandler { + + public String nextToken(); + + public String convertToHash(String string); +} diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java similarity index 96% rename from src/ch/eitchnet/privilege/handler/PrivilegeHandler.java rename to src/ch/eitchnet/privilege/handler/SessionHandler.java index b62df401f..39b35cc45 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -19,7 +19,7 @@ import ch.eitchnet.privilege.model.User; * @author rvonburg * */ -public interface PrivilegeHandler { +public interface SessionHandler { /** * @param certificate diff --git a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java index 11ba8dc4f..4960a42f9 100644 --- a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -25,4 +25,12 @@ public class PrivilegeException extends RuntimeException { super(string); } + /** + * + * @param string + * @param t + */ + public PrivilegeException(String string, Throwable t) { + super(string, t); + } } diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index 42b473ab4..bb92989fa 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -161,6 +161,6 @@ public class Certificate implements Serializable { */ @Override public String toString() { - return "Certificate [locale=" + locale + ", sessionId=" + sessionId + ", username=" + username + "]"; + return "Certificate [sessionId=" + sessionId + ", username=" + username + ", locale=" + locale + "]"; } } diff --git a/src/ch/eitchnet/privilege/model/Session.java b/src/ch/eitchnet/privilege/model/Session.java index b9dd1ea83..a90249f3d 100644 --- a/src/ch/eitchnet/privilege/model/Session.java +++ b/src/ch/eitchnet/privilege/model/Session.java @@ -125,6 +125,6 @@ public class Session { */ @Override public String toString() { - return "Session [loginTime=" + loginTime + ", sessionId=" + sessionId + ", username=" + username + "]"; + return "Session [username=" + username + ", sessionId=" + sessionId + ", loginTime=" + loginTime + "]"; } } From 4d46267b1d264514dca281c83071c120fe212bd4 Mon Sep 17 00:00:00 2001 From: eitch Date: Tue, 25 May 2010 21:15:35 +0000 Subject: [PATCH 009/457] --- .classpath | 1 + config/PrivilegeContainer.xml | 20 +++++++ lib/log4j-1.2.15.jar | Bin 0 -> 391834 bytes .../privilege/base/PrivilegeContainer.java | 45 +++++++++++++--- .../base/PrivilegeContainerObject.java | 22 ++++++++ .../handler/DefaultEncryptionHandler.java | 21 ++++++-- .../handler/DefaultPolicyHandler.java | 38 ++++++++++--- .../handler/DefaultSessionHandler.java | 10 ++-- .../privilege/handler/EncryptionHandler.java | 4 +- .../privilege/handler/PolicyHandler.java | 3 +- .../privilege/handler/SessionHandler.java | 3 +- .../privilege/helper/ClassHelper.java | 33 ++++++++++++ .../privilege/helper/ConfigurationHelper.java | 39 ++++++++++++++ .../eitchnet/privilege/helper/XmlHelper.java | 50 ++++++++++++++++++ 14 files changed, 265 insertions(+), 24 deletions(-) create mode 100644 config/PrivilegeContainer.xml create mode 100644 lib/log4j-1.2.15.jar create mode 100644 src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java create mode 100644 src/ch/eitchnet/privilege/helper/ClassHelper.java create mode 100644 src/ch/eitchnet/privilege/helper/ConfigurationHelper.java create mode 100644 src/ch/eitchnet/privilege/helper/XmlHelper.java diff --git a/.classpath b/.classpath index 63421de7a..bba4c498c 100644 --- a/.classpath +++ b/.classpath @@ -3,5 +3,6 @@ + diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml new file mode 100644 index 000000000..b40c6a226 --- /dev/null +++ b/config/PrivilegeContainer.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/log4j-1.2.15.jar b/lib/log4j-1.2.15.jar new file mode 100644 index 0000000000000000000000000000000000000000..c930a6ab4d4b73c1a6feb9e929091205664bb340 GIT binary patch literal 391834 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 z≫|t2$&PzMxg7#5~2Yo&n#%LAw13=~!afd|Ql&*BNboWgTBW-$zGh%MD>O2~6y0 zz#wbE+5uAnssL|5*O16HdVTW{L7fq;+-tcK&9e8To}9t#ob73GC^-lbi{}(%(;eL1 z(#-n#JbF0cz3M28_kt-@A7gvsj}|7t z%!t@Qp<)KkBoTHWAe9U%z#l^3ZZbugqj`8DYx6AgEz<($-^V47r$;hO84IdzPjG3%A?>6xCMMy0WjgMcUfR< z%;kaZyBejAI-ku<_)3x0K1`ZJDX4CFzug8iYlandXlnFKONRdt;36+L`Sm&A>ZNC2 zK*QFKSUJxl?5-!3nrfR!*;jjHR;}GBeB@b|XR7r$%VGMUme6H8+>;8yLW>O%oY-?VkPC}SfY*9& zE4jsm(Wi@Qmy(9fE*wg?f0@{`KhOYYBW#^$V~VqDG@CQ!EC$LBE6KJP(WO<+ubnue zT`tl?t6^Ug;mRrZCwOK{^!l52il2?cH6UtNz_obKu-gqGsrSUCY|36Sg7;A$0+!LP_f+1uLATGSL-*cvwc9i1=X(;b?9H+G z=X;c@&-DN<(fe#*W6N-8G12>IAj{RKdY`V=$-&h;!sl6^$Q2Lat~x49E%}~n6`D`~ z{rd~&{TsNCj_q!?YpklS{jTwQ__2=N?zXFPpO5QnH_b)gm+KKTM75-=V2&xDYCoW| zs*~Rpjx_EaIs71R#jC*DOc;09%#OtNRt`tm8Be!CiC`K}(N*|wrnT$y8d4Suz(z_q zCMK`+$ui{}N4x?dm@F0G4*r~n=ho)B451_5cI|aix%qn0)$)sbVbOqH{OzKj9)8Y~ zXks^~#C0)z$G3jYtB541dzy55Clsf0^zd%r(sSm!X?kp?704?msXtqaw1J|2&i^Xp z?yYHKXQ|U%bRlaOWK!J9e@UF75r(~6$#O*N?=9h5SDKDy#;%2CwGd-g>-@=#RENUZ z5F3J`(jU~rA^VdvMX@VJ08!KVTK+@C!pFzQC7!u50;7v4KqYCV!K7D*7>TiQi~z+} zWD)LR!Pobs7$2f4#%Fd0ULld+&r=(-u+ch?)J!q%sxC#vJ0${-pFo8fB}QZTfxKjU!l?`NQ@OAV=~a!h|L7>hdKb%P_q|Hs)oMOUIO{odVS z$F|Y2ZQIU_Z5thQY}>Z&bZpypI_cO+pSj+>)_cxgV|-(OdtJ<{ImW!H->j;q@Gm1o zaS91}<@f{#^evM8((jRveyzWPa}2Lg+vovQpA1*=;43^K4`)iTzxD>jie%H=1Q}ry zCQIfEpa%^gm*BF~WBnTZ9{EW{gF`fqRJ)#H$OrlpXIAnQ1)3Xx$O9WyV=7Y0*b9uH z=rsviY#lDEXWb|u;H|PuN}*jDxzcsqFqk4~P)3m~!cmbd3|d1aoT?p-a}Pr@N53y| zF<%V5DPL|U5T4eWqSt<*3e$E>O0i#-a*DSUQ)*6}Sie|O-WG7>DQ>PZ_H5#U2})LYAs z3KI)A4{kE&0joy8(cIRsUqypG;HAM54$ef^e`sA1RL}8Q+*mshVXQTkKjrFJ>BH+g z-=4@WlD*dT>LxmLoAUL@W?Z5d_M1sikJsM&6lfKxcjeRX=)O#m7t)(iZfNKF9&2j; zGzJ|KBqM82B(>3eN%ya_G!f{E9Iv%U@V9tXQEAu%WwV5mFc}C?# zj@quX98u0%j2A>^(iA_D`nL%Nj-(VyKY@&#d9GnT)(Ju9Zu0wB^~X8pes+hD(I%eP zDtd;5)_cplR0&3_5koeEfKe+s&Cl@?Z5G^l1V$w^&VzBz&anngYZqDy-`z zEkg1%ygl`0Yu}Q;y!NVnt-KLV$A9j!WGRO&3)?e^B2Rg4h@rw+@H#n8cr^xWkeTP_ z*E$2jYpob>SJrd(EnK__t2H37!5I1Y*hV zWM#-?HTcF%r>?B|GxJ%(u>lBC1#;C+^%IGbGV8z+vUYI~Zbnsxa;(f;{MeS^V3Skl;}vNP5Pf6a$ZiJ z1dow0>iFN}?gHsR7>EZ15mQlxedeLY10UCv@v8S6H>;Q0fUUp8$7%| zcTl-YQpXolwHxOsf9;$MQ50^r8C9`F&>kob4m2CGjo>I#!Q&I58ATc0I^NOFci!pe zn1DdK&GszVm#C#J;ZVh9tT3S%t; zx$MCzu~vXGF&oAs74Jb)U(C_Trr}t& zs%be|*Epi7nc0jQLT{o(PYH>f|vBkd};6Ega#}7T9tkdhR7JMQ^8=+)yV| z{V}JuDM{xEKgB-j{h7}cXBQ5;ZaXsC2r8_>xv5}EF}PxEpvZxSq%N61@`ejivY9wK zsbo10CPzeAlVcUz$xU@=j*CbMC=h|CBW_#M2`z<1p>#q0LoR9u2wv3)(Gt3X&s^i zHf3F(`=`00mA?xLK>n74%^}qn!+_PykXO(vyYS>nMKiIqsqRV7F|tQ-k9pLc#grHX z?x2gei`FeqJ(gWf(X4?GXL99d5lrXdQNeVfcH5fsV9Dt#Y@Ju~r)-j@Y<8lqfy(5| zJ+wkpn1xuC#$L6?PfjEEHB1KlufN0USVJ)BXC6h7_rLmkrwzCtatySl=h86Z=Q287 zjOq!y0mAn;yHKk<+34%RHQOkN5Fz6^n(+j9TV$7WgVzrc6b(s*F75dBLNj9cm|l}3 z6&|spbW65yocF{f#tO^}tv0A`W+-p!6jAjPV&mPqXQMByGx4<|c&ziy>!$D0UBj_J zS(K$#-!$jQb6j;(SgbIjRw1R3S53unxtZs}DS~MZSVAZzu`5W^)T&DqjO(=R8WM-= zDq=XYeK5_8jkOiihh*g(6B0ITd5XV#r-7)&eoq=kWkASbx9I*}qqHtZ{)=VVEPute z;=;9Rvw78~WYe~BT~_Ut?EgL_{t$(b4CQv;0hax3fR71=|L~9iuFj|!7+RYM+gZEV z*#7IykfeIYSLQBpiiH2Jp*u>lJIw zaN_(O9JFn(`+#WX6a5+VpP@|qCy~Fu=Pda4%b&HeOG`_qQWDf3Z#YfwyXmt3`2E_C z*IEm{-1&t_^)o_n=M2L{oAXz(TxrZC`!yISN-T&HBPOQIbf$XbDfc?CuCB(kYANYHX;UW@3j-Cu;I7aHdbSSyOf8fy)TTcU!acAKwSt7XmAI4p}_7X*dRS)$$m#aX@cZcl*1 z05A0ik-h6^;W)poeO}m0b5f)*rdH1mF02DNM{f#Ono&YdOVY?8i4DASTCT8rC(|AU zNY;Q}3_R80#o9d6x!qQq(=?XBa_k-VXjG$P%;rolmP$TbU|(H)l8J^^Y;3(9iR`ny zMNIbWg*PlUC_?@hvQnk2qu3$Z`^cpX0-Ob!-NXVmWfg~{GdVMHfNjhP*wTeDSqUq* zrm;Ghf6-jxkcXLJKkTdYXZOZTod6F2P^FKc!8#=MGei-M2Pzm5m_@HQ zDHWrpYwy6vV6cMaM*9vq_|6OyJ>UmT9QbV zX8e?)*XqRk(7=D?YxO3q(C_ks|JtV#Hoq32D4LTs0HD;-%-_gIA|n2T6E_O z;dR{h%{*@0_W8UH+;*)Q$8L6!F0WLuqD3N2!8-G`lXc0G0Mv%a?cm~Gp~>xnpIUwe z5#7=!zc4w3vZLcwQIWV7QJY4ihj%uJ!i8QZLYA+w(9L@AUf@mGO*cIP8HhG0M8St; z!NEDDG&i2m!4C~kXs}<OM3FVBnr#FjNwMu7qdk~}+p*7$T_+I&-iR5BCkd`_|9kRSO2Pi{r61d{1j zFKV)m-b`@BW0Rn(7t=>GZE;A7^aDc}O7u0C1E>V}ldZus-0rujH)zV7fre*_e?8hKh~65cH_hG}luo^? zbMPO6S2*C?6i>4LcIDMiadOolmV1)^037*tn9>+>UT~Iygv8x7cO|+W0(rFrb5Nvs zR%9Xr4g~?#E7*)S=erS{7hnT~WC(?Dc}#cCpn;S~SXM*Z zg8aJ_2JEpzD?hM8c?BYk+(o&|FUS}Mkv;I9rTdtic58|sLmB=J+Cd<7g^EBBkNxcJ1w$oR{S#^ z9|x*=|2bk}$QupmtQv|}99Fc39&g)y9d!7|mV=no*2s)QR7y2_R1R3XNuR)gcndFQ zB&TDRJ~&f&V2bD4m`Lq98PvD>qM=}EU;K5D3rho6@6x|I`G46WDsEg3RS-4!_q>w@ZW}B^_y%%h23DvVVJ(AE zFbS+Mkp< zNE5eD6v*wMVD6_s1hS)`k;KT4J4Mr9oOcxe=e9vm>%A^1|*$s z+(#u548lG&i`!YKaVa-+Si$&UgI^GPcbs#S3biNY9xBQl!uRP}y%i?BYj;vELd{OODMXH<)g9`srXA!4J zB;BP@$3zdFty+`YM( z@$^E%m1{ua;`g{Z8*m9R@Wqa`PC__W8!p;J{^e%jX`x`{$*%N=QNTjEKB$usR;5d< zvx`K>7+2PBv&j;+^?-=42=LXwe0Z0L3Lf9XJ@PC5DB7C7x^IT(Vyum@CZSf-mb8&| z!#NoiAKY-y0T;qPy9dDkS62~spo@>Z$I5z6^X0SNcO9$`ZJCua`^LdzRE@Up01aO8 zBdxKv-f!8QJ>O(*E{p0SDAgL$TkO9T*0L+Lws8qGpZJ)RF&jgSyd$L_n8ADLgAynX zl&D-yF<6kA7hny66%?`9B_nAm*yFgO`Tjrm#{OtRB5${Dl>v$Pfu^ugTUxpXfe5YDKNtfM1dU2nQ?MwXFQ4m=K9QArbVB&F&>r!>h7{{? zdZmLz)a!ZqxSftBB7HwzK0sA@20X(lH>wJPgHRu9Q}ro(-@(Q~=DTY`aI`?=9nU?L zvdZ)2>l?c+{Agm<9V<^GGbe29)X}!uAF?^|o|iT2#+m(Idd2r&4_taxrkiFzf_mnU zzP94yLNB;lr+{I%vE8{k&k;T9x=4u~Ick;OC~)FkqST@n!UzzqGY410bQ5!qx%L!Eo>3RUYwkUYl z7j#Mh?hi|;#zdMbS(|PEN6nKYtwPLzbDubNchE8Q9h$z@Id4E4f!E<2tb9^Ic0XpB z6N|mArX!Hnhf8_m#6s6ekSNC`u%?fCFo^-Ix%nACZizi~=?FuGPc)~ZGX$^$T78`@ z$}JkO+V|<$!Km*le!7QY{v~ouUFG*s2@?O_Pb$X^uEz{}uyZmaq8Hc5wt#cSaI4VY zX1<{R_X+oh{Y?$qq5qhoK>{|R|C#;&oc+sE*0Mtu1m2yB4RXL?6aoU8r203SaG9wDMv?-p;$X#SQkC>QeJy{tWFJ8=%7bQSr(I{U?#4=<|l{fs2Yl+*PM&h{c-ie3%a8Z zr3G)T-K5}B8$}Dl3d!wmZLr*nuYEQLlE#RN;`15d+vV$&z*D-xtsT;YtFEl*P7ScZ{9v6d2;4wd7xUIbbH z#D+?L95kgM1U33vDW=-7jx6-t5>k)?y<*95e1XN-JXIQ7>Zt#-Qe$$h%rS^0q%#|K z#{*hr^DTIpe3*RP1I_a%kY#f>(1{>_};6Z z&q0Qd8-y8)OXYqf!B5EF6vIw#zze|puq@1NzzB5wG1d0N>2g2E*Y`Kd=byc9lPf`q zQj->FT^t33Rm7o;;AX0UjKzDnQ`D`w`B)exX4<`9(Xi>LQx#_YBf}*-3wA8QmQ}2W zM8TI;8>QdjI&u7}2`loYADl+Sd%liLeu1V6XDFPeT-{JI$S>^kr_HbcZ$aaL?Y&+& zpoB;^TwyWPs-B@`%*!yr|5TeL*hTb|iOJfUW_BVaf6Dx#J(tB=DRgsJbQnhze8t$$ zG~f`N_heb1C{BM(B%i|KG8YLG0)cJ*LPn8^#669rIUhVYoh+?!sXf;~W|42fU6O&) zZZzVfiiIlLZ^9AVoF$}`v*aY2&2rJ#TUO|CtKc}a)36~~e9>2$jneEyVIrF{C@D;i zf_1$hi-j%xt>8PBP#oq%|a0Gjw54{W1AK`j%6|UlfW3CnY?4|9+k?)|A z*uTo4qIs3?GJk=~*@uDC^T+&<>c_!UyJ`xyW2oHK;xSx#oT$cv$hMuT_t>LkUn|^D z^J}#(XhO~lkez|wbKYX@(0*F%t6dzvlkwV2wLJyDbQf0?@4QAro4I|~zND=xp(EuJW*cocKq%?R?%S=T#=U9L zj>v$r;~7Qo@~L0>;ituV z_xIu)-q*r~Squ(21ixiQ38lW40luG*Jg1-Fk)spgcWce6_R!)%vSUcP5gtO1Sm#%e zpTE!%)`seve$Fe%1DGQjASXc+kB`~Ybmk{II+;#`EI?;>!~+78PYDBMfA|mfh3WN_ zpC!u}ey&1mq&0LR!`A6ZE|8MZ5b0QOQ<*tE%}IHYh}3qG^p3o%Q;>zPkM#SCjA2O2J7vC6?x4**Ce*5hALt$v!Sx=(6<|T5mRkz%sOJ(1)Y^*6!w@4e3 zG8{VEFw}7^g1|nqkilbWhjlDps5^wRdOs8qJ}YezI<>Gy?vTo7#YQf{M294>dnK_GeBu{Uu0slQ&_-%Y zHZFS2yFbf$FcfU{Wn2|6cL%lbVtn@WAJfC(<$SkG`ThR4ovZn>d@pcdz>fqbd@TRj z6AL-oS(!M>+S&dgiSk$WpQN-3TuS-l>ZpSaEPOGV{un-?gLG!{&36GFg)$`tfJg=5 zwLjTttc?AI6KOuD2lz)63=0qOe;M< zZy#rMf9Y#&;r!C)_81U{YLw@1y74W4?@@?1f+TI|j7XI)zL3v!rF24lt+W{vTFXJ5 zfkPH6?6|+HS)rp7FMil&2oJxxHGVCd6UI{-f?*|!0@;%KK$3VwImgP`BkN(R!axKt zs|Db`R&W_;UJy?HEnfro6uM5)+u#5Z9i?0g_r#{Qiv?&nMP|kMdETxbdoI`IWEd7!{ zBVnk-;@>;KZ`_X85^+wKW~+!l4raNBK+vliqH{d_zu zA8Lpzih|n@b+?m_zKnt?xHbS6ncf-}$u=xw9LF3t+$wn#_<3BQw}({NOUvCto)I<$ zIwFN zbLljpeo0_&;<*9a)~7tlWS-}sLs1IPREyLr|PxFqGCTc`1IKiUCE$Qdu!1^V23}Ur2CHLzkyEbT7;%H>B z@M0~q{Dcz*IO-@vkQzWRn2$&Ii;l5{i^`<~q~rP%5`}6NZt*MyGCH^H=h3vTPsq|D z$|o4E=&kZK7&>``J1HsWuiU55AiaL*cle{0rSGwFqJ*{|B?peqC(H2MnL+Qbi>=Pg z1cYF>&rC8n9RZ;6E!-oq!X89~aT}nsEmQlM!EdpVB`hTj1bg#?S&~tCmTM!?&eE4G zr)Fp+&zpA-?$lRpY&~Ijj|K~W^3q|xA+6H9Pex&JIc`*x49(z#nfi!p-v%BYl-A~r zY-@&XiA`bY9(v`0q3f|ZR9nVIsg7Ifx7S>RtKgJ zZIJ)K*Z;NNrKbez zAuwgiS;LcY_9IS$!DJekE!{sq-86X6760^zVd<#%Gy2BdY`@qJya3P#tIsg(l@k$Z zmLio29J%D;+>$3^p;z^eSfV3|hRdirgk(Yuj6ja}?G{vzNxlx9crMnFJs7SG z*#4Z2V~gwk(qHtOZ+^ffl43*=k6mU%M0+?jOiRiGM+1d3G9$GI56?|jhS<&!dY;Qv zW~1L5uYv67>idheld56pyVUN$8?hUyaHqoyC1pHJxu8&!y10=Mm6hLkt>!o6QT|0M z?u%t6!8W-!+k)uREg5ZCZv5FeQIY`KrNjKdS|Kn%w8Hr zPv2WB{3R|)@*7B5y2NkYA2Aw>w}igIyi=zW*K|;?sOcoA2u3_8l_iC1M?yEN>v#8j zux2blyWXkP-h$tqP>Yb%_}-j-m)m1WZ^UY}c(fPkh>^2FKW^gGUce9bQnt!0_exp_ zGa6`xLa@hm5CRqe+(5{~x`LI7TtPzmG0Uuka#sG9>mTXnqWA%eyS>1W_FpGuf9}I2 zDay*@02ctZ0=;Oo(r@5s@8X5O1i_(YmU0NFLRsV?3vOgh);a)|Myv$hsADN4c<&&u z3KWKkkVCa-R+(>keoeZ+-Mu{$Km+>K35-&!@q$Q#F z?PUX{@`fc$oZUUPh@5D+DM=FIqD?%Bqa3dhY1)Iq2@!vqVWoAc9C0$vVV602N8~8f zwcA9EA^V*UNiC$PGs&1F2hvlRJ=u6$7`_x~pQb$$pGPC*hSPtRqDZMj)yGoinV8i? zD(zbO*gRFX28V`Y&$KI`fTAHYj-gm8&iJkJEJ|fp`c1%t4n-(}-xt=_F`-(k8Ljp3 zy1MV!J+n(T8gGxCfGEAfcV`pM5rj`V6Lfk9^wsY+jnz+Hc(N?7&#n2gdag6%@q6C| zcei(c4eT?1G2}63wD3X6AnV$us@){ptChJAY-UfQ7}nxE4ewjT>r1mLzzR^s=lew$GbKVN2K~5-c1l z5mU+N68)u{Ry%muMbA}+Z%0i%wR+oQj(PJz+39B}3DIOrG|m^wpY7Wo(^}Q$rNZ=3 zd7Rh&)n9NUi13}W4D!Ylp2<-Ke@gvMh=tpB0?|X3dP@?%w1QX%T%P#eY4aF|Uc`;Z zmT_mV5Zr4C1%t!3wc9y7#!8D1-WtnX8-5BDV0857fX>R4kmRj(3f`$A#?Wtm;As|8k8ih}L9G8a3j3YX##WZ;ct=C)u9Gpf(> zR5wp>??P`}_G+`~8Vs5uA6SL*z-{n6hu=NJnpl@5AQ})>4(-Zfy+bzX$Qcg1daDbs z$!h=(t0)|Xe;nXZ> z`$T+9=bV9qpJ*o2;Ror4SajHj3t0XjWtR&P23TAm^s0=ZVq%xs~ zGDp9f1mV6|;*fI!r(EsZvt6h+M}LvFSGdS2*enWZiG*z2N9T8rXHZ6d@yH!DbGvVR9oWZ#e`x_aH^4t6znt6v6ug$YiJwe?CXZR~7-zKZRag?GR`uLlJ;=uKbi58k`J zt!Q?rlhDND;rC%GA8AYaXb`-LSCG}E4haMaNIWqQ#Ms37M3OZ1xr9&N3wWs?uX+yE%5gsO~@ z)~&JO*!BsE5ZedqV;~2<^t109nA%IBhjTiri?7z>Q*a;%chXtfs2tq>#AI6ln6)Uc zXn|kP-cv0#Yg6OGejNQeDic2&KZ6fwXLzuv6P^KIggQJ-<*~+^=_Vj^O+9q&2m_FV zk^}Oa`6uW(FS>?n)C7lUqJH%Q6WtV|?zJtVRFe^47)Vq0UZ!I4$lGVF9W#V;P{$>4 z8jT#}nZtx0tkO6&drH5K%mJNVrTD~evQsdB7ou2GKOA5HBIfW<5c_jQ4TzW(@Up$n z1G3Yk2b)D1$2aJBy@JIHn)5ENg%MSg5XzWooTSK%s ztT-?roSO*omcsgjF{;xEul4if^pW2xI&@p-Z-PJL#ooNNK1vMQN!Sc2<{ANc`f<-T z=RGGFqKFS>?L#qB83ox;OB<^b(?P0m6%d*Y|FBl5JK{Q$98ch8+)Asee;3DSg^d3F zdk2ze;gWC~=|J)jU}Eu`wveE!Gotvk_)T0@L>71dEQhP zRt=M%VY(hgLH1*1moy%&0w<#hzXZ!QHMFs1aeI+NYO63*P-HiH@Y|%>PApDbRTqmw z+wx1R(d}Mkl$Ve%Htx)%nog0>8il#&*AF%8Sf( znh^m&j|1O==i4rislMrN5L59lHDX_qUWh7xA~7BDfk~T1$1;y0{p8R6YOL`V4t0)G zT=k1Wi(kn<%_tVhFOF^xSsBopIh4$pMMl`l&=CH20%!Bbo|yhAft-KNvHlx@Hl$6K zMt=~vu3cV57_JZ@v|BDzL;w~i%OqK?`)h2AY%u5R!k0gM9Lzw<58^3Z68}U?bXPZ1 z+anKC*CW2)Z%~-0&SLl^B(yDbB7h4?47m$1OFPuXx zeXTq5yb);jJP2}dvzdnBKHnnD6;{e7a3d&tf-Ae=fOf9DggJ2A5^ERdGG`nM_&$jM zu2!+%OLGZr%)ZYCh5AOAU-JG=R)$fJRbO;6OpA3Xg@);EJdqv!g$gxKP&8?>_rA>UVdL_h2p%_rRuxrD|CGE?+EL#~NxuBgHWMwou$fTydu)AvB3K+D(anr8Eig8(JW#3mNV@Nu}7%M&J zwYpT4hDnr{Sw%pWeG3OkD=)M4z$Lp>5F#qD3ncQr#o{;+Gp?YAQ@L#7yIkq}ft}!* zyj^v}?*r1HGxyetUe)Y|SmWAEY2dy=oIoDf(@n5V>aj6p%;7C&*NEI|BWn61t*{ynJ^ zPrP}?0WxR|^n$Gabxoe(PY6~1*9{(ZvRTyr3qnaNx{_0J(V1LAZI#ly3TwKIgT4D^ z>t@Oinmn~@U<+Q4@hS>u8T}<*`D+-5Pw) zQ@tM?kkNXW>%Y#%X1!6TXfkqYz*5bEh3%&T8uRPco}v=~uSr6GY4p8*KqS5FFOROGS#(1{q#w=*w>CY@kwtp_k+qEO`PJ^dQ$1*@&7Q6*^O zk$s(N;sW_74dK+!u$Q-_Nac99*MQi;9BQXQJ0&=9{4)u%n!q#ZCUQ^M^F{pu@{RqF zH58OM^Ir~UHwz<_I%1?tQ;vrAt_Ob5gAF5yx*yyw{R8a)fytl%7`8JyHpPaz!?_b| zHXaPwC$`JB))?#nb~6Lm!9?AO1i#06Zsa^uifLn95hL2t2-vi8%4!~Fw8JkHRK7&DHhzJKJG<}qcv$C<;Y-t~ z_J^eA(B_4z+H!f-g%0WtjIt7}MX!U~|4tj8d363CBOhG+*-)!IEU_ot^|XO@b<|Eb&rdhKnPTYc#w0s z?f7ZK>UDfsKF7cSbQ0RSGEuRQmANu~*Atw5rq*E|7;0OvsD$y^8zmqehH5eIo??I8 z&&SEMf86TC!MZZd{+L$S@=Y<08!;Z)!IY|O_rnZNHTbH~etyf&6B#H!@8;>_<-Ckg z3?)Km5+{SB1)D)$0$+p)WrG>h=t+B5EoEHXXuuZ>)`Hc3X}!RYdV^451qnOY%!LZO zC_uJ1n7fVig7#~GMHxY5WFNG;D6AwwR2dtzl$u+T*qQstg37#@HDOz9{5a+UVF z*u$$rnyBD%a99~l`h~ba_zo#yTDIk-D&Ok4dJ-2lGJA1>TaQ0}3HG3GqGvqZvuqZ%TR`tJGstf`AE$)2Kafz(X!U!9 zp9eNm(#^STy?!~Wl2^D&M*(aNfek)-Jm5xs9Sc_N=P?-87lLX1;#5wzd!urW^*plJ z#W)%%X%e{ynfX$t%=o*Ct9(hj+Qs1P2r!;Y4Ipj;Np_l2r8%N$Hy!@{<8M=Cr&5($ zFhIlv{}Ez;wuAo4>zXl~jyhQ^%>Q^hAX~*ZuWDQHQ~INW?`kGV{1`i`m!$jmpjt4n zY9$h@G}*f~H8I6=`>}uA@~gHsKnHTUPVIZtJ;r|qe z5Ccss=EgS>xK<9dUWFS9te;Wt3){yD3fYo_@5Wa`UkQr1WcC!u&!B|!ZYh_9%lf}7 zWCxS_4DBo)2Uy@TAHc_F8&fy&Lp*owr5=YSD&p;NKE$)qxWX8zvmjW?`> zCqgXyRkE{?MpVjyE`}ogW#ccQEb9k=ST5$Bc>|E-Pr9BDAR@ z5i?VcNzKm-E&Y3iZ-*_G7KZ484o9m&!ESySEzUY!Q?*Gzhk1rLf(rznnHuaNO|!%R z=tnVkyq_uH2a7hpOj98;6Tl^5^?|3l^pxOERGRI#>T{)wO3K>r6UIvT!s0cHGUHz@ zcd@3VhATDf)y}SESRM~YY=*T-KdIB;v66>Mc=JreVR+@dXOoavM~cx3XmgbrZCF5| zZfR#I4db4W6~7JOgw4M}FKxW|2TiBRrG0qJ`85T6&DRk|kpovv-PP@##OmX2hZAb@ zhz@cBC?6EJnx<0T`1YV%TpKu`RF=YB-TcWivwXUaa;?bDNQ7-@Ndi$Hy zVxed!7zB{6-@qF5e;+RYY||wDuLElcu1$8?cQOJUljx|n9drjF>IFkqf{+jezxLl) zuu`5gmfDbeqbX!0#RsZdPz*F_L0J&y#^J8BA7_7yuDN|5Ubd^PjYtN?!4XBZ@9!mR z4!>(Z-9fiqFI{UjGRnxy4K`4-VPttH#j|%H_0Ky{WLm+wKDpw{3BCaoZ^%Q)&m9EF zAjEU1NWOf}TY1K;$Wt*8pg|CZ<~KC|7BRcf4*UFGNRK7ugE@|o;2>XIBTAn?@&{aJ z;VcWU9t<_$f53%5%(NLJk}MhCh*7R>vrU5V6{(aVOmFm#L2|f$y7u&$gb_^}mzu7G zN$su9k7v<}Bqc8N{)b;ETS6Rc;?G~?v&Ogytv56V`F)|U2E~GMyd#4K17pvUO#+Ta7XAunM?=|x4 z@~4xJZfE49gCk%X^@f@TZg}RAS&U_tgs$^8dkY2)3scZ>$vPxz@UZ@yvHDu{%4Yv|ob?L8- z4lk2Cwun9aTVo??;6RQH2p0Q4a$A3PHjM1VjgH5#(g4dEp z^~ka^?G>Zg_+E@funII;O?WF$8f4UDd{3W!60hZm(ksP^MkhAfoQ^h}eq{lh8i&*@ z8^Y@%@a#7XwKj&3?2FFS^7A$*Xd8^RRy)<7Gl;0g1(htAShSH4@P4h_if=#-*7Cac z^$MGzU!mXwFEa8)4RYfXgi^#3`wYbLzaWkxrTEFy#DF^T8b; z42mT0o@21?I=nUE)QNI>g%wlSkRy~$$(=MVA@XcTm@n%-;ywC`ha^Xi6h=))Cu4%h zWgANo>p@yvH=P4XRdL_&f(>nF0H+E=Hic8Fu_C;gf5b+^VUBIC(Ha8@I~DGckgg(g zA}V}rVkaYo)%=kf8$VVy0-IjtSW|->ZeXMs^3G7$(CEt#lfQ0P+VI)1|gR%P=eR9XyWv4m4H!weW*ybWcByA&YAvrFCHWt4%iQOv5%H9V3pq2fr$!g!B3RDuI zD7x?8)-U1&ga)bqF?fGf>i+^SPUr$UzoKHiy<$%jP?-BOVaWJvdaCoHF&sj7Y90{0 zi((*n`jIc9|A{@JaRGF7$C)|{eHiscXIk-j7f_RpnYya|V)zImk`P^OTZV-!2JDr$ z*bZTX*WFy!`uC4&-ur>LP-}8rp3pvZ;xzmdClnJ zIRlB7{%rjbZe7A`nIRn{-ea8{9#L}Vw(Z@xk_Q+wO(@e(0nZ#?L>3-AaA9s(Qo^-u zFO}duE~V&Uv%^XW#EfQ*RvI&ravU=VB(-Ej2`{?9ZWLC-w8e;}d_j?HXuNi%X01X# zjB#UI``-FQJi9O4>p-v|cS#)wN#sqMJeBlxc4aGb4f}d5@KjN_OVbuM+M#gZ@MKY1 z971|^ne3DjLa#j-YI9u03lk0YW1uL@{KF@5HK-ofnDQ`}j$Br5y}6`c9vcWP&Vz_q z$U=DOL_;u3x77|GnM`!YJobUkZkzPeCdF@rXu+7E%wb-$ELg46TtR;MWL=GDF~uf< zE{;-qQ6q3O^BnRD(Qk*0EE8O{{BLj}p0c-weHSI;S)a>r9`*1WH)bCbD?_UXnM-{Y;DDy0GwF zN4r!v{o|e|j>=b* zr6MG^7SOM*SEQIvSl|47sH^`B9Gukg+?z)Z)yxa~mXL^?>T>5JQMAoR902DduI%+O z&vPUU$-|D=jU5Bluxrb)cHk1!C{MKJ_9OqHZH#mHcaP}Z=VMMe;fYSNilA>@p+R+h z%Gm*ZGOsX}+lcSGfd4|`oWe>+be@oZ79zZDm z0D#~h0q|F)U+G_}>+`fUI_lvRKK_JP3KdK&y-&G}RI>xdXvAZ$&TWm#DR`M#$xjWD z{$U6opMbsmsg;2N%miB|Znvo|r^~i)z~d$T}K^ zi)XJ7-KZX5{JGH*qQkbB0G(bk^xr9@u|L!D zCQpMNf-yemx^~+rkt+*B8BzAt!c{B3j6mTaroym6t3Djaxe2tP0A5NOWYnXd?4U$w zw+A*QrJyD>UlvluH>WC{hfSxaBZ$R2`_38FStue zy`edepD)@%`HYhH7ltRS>eC4>M76Q0d3HCcCmMg`x(1&h@L#(-hjVbjV5j&33Giy{ zNj1}h%B*Nb1b7AYJ+|rndr2U*QRSry2mszc0^qMY{Qu(t_D`A@Usl)DUTZfk=YydA ziO=#0BMD~a9)I4svag?tFt5^n(DWi)3C@c9Cc2|wfc+oNzOgY6wduAE8{4*R+qUgA zw$s?QZQDuH*tTsq*15Z%XTR?```g*S;ab+c??)tNRIWPtVU#gzJM7 zAWW^Z{5Wbz+I>Kao~(FK|rpG%sJb0Nk?nG(LyGfvjw<&%>lDlg%n* zNRs7+q>%H%K8Iwjd+Q>-_C{Vm&1jr%xji*@k{bVWTg@FT3*Nr&p~) zX+53ZLvSLOUy?89nA1V1oD{z0GpUV?Qh3bjYzBf#wN`3)Z6BOPFr9w#P`<6-5!jpB zUfgzM4|cSRJ5KBaS?I$e`21#2X2aW~wy>Uk^nahfXZfy`Ucqg+K`r%rkJtrKKHppa z-V9s+%ww+ojq9dxpO{pn`$N&sw#cBLyWq$VNB`Ucqm5_TYyiAJ{=a(vzg2L+`~TSq zOpxiAXlHF|_Gw;>;q6!Ef|&h_+=W?W47nz=JU(l9hz`!gccwLv;k58aclUW6i__?NC7V1I#o7+6fpQu$f@ur z1$g{P0UHTKe<8plrpjH$We9+PSN`0(&t@m@KL`j(KM?~Uz?iWG4&-$0Fcu^|jrQdE zx>f3-=oEE)+>BIhz^{JwYRGs43-t&8OC|M)4O)45XxgN3k{Z`zTS)O}7V81-3XxIF ziM)PM63o1~g!3tJRH>5#RUr2b`!b4^;fb#R&`3ANYG$^jt=rAj>=`ech(F+_#&~~g z+%739$bKNqw{cLcAWBb%7OvcN2z{lqx)763H}tFf-ukOK_^aRXK{N?O|aIq+AjuEhUeco;GVWe0;tFfzIoa^g&cG^Y$#gEYySQ z;r2X(^*BH=Ux}gS)-Z>6ud}f&I7z9P<~$82zSmF5lx5XDf?|4- z6`du_aa3?mL-QdoTgi|*dg|qNTf>Uyrk%v=LdUcl>y_E`4ttq8sGL((yn8?|b{TyA zRyZ{pf9~=p4yKwGoy* zs&tTJ^3uy)N|QT$@#Al>g_X$7ItkB~iv|O{zre__CE)#W$t&L7!C3!#f4Pcqs~%-% znaJOV<(=LgLp+TtmyKn*@i>^C!XZEzSyq+BlI2EZ7LrK`a6+R-c;EmcSo!&f2(lq% z%pqOC)V%9>3^^(hFH0gAQ4A&_tJRU<9|4FU$1=H!$}n00!-1^+-X9{!wcA4i@9c%d zc0@r^t5`vCFT*lF&`(Jhs?#k4#T^7VztIi1sdX(EpyrR{BZz$gAOah5@Ecfo({5-E zvbSrF2;O}Z(MotsH4~OT?Irf6#n(SvYij4cV+O^9eV@XH3a&y6@C5eGuR7C$V;0fx z&K><6ajE%e}q1n198h8d_qfayF z5t+fj3WB;WzO>c>#FbVTq}EQ_RbL}rlvM`@x+;c8 z$wpY>sOnyb;X_@!lqCmnwC0~@BO{(3aT2K^D*>*zUC1>yCShu$j7eJf;07l84-+gW zT%%LbK_c>WRN36zI>O}yFhOP8Vc001A|$^k8&*{%l7+Mc=AzO6WrB+z049i?7WNkt zRQsgu+5<2_tK!f)bN~}vTSks`WW}#Vwn{9g+~3ic1~9=-025sL!vvW-y2Cv05_cuk z%SZkTfEoY*WNC2wfND^-zFk>AmGn~ea_|vG!#-ihZmNZDZcLebLH~ydVz&OYKQ%gZ zR-cG@B&3}{&c41xLG{d;e)2!W^Vz*27sPJzA3WG{XM(}2Vmw??13IEi29Bsvciz%LRsp-zY1<&UqM=yiJU0(D48V~SvLkJN`t!`3oB--XJlAW`o0;@!yM!?$O~_d z?MuEMn<=YtkZ1Z7_tVFdH7$b-NMPx=L~h!#e>6bauD4aDGc)F3C>xba1iU{e@Spd; ziO9!an^4g}Bzk31*=+NS0T97?3#Cp%F8YVW7a*()<3du&awakh!Jq`W@OVyA z0gwQWmm{qurvOCo*7ZbZDhk1XNi;MFUW=i*jsoAzia%FnWmqL{B&7?XNLK$nXA)6| z6A%F8ZC^=krW7Uhn}}UYH+zcbmwlFyfwQ8jZhtNBX|W8=r5-h%zwUH>{hW6bVLu`p znE9GRRd|Pz?|8;!iC2+%_x7L-5~04&{=jgTUU^vJ^%boIGRFN85-Lls)LY;kj_=lO zS`eN3uaZy(P!RkA6a;{huvWS5iB)Aw>z`bJvSa{z9DoDOe}#j;rO>}i!fKuN%I2~k zdidl6%F1BU?z8Y;Nf?6CqsQU^hTzGGYP_@;jEr?pe27BxOh>Fq;GQOU;n7}y>^ueYUbZDG=S1&?ooWTHUAgjDXoJA!km3*{d zjOQ(Ipfu7ZDyJ8t4-ON~lcR3F%lQ_R+V>`Ga24~*Oj&cG<{K>v;U|fkpGU1$Ikh%D z86~ggPng-VZBq@$h)$2hIM1H+e>V7#enjO68DqlPu@=pRYsn56T8f-^+T0}k1 zE+LpX@gni!mq;qsm!SFJmn|kp9q;7vS6X-w%~4mUBlZet+kh9U&^2hS%F2ZR*5Y!# z#OQ!(;bR=#;WY^}CM+&1jnZ~=V-GjI{P$|Q+WyNTj)I(imrCTku!Qq2iOjIuGkGyL zgnc8`a{uXPJV<>sj|&OULhtqHR#6KOn^&=%2)&1-qCY(8ggkkqgk@)i!t#xf9r$W4q@z8 zOU~t&UM%i<^uAkv-E*&gzkeN)gEoSYYZ(4Df*;_Cu|5=lFdnsSfEshnF6A@Qc{oR2 zQWrZ&C`4&9Y;F@|u%gs{dCU5U^$SMXyXyo@49i9>JbSvjR$!tO|N~8Ary#P@S~invYv(JxExXpx&oYtYA3OT9*AGs*SWmaJrA$`X7p$R|gm;~rARVUP7L6d>Q>Y!e4U_>2d zf<-G{ID><+n5m9EG=&{JQog-v%Z?XV<1|qgO1|riP*i7W1S4lvY1B7JnvA~wwas8S z$F<9K8QEu?-t%BOJJL`!L} zeMvTPFOP#$LFnEE=BS3G#0fg7$9P?}su?nhadRN8nf7}Fmv54;QC#TM@+R4>ME$Hu zB8JafC;9ay`LyHhZutBXc|fD}{3%63K+52{y-OvG{lY?6!- zr1<~tA2a?fcPgJL0r9#yl3IZZLKqb;B`*n!%&7}Ea7HqDtVMWpH z%Y9A@o4sho9$;NH#Nmx~{$?d zGqjfJ+^jQ$JUarO1d73p;=pmOgEH(M>}If}rjoag=-zgM=#^nWxsjGJ&KVE~GwPp5 zclBaeq(BAPHW-0R@cx|J-{nlJW8ESk1U~)+jO*xC(3A%^*gv)`I#EgOkUWG}SJ7sh z57mJwmPNr7UE-+aiPhCjiNjFqX|xSZ;quHLnYI957Dw2yR=a0Y{%rlbu!gX9%qvPs zZsD04a{)ZFu>^G+XHhR-K*ltc6}0}OD3F+pc7r_*+prozETzV}%qbzU8fpVuJhTvk zIB&e1Fh%TvJd*ije`i!itH~bg<#KC9*G8XzSY?#RI$NFbo*x*VVRY|Ftp{{ij(Zhq zi?9Sk=4K1EJDak<=D>8;A`14EaJUm|=zMcBagD-2v?B@ zt;@3s6x9GzSyz*pgZ*fO=M+)108O52=c`-aL*`G{M~Ad`&h&3V`S~?rI3hYKoNK+m zw}i8NxeJ47&Ji@0K zG8|jQwe6k%MD=(m-lI{c!QSjU&(S0khZ%$-j!Z#k)ivmMi?ZK?DUNE1^Kp>| zF$^P^SLHE(ow_d)X*7MpwM`4H8E^5h#oG6KuZ@SHocJ2V_ZX8W(cvcgYi05j^!Ni; zzjCO9w*LC_IM1NG#H;Ay+7bQAV3pojmrnjlHWx}ko9l47Yym*ivS*~IoV#jfwkeHAcP~SjaKTAe-Kyecf*-qJnJpB zfhJIJ%r2m;0UIXq&}r<-Ip8d>A|_g`;Rff+$2fF~{<4XAWHm@xr=sB=+f9~g=DT`9 zp;Qt>iLZFred03YoRTDkrMPZOr6yj%L0&t`bw(|54vR_tR05Z3zhF$7qKt-f`dvXJ zkc+CF%kqfwK4z&i?M$L2Pveb4_1{NMgWW1%o6Wdju+Um73(@8cODP7DkrlEGa_h8; zjK{(&ME9@5uNkC1AtS1%H82C(Ax<*MMZPwd-+ZPwED zNrq6}1NZ%Ma}S4MRwIHWG#xBu<7&!yH_pV=;rSb|chI)A>o*$KNPJ$zqY=#{zeu$nvfw|%X@JC z0>JT5)aV_f2hqMF@(X1=6Li=wfl}7MTOPr*`w{LT`Z!Bi(ymec{epOXbJuryYPFVd z!Rl*6{$yIVTesTLqMG~Qndv|Plj->;L~!@DMj1qdG<`I4TviF@V*SsBI7A1Yc&+?? z`cwzDXIj^%Y={2pZ%GLeI;f4hTPVyFY=jJu$57|wo#m>ikdE4hW| zefA4#_vC}Hq~x&_eaE#XMZpPSQNdfh?lDD#OX5s^!{bX&LHkB4;Y-sgo@{=)@f}w! zAA2Mq==}me-pQ>Hf9kC)EyhTgXmBLwi(sRJ8CYI%N)Tr=^^61@I;zr1!%T#I=+@hc z!g0EU0s} zdZZpamkNSM6jrpoM~zi`KibLF?9z`1*yEa@57~MJe2Ij@?-5DvaLE|%A;~RuQz)8i z!{CDm?GlUX`$4$cAwJk1_62^mLLH*wX0S63p^KQ2?M~If&6v_v^LE|cB)mUg5^_@% ze?CEvJ^5D*szIY~(Hnb-jXoieU6cW{#aouaz1ssOUyb0G3>7287xaeyq3J#}>YL>N zfR6fafX?){sIO!J06Mx)Fs)Swtvp8*;G#A)4gJBbFft_z3#g2x_?vLE33F%?tRTYqJ}HLdVE?=h0pE-R0R?OQ=5bBTb_Ld9bQ>U2K1CSRyE} z1ria1Vocih%cOd!;rcHSQWh=~!_|Hjq$PCC`F1TSWAs5t#!|bZG$mcM^@OA{n9C}e zI~p~sh#zRiCcLq7BDLh+Y9rgILX}VJYG;CNCW2}965M0-*;de|ZfU*4qbj}Ax^~`m zjT$sz+jUB};Pi>*#3G2N6Q$ zNG_!+23UL$jM>=EWb>|})KmCW5GhnT?3_uW@YYr&Iw470-!vBH#r10Qnc7o8Wo7by z=Q=@G9jSNG8I%Hd$h-(jEXqk`{yOw1;nAsKM@$J%F^`qgZhI8FY(}fW9_et^Yri_H zX(OW#cXZeqtEqiR-n|$B$LPqQ&bY;sJCX+0W34_>hyd88zO8S>b8RERsIu*6m<&}b z3yFE`d~@NYtvbY(a`jN3kI!SYN?`e8a2^$hHhXluK(Rvif_vC&)^_Q;cq=x!7O_dQzaL zhW9rfK_@X+z~#tqUcwN1RI!H{uh8V8dD?UrgKmM}Bj$yZ{jmBMdx@r^kNTg`3jwB( z_g@wEh;|3b3L~dd>AM5-l$@UmrqelNzCz-PV4~T@!SAB?lW-#BBM}^$WWvl9;lO(D zZvA7J>WcZ~vj>29h<}Cnzq=TK&^{-R>~m?ZrWz61OH3a6E3xZ~3H>#U0=m8_SZd_; zg43Q2a>t^RtI?C*8v-d~6drWWyF$O?SY&v=v5bf3WZRVc1#@+>AJC-S&u>Kp@{5-k*Gy?Wx9VqkJ+un7zmCN4wZK-`8nHT^gmPmihQRn;g0 z8%|9!7i-pO{Yw2a#N$N1+S+xO>-yG>wwBumQ?U5SJc^k1GK!%_(@5uFtjvRG0;j(G z?IwX4I`t~;mi6M;`c}&*tcIaTjUfkkvaM9tcV**0P6UwEWAB|tESQCmzlIR;jFdD01?$>pd57F;HkrApIGK`?17_f6 zr4KC)CIid%U*B6AWUi%TD^T0BaI?{ep@Z)1Zx4n6cDKS@j&EEOr%&a1_+5kUd0?cA z0FAW7T4BxxQu0-?Bfh>y;LucQIU3{lO^R#b;D8Aox@dgau8I$bD8vpd#ak`DTb98w zTlqGmZh}E$@&WU+Zju4%v+dC*@nt)_W6wo6@CmB1gi+c>5dm}`3~e)yTB7+j31$&h zrc%0ZJ-qVyUHKH2ScbNGbHmh{;l>fF%3JfQb1%@w*;mdpwv(t4i8p-7ieDq5mPl%p2L zc0kX`2uLFBi&cq&CA5l3D@gD079T&dil$l+AN!*I3cw!~sviM5egWT{`Nv%A3W4{Q zB;ZHb{2Q@`>F=hF@_*)gl-dFFJ*DJ8kooFOG%EXS!X$IJ7Nm-js~5BdNTI8bhiT(|_>?EL-Rv=Hv> z4!51W$nc2r2nw`0pstf_SxHBr2EQ;ZMV!(>+>zjeRrU5O| z*|IP_NMsk;e5V-24gF-4iAxo~X9rj2q;!6tK4uAh{AmPhyyi1xU&Z@ao&Z0K(%ND} z>kBBeO;o@VuP;+L`Q|s3czDmlj!|t*?QJILQqgjm-*LM-NG)X)^a`*KDq<4OiIS)C ziK9)>w~N&c4xnfZE=c0JB{tEZ!A#S2($P-y@mFlp9X5*-3|CwPVnLbbFY#@QKY`Fs zE>ini1+E%-UZxj>##cdjUl{iGP`%I^c!^ungqBPn7;M>wY^6^Pdxr>bRR8$dBTh0to0qzIypOL;Hs6UXtbfaw@?x7q$ufR3q`ED2a)aJDvHi?? zp0GC)^R+pmQ`2zGqw~}?^mLwWzuP#K>+RA6Iuj*Y40VcpeZYHpj5jh4Dwwt4jypy+ zEB^@kx#~oT-RyXy+DF9Z5c!htmjINL>daB6;Ea0|f*B1_O*WEt?Aii3F15qDC}JbKs@VZv4Q z^Or~&9Oc>R%w09Ni&m&Ez}>=y@-DNLC(XQauxPile%otJi3q$&DXXHAtae@+_CS8n zLAjr~urhRLJ>}u@JlUL_-d7`q-&qV!Iz*S^%AFaX+ruu(xy(B(MJpiw~#x^qA zG4oojuD!?DL`(c6SGC@-CA?nxRD_yZ5M1x48S1`ZcU}Hr0>RoGYO(!~l}WXr-JM*{ zFJGQRzkFf*|3uAyalsAYr9AMwHJ&lD*2yg>{*5@0Xq^CjhZphk3!*eXzQ5Z}Jhib& zLR1G9Y>mfu)q+QXDh;mNmeo(Uocqd!Ma}P8O_o(^O}JH$pRb1#-QFu!KN~-{V#Yg- z4m)bn*v1<8)<5CVgf*Bb^O27jN%L~Ufh|#CPAntLq)L22>1Dh|nD=wu zgV3jDib^6fEG!MuM$MpYu$>&m3`_oAN2a(ecV!ydAHeX7ieZ zihKMy3T7DhgN&BRuaN7OlkCfks#xEt_u!4HEU+yFg6vaGux+pf zfMW_>&%P}yI31L@pV6={m=_k0Fax1Vv$0N3h>n@F`WM?bf-Z=1uw;0NhQ6SVnX~*# zo-xH1k|9Q-WyqiJ|0crKyDqJ!RUcV3$lA`CoQ)%wIfu`WRDO3=Z=?WJFLDRaskj@Y^h7HEVU=8elM{MqHgsW{(p}aPWS9oqrnAy4vm}88|`uBA+%nlm%%}}K|ftttZ^{!(3 z9#Ar@tm2z55RU>LPw+Yy`9p9SgG8vAVTP=@EVxWjLp1Cm#y-(EAS`lJ(x7q>qB5Ubgl+X5uAOdR{j3+GYB%z|7H{4ikz!oub~;8$2q$2?B+~ zIw-y=ntBTVxhXNxL$k*>xYR$&R(w+hDu=a&FtMd{7hv&b-s>pZCimr@yzL02AXv#L zRZ#u9K0Z9wjfC-dv~W;NhLi=BMWeXZJ3j{0SFn;$>vvHIhem$Ra1rSg^81MdFDyK8 zDTa6p=sdC_Q)oJ&67)kD0jih&xIOgTM=q7>hB5<1yjiR&6xaOWP=6oVUkkP@JKgJsey!O2{cc^ z*D`gf64xb&p%%rdvhUEArhso@Uoe2YfI23?6Rv^U_bpJC$Q0?T~Xilg1U&7C84uF?+wE(uo6&08e6iUn$2rSv37X9BPIL-BQ{vdBjAKR1d|X zmep6kP6cMgIpgpzGAsIZ1BOJb$2aD_0!qvogvUr?ul=ME3t{jumN#Llr+vILrr}1^ zi~5}+^`TF~iP=i)h!^wn>xf!jh81#FOapbOT=CO*M@T~%yC4x&B^^jIeg;(hDP(Ct zd#X$l=t4^~#_1)d;TOBijr#WO6qC8|0kYmT>~n}_VGZ89*yjx5p^o0W+!0M&508#H z>^Y{|`jF7(LveB1Ha^y!J(M+6PEIzr!lL zcQNa8zsuZEMP!Nnz7Wpu=Vw6s^6kTUUR!PgW_~^NM(q4HEJND9VStvbp?$zmja&}3 zCH`hZbmF48wa;)V)<}owvha}B^0HXrF*f?DsN&Vjutlz40tbh;Ziwt5eb*;qBN%%Z zaE9UW2LxvEe4%qNNyxyH=8Hc)9dliinFm4bT2L-ka~IIe9Bn>AM^e zJjB^0hoBy}`4m2TNOUtM1#)kTWV#PwYF@jpyf>aXZVO}|+ekGAm++LcRRtEqJ7>H% zWKx|3ACZk=Jx~35WxjLBwhZWsXq&D0h?~fv^C4ORb#}%wwM`VACO!#0Ft+H+O;uHX z;xfZ{QDFJdFVx!e^gYcsGrycGzN1wc#A>-V`Zn}1?%Qstw^5=(G}}}T3b5=fnvZW` zYF)rJfurwNLItr|UPlmppnA>gO{{5RZ1^itrj(bkGx0g^r|TktEjiOP|O6*&QHYs`syBph`{gyxDtXl_2mqRutBi5Erd5g zIwI*_K}_e)r7`NBjgvju@`d7hJOc`dJFGURo1>)?q@DZWnqmJ0GSB-k*O?2ob$B*Lslb+6it1g9OF|h2& zdrDQ9)rM&fowyxnEtTqujwt#o)PEtakkBw`br!NS^6bYGLOp%2`iL^7Z+Ngy@{rA~ zC!Me&F28C|7U#m$`w7Z^K?YQ*=U!`u%kj_-i}g zL?eN++qwWPEYnKC?+?`smfX}*DVAc-&6c7ctolkM(7psGQppi2h}pwebBWkJ{D#>6 zYvh?{`9~;g$P9>x1kDUd29P-{bR=RsQwDa@_Sqr+%$lzeelxtcw>|spZcLkDB{fD$ zHuHc}3xv4Fr@1CSlB5S{h&gjQYa@N>G06uIU=*o~Ki2tom%n)ou<%G;;PNdS%K~`5 zLg28zFUmU3j6&yj#z$?}Xe$At;Gqlk&*dt0=9m%^R7KY|_449KD#t%%1 z3^lKB?F8sV6{B<~+4YGq{h5!fuqtzz+k{eTmM1-zrj`*})kK_qRVF7+h$yZwo97 zzZ-<@sE#wWz`<%BNL&*Zw`ZEOQKu=4D?l~xC_yJ-)mGoi-WiBS_p2%*3uai@S}Ufw zi=XZ42aX27V~YNC51-VK+MZ{+@3)e8$`$#6<4^}gS#~k0)*>ERs}67GF^0?(e@#Io ztZHqz&0n_nKL5_?jv4YxJsx337(?<)sJ;*}ya*bm^6Wxt=#S^PAA0V7bj4TgDCn2; z^9jLC^ze7!LNJ$+oubrLgc~`mL>7JnI%6sM@wlfcn(;ANrOKI*%`x51>nIwUc$?vJ zM1DrAz~~ei*dbHVmPI7N_Y&OG`nTM9hA5`vi|XHi1qCGPe+W>n{;GP)*A&SbAME!| zVI<>#JivryL6C02&HMWM$7T~)^RerNwM-&Bw_nx7maKgiC^t_87{a7XFD%g%O^vyJ^UW~j<859ki57{eXI z78z|9sP3nFelwJ?YEzIu!;!s!q)vug0DMR>)m|4qQ-K8gGkT4^8`}}>3B`C}WTh%8 zjy2qXhqi@Nj>0y+44QKJEJ=P2WJ^xLl*(p#_U(_!NSjrAr6#N1StxLsO*3)l?TU7F zkrR(}VF64fja$DK^~(Tp9Lh{~#vivPt0H+?PDWq)!qV#O#_=4gq1t8Cr@IXT44srC ze3kG8V1sJ-zYDRmOcz)O%~Q2wCp!U4XIIsYK1Uh%IeUB|0TH8sU6GAER^^DDfIm{5 zXDau7VZfL~_qzHb3&S5&_LC>CGKMJ#@dU^>&|hFw{k>Roz>CM6UrxwD2g?--oRWR^ zN1=)FY6(V|v!t)w=1=UHF-R31I0yN0xFAIJ{E_^8tdeXX1N|6V$fktAIezTrBqD08 zUkD*Ikx`ev^!b+GMDnC#ueQNV#=yfbq==F1hoDxGpt>kr~ttcM2cD2{z-ItUatvn6Pe%Bobm(U=S+Z5L^| zhEBx`dulmRSb=4LNN|lBtHXw~j7fsMQ@49T0M)&_gS8*XOJ0@4?uNN6y6Du}L^ZD( zsxP#?MRla)nYj_7nLXx}gId#QN0R#O#E;3tMRkiBB#J0XOmYRdW}}59FeU&O-tQ7n z+$d+X^g6Fl@s6X=vf8RJaE54Z(g(J^?2Hjt+Cz$)K`ke+f|JFUUTiKMhCQE4Hb z@+#|Fnm+I$s{Ar9!n}Tii2TzoMmf`@w4we1Ra~QYvH3y$SDg=K_UeA2WmBTLg(Hli z1pa(6O#VvOd5+hSOyB#+OadIDvAK9ex<3O9#kTH)T(?%FRH#X6f_TS@Um`e%&d>}I z)ppy6RJUHx84k5`q7GR5CK{<&-ejEfE!jIQUF>C9^Lelxdm=B>DI2mS+wnzT0mk+6 ziwy)L2V;WhDGGm1B8Z@kJF;A8sMc&xTtb!IJRwjZ! zssy`C%^*3uqND|^k+^UNMj|5~kxtYd?1~|lyj&cACC_a5cjiVPIQSy-0v<#MH1Wpd zvd0kC+&l2}>!zj8SN0K5;F<(oD;Npe9QUt#=*XpJ3rAH-XfsWHc%15AQqs7OG2yM7 z%47W?VV)>fu<~GrU%OceDVFi4z@Ld=$1;tRuNWxk&;lMski*k&LHzN-BDkOD>S}_o zi}|TukPH|!*RYrx36S*tn#O)*7}@WES@}MyJ+oK!Gj_=0ZRw z$S&fXH5Wc0>r~N1_tmEV_>oeV|wT*k&za#lDckJq!Fx{%VvVH(&;j6c`hMlJm?B`&!qHS$- z2V=7~Lhiu>-OAU1%#D`5Vbkg)+9Wx;4axti)M*ycbLZadmNQlGhHL@1yo&}<` z{mzS6%_~f8mrB>Fp=EiE6>k{IN_QCi9<`TP@Y+`EP0Y(W+}4UW{V0E+Snt>BuGS5X zXvl8(uJK~az@t|-g-`7>yRKz8o;5`LO1zZvlp8adu7nJf#8b|>QU>10QkvqjV*ECj zThheG;AY*k#GA2}sra1fRsMiD9^dq#R9#%!n@K*h)k;>JAtU4b1@qf5iq};}v4@5gzGd)Y;hBXIZmkEh)@O$=0eP>R`L*uAe9Ote zGv>x$S%Ms5DI%;mQf-2-6}4awLRQ}3yBCxDzFF2`>yPa?1X75tIwWEnkIi_6PmtY4 zHDePE3 zD5j|}ZknJwOwr#w+xbo#l|7(8FGukuG@yy~Gu_u{Y{db#zOF_CCNUjh?X9?At$;xr zy&ut&v^ewon7c!F^X;XleY5rtU6Sw9J^O+jW62Sv8q)C@pM17EYj18o{;FzAHJ$x> zK@@Pc2Z&)Pdx@Wi4aGDml8uE@&nCr{4JKXMreg=U_|Q`<<pIfbu>`C zEYi)9#-E_=>r4HhwE}(qwGw(=IRW$=5-r=3VY0?XpZW* zwu&HK!rtTLz13x3C}Yae(D>*HJTlXq+(QY=>|MFFHx37+gyi78p_nTcD{AU`a~pQJ6yh9SiIy+VZPY6fCQL!tf2M1CQ(H+W2%=?e3fyr(H5@uJk zhLma2wOj7Uy4$1u0wm9_9`n1p&Oo4F&(OgaW_)*KeUIk}`OS5zba-0Wk*Kd)5dl@L z^>C~xEzf1OgMyZw&hR*1(8xoCYvJ!$@4bsR!V{pZzCq}SjYSrBY|4Y;gyavG4iAd@ z>Moyxy5hz2$oT+;4AG&PA_4@y{PB*-`mKLx4laMhPEmj%zragLN83$2`!R|}YkcKj%&O3;5NTIRKKQy&izD3peKBhI&6`t|SuT=A( zB7jF-MQm;D7}`3wES2AA6@5p8K19$j>SOwCc+f=?++$aeE-b_NPKn%fLv((9k_sL9 zcO#Zm?>4N~CdF0#!Nx7%5Uh1%-9gvW_^}lB6Cww!4(u8wrY|0FL3uqQ$uxFEQyJA- zN0J!-AVTa(*!vIM7Q4E5P^&^ZXzG}``5%XqEqrij!InrngrBFi+0jUhY zOrQ_8fH0)TP6%h%(<~xy&X${2!-^VSv=UZ`(fU{UIb^B^`WL8Gh*u3*o}ph!B<>9v zw4cObuR0Mo?!`Yngnj3Z&YB@&_O?rjr=F;q!(KFhmjc=0%FzErT>g(Z{2{n9RFW~PO%R{c>- zdelD2v%{3XR+3Ongj~Asq9Ah%tz$~x&WR3FZmXZ*F%rcY%QlSFl~@NrE0)t}0(GUk zooJR`))nS)((dTPqUvKp=C{(>MMb?QW)`XsJ!7qg)zJD0W*Mwx_YoADkXf^ER@W^dhL=o_nYK%| z6{mptiSCQcAna|N!*>`7mnA0RRO@N%1&w@lom2O2`&TZq3b?B{Q$|6*#VDst?xg}k zSDk5RB*{-q|7Ay5(7EJ1q7?)RJgcz5YNK-J%`^8v$N5g)`IpwO>6eFgdywx}49ffg`X8m%V)DS~b z^Il>p%0w!(&279;Lb6fomE4*vu{dR-Kwu!%v6Dr6cy)gN$n&w-{a&z}ubTBO8f;zY zaMA}G;Up9F^5`DiJDFXo$JuA}Kxv4#B?ySZo;FXZNSP`9OR82p5Z%q{4WC@FrMw7m zaj!W!ym>)R7PU$q^(kk|YWYu1di%x@aRoA#V>K~diPDgTu)=t+{ppaL=iWcl(Fw!m9&~$2jGA9a zV9MCA7mvC-vMeP!%Vl+1NROs%JrJ?ZK?vU6wSFaaDP#!=ah#OtI6!pWfM+QUwQb~- z7v3cNBF)ph`_%R!nffMkPO@JZ+j6Wf(o<(K4c>lVQ2Z(9Ft*kg^5pxjp+Hzi`?QtEes8@5e0p-amPmBo|M-P@hGa+lTAi_Y`c2->=zD}il3-00 zVbrnyr0y!~qt_GRi-lamU370OGPp)f_F37&sjFCB|8gG5D|$I)M!Y83(;W5DTFR>y z)~946_bHuHb5STe!7ptT@7gvp{ac6q-`W-3Euvmd6eLz1Yb>U^T+7an zYGP&un$!(*!r&KLz>Z6$i4F?W_aO_$zLKnlUW;X22+x$aSM#wrevg9S$?Cj()^3R^ zuBYUGErV1_Ci5ek6espUY5ipECFwUOf|(F!dLsiGHn(~ZD;3Sdn=|ZctZVVf$==Gb zORbHelpyqrLf~wTj}JsDnSxSM4Ce%{i2>789wwz+CV7P#(D9K?5sm{h({0Hih?Vrv zv}@;f4v=OMoo1n-LYF!1kUHd~uZ0N{$^#gN9yM1EtroAXTau+r*;)w`W15W0-Nzdt zl}$NLYQIktUp(6*sO0Dv_dka+QeA{iW`k^amt7;3R3I{;s_KDu%p7Xglt}9SIP3d? z*QLW;$?c}F`^ccPIRo-LB^`c&Yosc*j&H&f@j&oa-M*ev!gZIO?*giX-svF_U@Gn0 zc0#MXo*)|4PEYtgOW8`Wd{`*})`9B!1#I$A-^fYf>`V&+--`v~>ZCFmqd>UFA^!^&z$J?Of4s%cGPQn|v*)uV!_Sz?(xM z>>PYWs^cy`nDAB>w)VKcRn8!eeik{7rmOO0SMDYkZpbE3au}}XUK|UbALoUZ=vi4w zi&`AfTgVDpjeN59%KIq1>%EHP<4Pr07JslLya?#N1g2p0dNrrPuAl9ve9YGn$sK*I zU))v0>mY9_i?RU(eY5zJTp#QBzUq=BD9PDw`I4-KS!4ku1(=;+yNpfqX|W)reBF;N zTju%VoAA|sG~g;2F{eqwCBaV{89SERLb76MGdQv@eJd5)z&{A&hd+jO!3RGAU9n$& z( z1wYd9Tsdy|_I4}3tF?c!y~dpOEItAXRFzMhSKWKQ;}5YT4jf!n9>>8s1j7X=T9uq$ znez-kd%UUsMIe1Me-O-jLksZQSs$-)o9&|aWwef7@(Gj{M?0PIl zDaXnU{iH+F3$PC6mMNQIS{4_fEk*1sA+>iqE86IZHJJAsMm#$3^I*S`F@Hr6CGiP- zOg&+>fHDlhiRoEJj}GwQo!9kU9{SGiH;dt690fhxkT`pE6}@aOPFN$CA2IR|R8f7) zv#+^@NP|^0BDB}t=Qgt>Q@7! zx__WY2tC8XySfSvtj^}cqW6GPCcGW&{vXD^Axsk>$g*tPwrv}K*|u%lHo9!vt}fd) zyKGmNre}8cFngK(zC1);^AM5oBHnvafwI3vWSR^i;X-ZD`+#SCF%5v{M5|3;WCP;N zV2nltikBmYLdgKKGrb7(N8aF5?h;m-v9lI7P}OXqA`!DuC5a?GH?eKeJey{j4^5PaKJHfQXy~4zRTVk>yax&1`D_QYzM*)!` z?b=#fAQa_L98%ouzP6ScQ1g<;MnTdP2N8--_=f{f#CTwacwk>L!cxarcY3eFvNd;8 z!DcE1wMx~1*z}Jm$f9eJmUk?>RueC2ocpwrs)y#5L)A0aD)r-DR;k&emE|QEc5`BqZpu!oCFmd}Jc%qA04Ui{eMguIxmXyb6YTJ

    sl*CtvD7j$_;?KD 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

    {0o23%x0zG^lL-+)0hE&z$21eha>paa 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 LL+IIXJH`J1EdB>a literal 0 HcmV?d00001 diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 9a1560f03..522b7ada2 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -12,9 +12,13 @@ package ch.eitchnet.privilege.base; import java.io.File; +import org.dom4j.Element; + +import ch.eitchnet.privilege.handler.EncryptionHandler; import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; -import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.helper.ClassHelper; +import ch.eitchnet.privilege.helper.XmlHelper; /** * @@ -23,13 +27,17 @@ import ch.eitchnet.privilege.handler.EncryptionHandler; */ public class PrivilegeContainer { + public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; + public static final String XML_HANDLER_SESSION = "SessionHandler"; + public static final String XML_HANDLER_POLICY = "PolicyHandler"; + private static final PrivilegeContainer instance; static { instance = new PrivilegeContainer(); } - private SessionHandler privilegeHandler; + private SessionHandler sessionHandler; private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; @@ -45,10 +53,10 @@ public class PrivilegeContainer { } /** - * @return the privilegeHandler + * @return the sessionHandler */ - public SessionHandler getPrivilegeHandler() { - return privilegeHandler; + public SessionHandler getSessionHandler() { + return sessionHandler; } /** @@ -66,7 +74,30 @@ public class PrivilegeContainer { } public void initialize(File privilegeContainerXml) { - // TODO implement - } + // parse container xml file to XML document + Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); + + // instantiate session handler + Element sessionHandlerElement = containerRootElement.element(XML_HANDLER_SESSION); + String sessionHandlerClassName = sessionHandlerElement.attributeValue("class"); + SessionHandler sessionHandler = ClassHelper.instantiateClass(sessionHandlerClassName); + sessionHandler.initialize(sessionHandlerElement); + + // instantiate encryption handler + Element encryptionHandlerElement = containerRootElement.element(XML_HANDLER_ENCRYPTION); + String encryptionHandlerClassName = encryptionHandlerElement.attributeValue("class"); + EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); + encryptionHandler.initialize(encryptionHandlerElement); + + // instantiate policy handler + Element policyHandlerElement = containerRootElement.element(XML_HANDLER_POLICY); + String policyHandlerClassName = policyHandlerElement.attributeValue("class"); + PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); + policyHandler.initialize(policyHandlerElement); + + this.sessionHandler = sessionHandler; + this.encryptionHandler = encryptionHandler; + this.policyHandler = policyHandler; + } } diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java b/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java new file mode 100644 index 000000000..88a8b2c2f --- /dev/null +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +import org.dom4j.Element; + +/** + * @author rvonburg + * + */ +public interface PrivilegeContainerObject { + + public void initialize(Element element); +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 8bdde3da3..ad855e4fd 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -15,9 +15,11 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Map; import org.dom4j.Element; +import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** @@ -26,7 +28,9 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; */ public class DefaultEncryptionHandler implements EncryptionHandler { - public static final String HASH_ALGORITHM = "SHA-1"; + public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; + + public String hashAlgorithm; /** * Hex char table for fast calculating of hex value @@ -42,7 +46,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String convertToHash(String string) { try { - MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); + MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); byte[] hashArray = digest.digest(string.getBytes()); byte[] hex = new byte[2 * hashArray.length]; @@ -57,7 +61,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { return new String(hex, "ASCII"); } catch (NoSuchAlgorithmException e) { - throw new PrivilegeException("Algorithm " + HASH_ALGORITHM + " was not found!", e); + throw new PrivilegeException("Algorithm " + hashAlgorithm + " was not found!", e); } catch (UnsupportedEncodingException e) { throw new PrivilegeException("Charset ASCII is not supported!", e); } @@ -73,7 +77,16 @@ public class DefaultEncryptionHandler implements EncryptionHandler { return randomString; } + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ public void initialize(Element element) { - // TODO implement + + Element parameterElement = element.element("Parameters"); + Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + + // get and test configured algorithm + hashAlgorithm = parameterMap.get(XML_PARAM_HASH_ALGORITHM); + convertToHash("test"); } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index e9e5176c2..96418bf6e 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -10,9 +10,11 @@ package ch.eitchnet.privilege.handler; -import java.io.File; import java.util.Map; +import org.dom4j.Element; + +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RestrictionPolicy; import ch.eitchnet.privilege.model.User; @@ -31,14 +33,36 @@ public class DefaultPolicyHandler implements PolicyHandler { */ @Override public boolean actionAllowed(User user, Restrictable restrictable) { - - // TODO auth user - - // TODO Auto-generated method stub - return false; + + // user and restrictable must not be null + if (user == null) + throw new PrivilegeException("User may not be null!"); + else if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + // validate restriction key for this restrictable + String restrictionKey = restrictable.getRestrictionKey(); + if (restrictionKey == null || restrictionKey.length() < 3) { + throw new PrivilegeException( + "The RestrictionKey may not be shorter than 3 characters. Invalid Restrictable " + + restrictable.getClass().getName()); + } + + // get restriction policy + RestrictionPolicy policy = policyMap.get(restrictionKey); + if (policy == null) { + throw new PrivilegeException("No RestrictionPolicy exists for the RestrictionKey " + restrictionKey + + " for Restrictable " + restrictable.getClass().getName()); + } + + // delegate checking to restriction policy + return policy.actionAllowed(user, restrictable); } - public void initialize(File policyXml) { + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ + public void initialize(Element element) { // TODO implement } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 9e3cf646b..56e33ebba 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -10,10 +10,10 @@ package ch.eitchnet.privilege.handler; -import java.io.File; import java.util.Map; import org.apache.log4j.Logger; +import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.i18n.AccessDeniedException; @@ -48,6 +48,7 @@ public class DefaultSessionHandler implements SessionHandler { @Override public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + // certificate and restrictable must not be null if (certificate == null) throw new PrivilegeException("Certificate may not be null!"); else if (restrictable == null) @@ -74,7 +75,7 @@ public class DefaultSessionHandler implements SessionHandler { User user = userMap.get(certificateSessionPair.session.getUsername()); if (user == null) { throw new PrivilegeException( - "Oh now, how did this happen: No User in user map although certificate is valid!"); + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); } // now validate on policy handler @@ -141,7 +142,10 @@ public class DefaultSessionHandler implements SessionHandler { return Long.toString(++lastSessionId % Long.MAX_VALUE); } - public void initialize(File userFile) { + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ + public void initialize(Element element) { // TODO implement } diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index bda572e0c..c26be38a4 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -10,11 +10,13 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.base.PrivilegeContainerObject; + /** * @author rvonburg * */ -public interface EncryptionHandler { +public interface EncryptionHandler extends PrivilegeContainerObject{ public String nextToken(); diff --git a/src/ch/eitchnet/privilege/handler/PolicyHandler.java b/src/ch/eitchnet/privilege/handler/PolicyHandler.java index 992a45f16..93a4b45d6 100644 --- a/src/ch/eitchnet/privilege/handler/PolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/PolicyHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.User; @@ -17,7 +18,7 @@ import ch.eitchnet.privilege.model.User; * @author rvonburg * */ -public interface PolicyHandler { +public interface PolicyHandler extends PrivilegeContainerObject { public boolean actionAllowed(User user, Restrictable restrictable); } diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java index 39b35cc45..9b9d26468 100644 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; @@ -19,7 +20,7 @@ import ch.eitchnet.privilege.model.User; * @author rvonburg * */ -public interface SessionHandler { +public interface SessionHandler extends PrivilegeContainerObject { /** * @param certificate diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java new file mode 100644 index 000000000..521c4df4f --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import ch.eitchnet.privilege.i18n.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class ClassHelper { + + @SuppressWarnings("unchecked") + public static T instantiateClass(String className) { + try { + + Class clazz = (Class) Class.forName(className); + + return clazz.getConstructor().newInstance(); + + } catch (Exception e) { + throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); + } + } +} diff --git a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java new file mode 100644 index 000000000..177c5a3d7 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.dom4j.Element; + +/** + * @author rvonburg + * + */ +public class ConfigurationHelper { + + @SuppressWarnings("unchecked") + public static Map convertToParameterMap(Element element) { + + Map parameterMap = new HashMap(); + + List elements = element.elements("Parameter"); + for (Element parameter : elements) { + String name = parameter.attributeValue("name"); + String value = parameter.attributeValue("value"); + parameterMap.put(name, value); + } + + return parameterMap; + } +} diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java new file mode 100644 index 000000000..3a8f2d4b4 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import org.apache.log4j.Logger; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.io.SAXReader; + +import ch.eitchnet.privilege.i18n.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class XmlHelper { + + private static final Logger logger = Logger.getLogger(XmlHelper.class); + + public static Document parseDocument(File xmlFile) { + try { + + InputStream inStream = new FileInputStream(xmlFile); + + SAXReader reader = new SAXReader(); + Document document = reader.read(inStream); + + logger.info("Read Xml document " + document.getName()); + return document; + + } catch (FileNotFoundException e) { + throw new PrivilegeException("The Xml file does not exist or is not readable: " + xmlFile.getAbsolutePath()); + } catch (DocumentException e) { + throw new PrivilegeException("the Xml file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } + } +} From 7de4b5f4f12c6f04e21ddc94120b483586e7f4bf Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 29 May 2010 18:47:04 +0000 Subject: [PATCH 010/457] --- .../privilege/base/PrivilegeContainer.java | 16 ++--- .../eitchnet/privilege/base/XmlConstants.java | 27 ++++++++ .../handler/DefaultEncryptionHandler.java | 7 +- .../handler/DefaultPolicyHandler.java | 14 ++-- .../handler/DefaultSessionHandler.java | 39 +++++++++-- .../privilege/handler/PersistenceHandler.java | 27 ++++++++ .../privilege/handler/PolicyHandler.java | 4 +- .../privilege/handler/SessionHandler.java | 2 +- .../{ => internal}/DefaultRestriction.java | 66 ++++--------------- .../privilege/model/internal/Privilege.java | 57 ++++++++++++++++ .../{ => internal}/RestrictionPolicy.java | 6 +- .../privilege/model/internal/Role.java | 39 +++++++++++ .../model/{ => internal}/Session.java | 2 +- .../privilege/model/{ => internal}/User.java | 4 +- 14 files changed, 221 insertions(+), 89 deletions(-) create mode 100644 src/ch/eitchnet/privilege/base/XmlConstants.java create mode 100644 src/ch/eitchnet/privilege/handler/PersistenceHandler.java rename src/ch/eitchnet/privilege/model/{ => internal}/DefaultRestriction.java (53%) create mode 100644 src/ch/eitchnet/privilege/model/internal/Privilege.java rename src/ch/eitchnet/privilege/model/{ => internal}/RestrictionPolicy.java (53%) create mode 100644 src/ch/eitchnet/privilege/model/internal/Role.java rename src/ch/eitchnet/privilege/model/{ => internal}/Session.java (98%) rename src/ch/eitchnet/privilege/model/{ => internal}/User.java (95%) diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 522b7ada2..f715748bd 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -27,10 +27,6 @@ import ch.eitchnet.privilege.helper.XmlHelper; */ public class PrivilegeContainer { - public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; - public static final String XML_HANDLER_SESSION = "SessionHandler"; - public static final String XML_HANDLER_POLICY = "PolicyHandler"; - private static final PrivilegeContainer instance; static { @@ -79,20 +75,20 @@ public class PrivilegeContainer { Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); // instantiate session handler - Element sessionHandlerElement = containerRootElement.element(XML_HANDLER_SESSION); - String sessionHandlerClassName = sessionHandlerElement.attributeValue("class"); + Element sessionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_SESSION); + String sessionHandlerClassName = sessionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); SessionHandler sessionHandler = ClassHelper.instantiateClass(sessionHandlerClassName); sessionHandler.initialize(sessionHandlerElement); // instantiate encryption handler - Element encryptionHandlerElement = containerRootElement.element(XML_HANDLER_ENCRYPTION); - String encryptionHandlerClassName = encryptionHandlerElement.attributeValue("class"); + Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); + String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); encryptionHandler.initialize(encryptionHandlerElement); // instantiate policy handler - Element policyHandlerElement = containerRootElement.element(XML_HANDLER_POLICY); - String policyHandlerClassName = policyHandlerElement.attributeValue("class"); + Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); + String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); policyHandler.initialize(policyHandlerElement); diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java new file mode 100644 index 000000000..5d8a32519 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +/** + * @author rvonburg + * + */ +public class XmlConstants { + public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; + public static final String XML_HANDLER_SESSION = "SessionHandler"; + public static final String XML_HANDLER_POLICY = "PolicyHandler"; + + public static final String XML_PARAMETERS = "Parameters"; + + public static final String XML_ATTR_CLASS = "class"; + + public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index ad855e4fd..a927e2ac7 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -19,6 +19,7 @@ import java.util.Map; import org.dom4j.Element; +import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -28,8 +29,6 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; */ public class DefaultEncryptionHandler implements EncryptionHandler { - public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; - public String hashAlgorithm; /** @@ -82,11 +81,11 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ public void initialize(Element element) { - Element parameterElement = element.element("Parameters"); + Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); // get and test configured algorithm - hashAlgorithm = parameterMap.get(XML_PARAM_HASH_ALGORITHM); + hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); convertToHash("test"); } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index 96418bf6e..8d3780411 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -16,8 +16,8 @@ import org.dom4j.Element; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.RestrictionPolicy; -import ch.eitchnet.privilege.model.User; +import ch.eitchnet.privilege.model.internal.RestrictionPolicy; +import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg @@ -28,15 +28,15 @@ public class DefaultPolicyHandler implements PolicyHandler { private Map policyMap; /** - * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.User, + * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(User user, Restrictable restrictable) { + public boolean actionAllowed(Role role, Restrictable restrictable) { // user and restrictable must not be null - if (user == null) - throw new PrivilegeException("User may not be null!"); + if (role == null) + throw new PrivilegeException("Role may not be null!"); else if (restrictable == null) throw new PrivilegeException("Restrictable may not be null!"); @@ -56,7 +56,7 @@ public class DefaultPolicyHandler implements PolicyHandler { } // delegate checking to restriction policy - return policy.actionAllowed(user, restrictable); + return policy.actionAllowed(role, restrictable); } /** diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 56e33ebba..8e07079d3 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -20,9 +20,10 @@ import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.Session; -import ch.eitchnet.privilege.model.User; import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.Session; +import ch.eitchnet.privilege.model.internal.User; /** * @author rvonburg @@ -35,6 +36,7 @@ public class DefaultSessionHandler implements SessionHandler { private static long lastSessionId; private Map userMap; + private Map roleMap; private Map sessionMap; /** @@ -64,7 +66,7 @@ public class DefaultSessionHandler implements SessionHandler { if (!sessionCertificate.equals(certificate)) throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - // TODO is this overkill? + // TODO is validating authToken overkill since the two certificates have already been checked on equality? // validate authToken from certificate using the sessions authPassword String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) @@ -78,8 +80,28 @@ public class DefaultSessionHandler implements SessionHandler { "Oh boy, how did this happen: No User in user map although the certificate is valid!"); } - // now validate on policy handler - return PrivilegeContainer.getInstance().getPolicyHandler().actionAllowed(user, restrictable); + // default is to not allow the action + // TODO should default deny/allow policy be configurable? + boolean actionAllowed = false; + + // now iterate roles and validate on policy handler + PolicyHandler policyHandler = PrivilegeContainer.getInstance().getPolicyHandler(); + for (String roleName : user.getRoleList()) { + + Role role = roleMap.get(roleName); + if (role == null) { + logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + continue; + } + + actionAllowed = policyHandler.actionAllowed(role, restrictable); + + // if action is allowed, then break iteration as a privilege match has been made + if (actionAllowed) + break; + } + + return actionAllowed; } /** @@ -116,6 +138,11 @@ public class DefaultSessionHandler implements SessionHandler { if (user.getState() != UserState.ENABLED) throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + // validate user has at least one role + if (user.getRoleList().isEmpty()) { + throw new PrivilegeException("User " + username + " does not have any roles defined!"); + } + // get 2 auth tokens String authToken = encryptionHandler.nextToken(); String authPassword = encryptionHandler.nextToken(); @@ -138,7 +165,7 @@ public class DefaultSessionHandler implements SessionHandler { return certificate; } - private String nextSessionId() { + private synchronized String nextSessionId() { return Long.toString(++lastSessionId % Long.MAX_VALUE); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java new file mode 100644 index 000000000..bd12a2b5d --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.util.List; + +import ch.eitchnet.privilege.model.internal.RestrictionPolicy; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public interface PersistenceHandler { + + public List getAllUsers(); + public void saveUsers(List users); + public List getAllRestrictionPolicies(); +} diff --git a/src/ch/eitchnet/privilege/handler/PolicyHandler.java b/src/ch/eitchnet/privilege/handler/PolicyHandler.java index 93a4b45d6..5c55ad7a6 100644 --- a/src/ch/eitchnet/privilege/handler/PolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/PolicyHandler.java @@ -12,7 +12,7 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.User; +import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg @@ -20,5 +20,5 @@ import ch.eitchnet.privilege.model.User; */ public interface PolicyHandler extends PrivilegeContainerObject { - public boolean actionAllowed(User user, Restrictable restrictable); + public boolean actionAllowed(Role role, Restrictable restrictable); } diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java index 9b9d26468..3c46a11f9 100644 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -14,7 +14,7 @@ import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.User; +import ch.eitchnet.privilege.model.internal.User; /** * @author rvonburg diff --git a/src/ch/eitchnet/privilege/model/DefaultRestriction.java b/src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java similarity index 53% rename from src/ch/eitchnet/privilege/model/DefaultRestriction.java rename to src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java index a24a622f5..0516b4281 100644 --- a/src/ch/eitchnet/privilege/model/DefaultRestriction.java +++ b/src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java @@ -8,14 +8,12 @@ * */ -package ch.eitchnet.privilege.model; - -import java.util.List; -import java.util.Map; +package ch.eitchnet.privilege.model.internal; import org.dom4j.Element; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Restrictable; /** * @author rvonburg @@ -25,18 +23,16 @@ public class DefaultRestriction implements RestrictionPolicy { private String restrictionKey; - private Map roleRestrictionMap; - /** - * @see ch.eitchnet.privilege.model.RestrictionPolicy#actionAllowed(ch.eitchnet.privilege.model.User, + * @see ch.eitchnet.privilege.model.internal.RestrictionPolicy#actionAllowed(ch.eitchnet.privilege.model.User, * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(User user, Restrictable restrictable) { + public boolean actionAllowed(Role role, Restrictable restrictable) { // validate user is not null - if (user == null) - throw new PrivilegeException("User may not be null!"); + if (role == null) + throw new PrivilegeException("Role may not be null!"); // validate Restrictable is set for this RestrictionPolicy if (!restrictionKey.equals(restrictable.getRestrictionKey())) { @@ -46,38 +42,15 @@ public class DefaultRestriction implements RestrictionPolicy { + restrictable.getRestrictionKey()); } - // default is that user does not have privilege - boolean hasPrivilege = false; - - // iterate user roles and validate role has privilege - for (String role : user.getRoleList()) { - - hasPrivilege = internalActionAllowed(role, restrictable); - - // if privilege is found, then stop iterating - if (hasPrivilege) - break; - } - - return hasPrivilege; - } - - /** - * @param role - * @param restrictable - * @return - */ - private boolean internalActionAllowed(String role, Restrictable restrictable) { - // get restriction object for users role - Restriction restriction = roleRestrictionMap.get(role); + Privilege privilege = role.getPrivilege(restrictionKey); // no restriction object means no privilege - if (restriction == null) + if (privilege == null) return false; // does this role have privilege for any values? - if (restriction.anyValue) + if (privilege.isAllAllowed()) return true; // get the value on which the action is to be performed @@ -92,13 +65,13 @@ public class DefaultRestriction implements RestrictionPolicy { String restrictionValue = (String) object; // first check values not allowed - for (String notAllowed : restriction.valuesNotAllowed) { + for (String notAllowed : privilege.getValuesNotAllowed()) { if (notAllowed.equals(restrictionValue)) return false; } // now check values allowed - for (String allowed : restriction.valuesAllowed) { + for (String allowed : privilege.getValuesAllowed()) { if (allowed.equals(restrictionValue)) return true; } @@ -111,21 +84,4 @@ public class DefaultRestriction implements RestrictionPolicy { // TODO implement } - - private class Restriction { - private boolean anyValue; - private List valuesAllowed; - private List valuesNotAllowed; - - /** - * @param allAllowed - * @param valuesAllowed - * @param valuesNotAllowed - */ - public Restriction(boolean anyValue, List valuesAllowed, List valuesNotAllowed) { - this.anyValue = anyValue; - this.valuesAllowed = valuesAllowed; - this.valuesNotAllowed = valuesNotAllowed; - } - } } diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java new file mode 100644 index 000000000..779fdd802 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model.internal; + +import java.util.Collections; +import java.util.List; + +/** + * @author rvonburg + * + */ +public class Privilege { + + private final boolean allAllowed; + private final List valuesAllowed; + private final List valuesNotAllowed; + + /** + * @param allAllowed + * @param valuesAllowed + * @param valuesNotAllowed + */ + public Privilege(boolean allAllowed, List valuesAllowed, List valuesNotAllowed) { + this.allAllowed = allAllowed; + this.valuesAllowed = Collections.unmodifiableList(valuesAllowed); + this.valuesNotAllowed = Collections.unmodifiableList(valuesNotAllowed); + } + + /** + * @return the allAllowed + */ + public boolean isAllAllowed() { + return allAllowed; + } + + /** + * @return the valuesAllowed + */ + public List getValuesAllowed() { + return valuesAllowed; + } + + /** + * @return the valuesNotAllowed + */ + public List getValuesNotAllowed() { + return valuesNotAllowed; + } +} diff --git a/src/ch/eitchnet/privilege/model/RestrictionPolicy.java b/src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java similarity index 53% rename from src/ch/eitchnet/privilege/model/RestrictionPolicy.java rename to src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java index 7bcccea64..35c18859a 100644 --- a/src/ch/eitchnet/privilege/model/RestrictionPolicy.java +++ b/src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java @@ -8,7 +8,9 @@ * */ -package ch.eitchnet.privilege.model; +package ch.eitchnet.privilege.model.internal; + +import ch.eitchnet.privilege.model.Restrictable; /** * @author rvonburg @@ -16,5 +18,5 @@ package ch.eitchnet.privilege.model; */ public interface RestrictionPolicy { - public boolean actionAllowed(User user, Restrictable restrictable); + public boolean actionAllowed(Role role, Restrictable restrictable); } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java new file mode 100644 index 000000000..be6c7e841 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model.internal; + +import java.util.Collections; +import java.util.Map; + +/** + * @author rvonburg + * + */ +public class Role { + + private final Map privilegeMap; + + /** + * @param privilegeMap + */ + public Role(Map privilegeMap) { + this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + } + + /** + * @param key + * @return + * @see java.util.Map#get(java.lang.Object) + */ + public Privilege getPrivilege(String key) { + return privilegeMap.get(key); + } +} diff --git a/src/ch/eitchnet/privilege/model/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java similarity index 98% rename from src/ch/eitchnet/privilege/model/Session.java rename to src/ch/eitchnet/privilege/model/internal/Session.java index a90249f3d..d648a6fb1 100644 --- a/src/ch/eitchnet/privilege/model/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -8,7 +8,7 @@ * */ -package ch.eitchnet.privilege.model; +package ch.eitchnet.privilege.model.internal; /** * @author rvonburg diff --git a/src/ch/eitchnet/privilege/model/User.java b/src/ch/eitchnet/privilege/model/internal/User.java similarity index 95% rename from src/ch/eitchnet/privilege/model/User.java rename to src/ch/eitchnet/privilege/model/internal/User.java index 9cf9d58c4..fad6be185 100644 --- a/src/ch/eitchnet/privilege/model/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -8,12 +8,14 @@ * */ -package ch.eitchnet.privilege.model; +package ch.eitchnet.privilege.model.internal; import java.util.Collections; import java.util.List; import java.util.Locale; +import ch.eitchnet.privilege.model.UserState; + /** * @author rvonburg * From d4f471028e9b608f19ebc53bc18020b5c855f9fa Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 29 May 2010 19:11:28 +0000 Subject: [PATCH 011/457] --- config/RestrictionPolicy.xml | 6 +++ .../privilege/base/PrivilegeContainer.java | 45 +++++++++++++++++-- .../eitchnet/privilege/base/XmlConstants.java | 3 +- .../handler/DefaultEncryptionHandler.java | 21 ++++++++- .../handler/DefaultPolicyHandler.java | 28 +++++++++++- 5 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 config/RestrictionPolicy.xml diff --git a/config/RestrictionPolicy.xml b/config/RestrictionPolicy.xml new file mode 100644 index 000000000..c1ec64418 --- /dev/null +++ b/config/RestrictionPolicy.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index f715748bd..953b3b7ae 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -12,6 +12,7 @@ package ch.eitchnet.privilege.base; import java.io.File; +import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; @@ -19,6 +20,7 @@ import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.i18n.PrivilegeException; /** * @@ -26,6 +28,7 @@ import ch.eitchnet.privilege.helper.XmlHelper; * @author rvonburg */ public class PrivilegeContainer { + private static final Logger logger = Logger.getLogger(PrivilegeContainer.class); private static final PrivilegeContainer instance; @@ -37,6 +40,8 @@ public class PrivilegeContainer { private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; + private String basePath; + public static PrivilegeContainer getInstance() { return instance; } @@ -69,8 +74,24 @@ public class PrivilegeContainer { return encryptionHandler; } + /** + * @return the basePath + */ + public String getBasePath() { + return basePath; + } + public void initialize(File privilegeContainerXml) { + // make sure file exists + if (!privilegeContainerXml.exists()) { + throw new PrivilegeException("Privilige file does not exist at path " + + privilegeContainerXml.getAbsolutePath()); + } + + // set base path from privilege container xml + basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); + // parse container xml file to XML document Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); @@ -78,20 +99,38 @@ public class PrivilegeContainer { Element sessionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_SESSION); String sessionHandlerClassName = sessionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); SessionHandler sessionHandler = ClassHelper.instantiateClass(sessionHandlerClassName); - sessionHandler.initialize(sessionHandlerElement); // instantiate encryption handler Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); - encryptionHandler.initialize(encryptionHandlerElement); // instantiate policy handler Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); - policyHandler.initialize(policyHandlerElement); + try { + sessionHandler.initialize(sessionHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("SessionHandler " + sessionHandlerClassName + " could not be initialized"); + } + try { + encryptionHandler.initialize(encryptionHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + + " could not be initialized"); + } + try { + policyHandler.initialize(policyHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); + } + + // keep references to the handlers this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; this.policyHandler = policyHandler; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 5d8a32519..692188cee 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -20,8 +20,9 @@ public class XmlConstants { public static final String XML_HANDLER_POLICY = "PolicyHandler"; public static final String XML_PARAMETERS = "Parameters"; - + public static final String XML_ATTR_CLASS = "class"; public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; + public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; } diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index a927e2ac7..2810ef699 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -17,6 +17,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; +import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.base.XmlConstants; @@ -28,6 +29,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * */ public class DefaultEncryptionHandler implements EncryptionHandler { + private static final Logger logger = Logger.getLogger(DefaultEncryptionHandler.class); public String hashAlgorithm; @@ -73,6 +75,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String nextToken() { SecureRandom secureRandom = new SecureRandom(); String randomString = new BigInteger(130, secureRandom).toString(32); + logger.info("Token: " + randomString); // XXX remove this line after testing!!! return randomString; } @@ -81,11 +84,25 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ public void initialize(Element element) { + // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); - // get and test configured algorithm + // get hash algorithm parameters hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); - convertToHash("test"); + if (hashAlgorithm == null || hashAlgorithm.isEmpty()) { + throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid"); + } + + // test hash algorithm + try { + convertToHash("test"); + logger.info("Using hashing algorithm " + hashAlgorithm); + } catch (Exception e) { + throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid because of underlying exception: " + + e.getLocalizedMessage(), e); + } } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index 8d3780411..339752bbc 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -10,10 +10,15 @@ package ch.eitchnet.privilege.handler; +import java.io.File; import java.util.Map; import org.dom4j.Element; +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.RestrictionPolicy; @@ -63,6 +68,27 @@ public class DefaultPolicyHandler implements PolicyHandler { * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) */ public void initialize(Element element) { - // TODO implement + + // get parameters + Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + + // get policy file name + String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); + if (policyFileName == null || policyFileName.isEmpty()) { + throw new PrivilegeException("[" + PolicyHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid"); + } + + // get policy file + File policyFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + policyFileName); + if (!policyFile.exists()) { + throw new PrivilegeException("[" + PolicyHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " + + policyFile.getAbsolutePath()); + } + + // parse policy xml file to XML document + Element containerRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); } } From 00ed10014db84e84ebf5dfd7c9d7b3992a090191 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 29 May 2010 22:32:53 +0000 Subject: [PATCH 012/457] --- config/PrivilegeRoles.xml | 12 +++++++ config/PrivilegeUsers.xml | 10 ++++++ config/RestrictionPolicies.xml | 6 ++++ config/RestrictionPolicy.xml | 6 ---- .../eitchnet/privilege/base/XmlConstants.java | 2 ++ .../handler/DefaultPolicyHandler.java | 29 +++++++++++++--- .../privilege/handler/PersistenceHandler.java | 2 +- .../privilege/helper/ClassHelper.java | 23 +++++++++++++ .../privilege/model/internal/Privilege.java | 27 ++++++++------- .../DefaultRestriction.java | 34 +++++++------------ .../RestrictionPolicy.java | 3 +- 11 files changed, 107 insertions(+), 47 deletions(-) create mode 100644 config/PrivilegeRoles.xml create mode 100644 config/PrivilegeUsers.xml create mode 100644 config/RestrictionPolicies.xml delete mode 100644 config/RestrictionPolicy.xml rename src/ch/eitchnet/privilege/{model/internal => policy}/DefaultRestriction.java (61%) rename src/ch/eitchnet/privilege/{model/internal => policy}/RestrictionPolicy.java (77%) diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml new file mode 100644 index 000000000..9a1a73074 --- /dev/null +++ b/config/PrivilegeRoles.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml new file mode 100644 index 000000000..d3571ea83 --- /dev/null +++ b/config/PrivilegeUsers.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/config/RestrictionPolicies.xml b/config/RestrictionPolicies.xml new file mode 100644 index 000000000..878d30e8a --- /dev/null +++ b/config/RestrictionPolicies.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config/RestrictionPolicy.xml b/config/RestrictionPolicy.xml deleted file mode 100644 index c1ec64418..000000000 --- a/config/RestrictionPolicy.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 692188cee..d7f1e50f3 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,9 +19,11 @@ public class XmlConstants { public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; + public static final String XML_POLICY = "Policy"; public static final String XML_PARAMETERS = "Parameters"; public static final String XML_ATTR_CLASS = "class"; + public static final String XML_ATTR_NAME = "name"; public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index 339752bbc..a72f5c996 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -11,18 +11,21 @@ package ch.eitchnet.privilege.handler; import java.io.File; +import java.util.HashMap; +import java.util.List; import java.util.Map; import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.RestrictionPolicy; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.policy.RestrictionPolicy; /** * @author rvonburg @@ -30,7 +33,7 @@ import ch.eitchnet.privilege.model.internal.Role; */ public class DefaultPolicyHandler implements PolicyHandler { - private Map policyMap; + private Map> policyMap; /** * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, @@ -53,13 +56,16 @@ public class DefaultPolicyHandler implements PolicyHandler { + restrictable.getClass().getName()); } - // get restriction policy - RestrictionPolicy policy = policyMap.get(restrictionKey); - if (policy == null) { + // get restriction policy class + Class policyClazz = policyMap.get(restrictionKey); + if (policyClazz == null) { throw new PrivilegeException("No RestrictionPolicy exists for the RestrictionKey " + restrictionKey + " for Restrictable " + restrictable.getClass().getName()); } + // instantiate policy + RestrictionPolicy policy = ClassHelper.instantiateClass(policyClazz); + // delegate checking to restriction policy return policy.actionAllowed(role, restrictable); } @@ -67,6 +73,7 @@ public class DefaultPolicyHandler implements PolicyHandler { /** * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) */ + @SuppressWarnings("unchecked") public void initialize(Element element) { // get parameters @@ -88,7 +95,19 @@ public class DefaultPolicyHandler implements PolicyHandler { + policyFile.getAbsolutePath()); } + policyMap = new HashMap>(); + // parse policy xml file to XML document Element containerRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); + + List policyElements = containerRootElement.elements(XmlConstants.XML_POLICY); + for (Element policyElement : policyElements) { + String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + + Class clazz = ClassHelper.loadClass(policyClass); + + policyMap.put(policyName, clazz); + } } } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index bd12a2b5d..617bb98c2 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -12,8 +12,8 @@ package ch.eitchnet.privilege.handler; import java.util.List; -import ch.eitchnet.privilege.model.internal.RestrictionPolicy; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.RestrictionPolicy; /** * @author rvonburg diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java index 521c4df4f..34c9a1139 100644 --- a/src/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -30,4 +30,27 @@ public class ClassHelper { throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); } } + + public static T instantiateClass(Class clazz) { + try { + + return clazz.getConstructor().newInstance(); + + } catch (Exception e) { + throw new PrivilegeException("The class " + clazz.getName() + " could not be instantiated: ", e); + } + } + + @SuppressWarnings("unchecked") + public static Class loadClass(String className) { + try { + + Class clazz = (Class) Class.forName(className); + + return clazz; + + } catch (Exception e) { + throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); + } + } } diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 779fdd802..4e1d7201b 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -20,18 +20,18 @@ import java.util.List; public class Privilege { private final boolean allAllowed; - private final List valuesAllowed; - private final List valuesNotAllowed; + private final List allowList; + private final List denyList; /** * @param allAllowed - * @param valuesAllowed - * @param valuesNotAllowed + * @param allowList + * @param denyList */ - public Privilege(boolean allAllowed, List valuesAllowed, List valuesNotAllowed) { + public Privilege(boolean allAllowed, List allowList, List denyList) { this.allAllowed = allAllowed; - this.valuesAllowed = Collections.unmodifiableList(valuesAllowed); - this.valuesNotAllowed = Collections.unmodifiableList(valuesNotAllowed); + this.allowList = Collections.unmodifiableList(allowList); + this.denyList = Collections.unmodifiableList(denyList); } /** @@ -42,16 +42,17 @@ public class Privilege { } /** - * @return the valuesAllowed + * @return the allowList */ - public List getValuesAllowed() { - return valuesAllowed; + public List getAllowList() { + return allowList; } /** - * @return the valuesNotAllowed + * @return the denyList */ - public List getValuesNotAllowed() { - return valuesNotAllowed; + public List getDenyList() { + return denyList; } + } diff --git a/src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java similarity index 61% rename from src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java rename to src/ch/eitchnet/privilege/policy/DefaultRestriction.java index 0516b4281..33ff8709d 100644 --- a/src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java +++ b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java @@ -8,12 +8,12 @@ * */ -package ch.eitchnet.privilege.model.internal; - -import org.dom4j.Element; +package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg @@ -21,11 +21,9 @@ import ch.eitchnet.privilege.model.Restrictable; */ public class DefaultRestriction implements RestrictionPolicy { - private String restrictionKey; - /** - * @see ch.eitchnet.privilege.model.internal.RestrictionPolicy#actionAllowed(ch.eitchnet.privilege.model.User, - * ch.eitchnet.privilege.model.Restrictable) + * @see ch.eitchnet.privilege.policy.RestrictionPolicy#actionAllowed(java.lang.String, + * ch.eitchnet.privilege.model.internal.Role, ch.eitchnet.privilege.model.Restrictable) */ @Override public boolean actionAllowed(Role role, Restrictable restrictable) { @@ -34,18 +32,17 @@ public class DefaultRestriction implements RestrictionPolicy { if (role == null) throw new PrivilegeException("Role may not be null!"); - // validate Restrictable is set for this RestrictionPolicy - if (!restrictionKey.equals(restrictable.getRestrictionKey())) { - throw new PrivilegeException(RestrictionPolicy.class.getSimpleName() + " " - + DefaultRestriction.class.getSimpleName() + " with restriction key " + restrictionKey - + " can not validate " + Restrictable.class.getSimpleName() + " with key " - + restrictable.getRestrictionKey()); + // get the restriction key + String restrictionKey = restrictable.getRestrictionKey(); + if (restrictionKey == null || restrictionKey.isEmpty()) { + throw new PrivilegeException("The restriction key for the Restrictable is null or empty: " + restrictable); } // get restriction object for users role Privilege privilege = role.getPrivilege(restrictionKey); // no restriction object means no privilege + // TODO should default deny/allow policy be configurable? if (privilege == null) return false; @@ -65,13 +62,13 @@ public class DefaultRestriction implements RestrictionPolicy { String restrictionValue = (String) object; // first check values not allowed - for (String notAllowed : privilege.getValuesNotAllowed()) { - if (notAllowed.equals(restrictionValue)) + for (String denied : privilege.getDenyList()) { + if (denied.equals(restrictionValue)) return false; } // now check values allowed - for (String allowed : privilege.getValuesAllowed()) { + for (String allowed : privilege.getAllowList()) { if (allowed.equals(restrictionValue)) return true; } @@ -79,9 +76,4 @@ public class DefaultRestriction implements RestrictionPolicy { // default is not allowed return false; } - - public void initialize(Element element) { - - // TODO implement - } } diff --git a/src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java b/src/ch/eitchnet/privilege/policy/RestrictionPolicy.java similarity index 77% rename from src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java rename to src/ch/eitchnet/privilege/policy/RestrictionPolicy.java index 35c18859a..093ca1468 100644 --- a/src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java +++ b/src/ch/eitchnet/privilege/policy/RestrictionPolicy.java @@ -8,9 +8,10 @@ * */ -package ch.eitchnet.privilege.model.internal; +package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg From 89e4f30bcf5dbdae2fc661330de0dd785e7287ad Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 30 May 2010 19:28:39 +0000 Subject: [PATCH 013/457] --- config/PrivilegeContainer.xml | 5 +- config/PrivilegeRoles.xml | 6 +- config/PrivilegeUsers.xml | 8 +- config/RestrictionPolicies.xml | 2 +- .../eitchnet/privilege/base/XmlConstants.java | 16 +++ .../handler/DefaultSessionHandler.java | 102 ++++++++++++++++++ 6 files changed, 131 insertions(+), 8 deletions(-) diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index b40c6a226..c2b5beab8 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -3,7 +3,8 @@ - + + @@ -13,7 +14,7 @@ - + diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index 9a1a73074..23e686431 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -1,9 +1,9 @@ - - - + + + true diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index d3571ea83..e215250cd 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -1,9 +1,13 @@ - + + Robert + von Burg + NEW + en_GB - + admin diff --git a/config/RestrictionPolicies.xml b/config/RestrictionPolicies.xml index 878d30e8a..bcc0629bc 100644 --- a/config/RestrictionPolicies.xml +++ b/config/RestrictionPolicies.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index d7f1e50f3..a5cee78dc 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,12 +19,28 @@ public class XmlConstants { public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; + public static final String XML_ROLES = "Roles"; + public static final String XML_ROLE = "role"; + public static final String XML_USER = "User"; + public static final String XML_PRIVILEGE = "Privilege"; public static final String XML_POLICY = "Policy"; public static final String XML_PARAMETERS = "Parameters"; + public static final String XML_ALL_ALLOWED = "allAllowed"; + public static final String XML_DENY = "deny"; + public static final String XML_ALLOW = "allow"; + public static final String XML_FIRSTNAME = "firstname"; + public static final String XML_SURNAME = "surname"; + public static final String XML_STATE = "state"; + public static final String XML_LOCALE = "locale"; public static final String XML_ATTR_CLASS = "class"; public static final String XML_ATTR_NAME = "name"; + public static final String XML_ATTR_POLICY = "policy"; + public static final String XML_ATTR_USERNAME = "username"; + public static final String XML_ATTR_PASSWORD = "password"; public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; + public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; + public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; } diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 8e07079d3..c7c7667d5 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -10,12 +10,19 @@ package ch.eitchnet.privilege.handler; +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -173,9 +180,104 @@ public class DefaultSessionHandler implements SessionHandler { * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) */ public void initialize(Element element) { + + // get parameters + Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + + // get roles file name + String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); + } + + // get roles file + File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + if (!rolesFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " + + rolesFile.getAbsolutePath()); + } + + // parse roles xml file to XML document + Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); + readRoles(rolesRootElement); + + // TODO read roles + + // get users file name + String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + } + + // get users file + File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + if (!usersFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " + + usersFile.getAbsolutePath()); + } + + // parse users xml file to XML document + Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); + readUsers(usersRootElement); + + // TODO read users + // TODO implement } + /** + * @param usersRootElement + */ + private void readUsers(Element usersRootElement) { + + List userElements = usersRootElement.elements(XmlConstants.XML_USER); + for (Element userElement : userElements) { + + String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); + String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); + + String firstname = userElement.element(XmlConstants.XML_FIRSTNAME).getTextTrim(); + String surname = userElement.element(XmlConstants.XML_SURNAME).getTextTrim(); + + UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); + + // TODO better handling needed + String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); + Locale locale = new Locale(localeName); + + Element rolesElement = userElement.element(XmlConstants.XML_ROLES); + List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); + List roleList = new LinkedList(); + for (Element roleElement : rolesElementList) { + String roleName = roleElement.getTextTrim(); + if (roleList.isEmpty()) { + logger.warn("User " + username + " has an role defined with empty name, Skipped."); + } else { + roleList.add(roleName); + } + } + + // create user + User user = User.buildUser(username, password, firstname, surname, userState, roleList, locale); + + // put user in map + userMap.put(username, user); + } + } + + /** + * @param rolesRootElement + */ + private void readRoles(Element rolesRootElement) { + // TODO Auto-generated method stub + + } + private class CertificateSessionPair { private Session session; private Certificate certificate; From 0775f52b0cd7de9e1e2cea3cbd5ca714a9d5aaad Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 31 May 2010 19:34:26 +0000 Subject: [PATCH 014/457] --- config/PrivilegeRoles.xml | 2 +- config/RestrictionPolicies.xml | 2 +- .../privilege/base/PrivilegeContainer.java | 2 - .../handler/DefaultSessionHandler.java | 61 +++++++++++++++++-- .../privilege/handler/PersistenceHandler.java | 4 +- .../privilege/i18n/AccessDeniedException.java | 6 +- .../eitchnet/privilege/model/Certificate.java | 2 +- .../privilege/model/internal/Privilege.java | 28 +++++++-- .../privilege/model/internal/Role.java | 13 +++- .../privilege/model/internal/Session.java | 2 +- .../privilege/model/internal/User.java | 15 ++++- 11 files changed, 112 insertions(+), 25 deletions(-) diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index 23e686431..49ea61ef9 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -2,7 +2,7 @@ - + true diff --git a/config/RestrictionPolicies.xml b/config/RestrictionPolicies.xml index bcc0629bc..acda375fe 100644 --- a/config/RestrictionPolicies.xml +++ b/config/RestrictionPolicies.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 953b3b7ae..5b737c4d9 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -23,8 +23,6 @@ import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** - * - * * @author rvonburg */ public class PrivilegeContainer { diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index c7c7667d5..714fddc43 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -11,6 +11,8 @@ package ch.eitchnet.privilege.handler; import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -28,6 +30,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; @@ -202,9 +205,9 @@ public class DefaultSessionHandler implements SessionHandler { // parse roles xml file to XML document Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); - readRoles(rolesRootElement); - // TODO read roles + // read roles + readRoles(rolesRootElement); // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); @@ -223,11 +226,12 @@ public class DefaultSessionHandler implements SessionHandler { // parse users xml file to XML document Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); + + // read users readUsers(usersRootElement); - // TODO read users - - // TODO implement + logger.info("Read " + userMap.size() + " Users"); + logger.info("Read " + roleMap.size() + " Roles"); } /** @@ -274,8 +278,53 @@ public class DefaultSessionHandler implements SessionHandler { * @param rolesRootElement */ private void readRoles(Element rolesRootElement) { - // TODO Auto-generated method stub + List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); + for (Element roleElement : roleElements) { + + String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); + + List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); + Map privilegeMap = new HashMap(); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); + + String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); + boolean allAllowed = Boolean.valueOf(allAllowedS); + + List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); + List denyList = new ArrayList(denyElements.size()); + for (Element denyElement : denyElements) { + String denyValue = denyElement.getTextTrim(); + if (denyValue.isEmpty()) { + logger.error("Role " + roleName + " has privilege " + privilegeName + + " with an empty deny value!"); + } else { + denyList.add(denyValue); + } + } + + List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); + List allowList = new ArrayList(allowElements.size()); + for (Element allowElement : allowElements) { + String allowValue = allowElement.getTextTrim(); + if (allowValue.isEmpty()) { + logger.error("Role " + roleName + " has privilege " + privilegeName + + " with an empty allow value!"); + } else { + allowList.add(allowValue); + } + } + + Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); + privilegeMap.put(privilegeName, privilege); + } + + Role role = new Role(roleName, privilegeMap); + roleMap.put(roleName, role); + } } private class CertificateSessionPair { diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 617bb98c2..ba73ff7ea 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -17,11 +17,13 @@ import ch.eitchnet.privilege.policy.RestrictionPolicy; /** * @author rvonburg - * + * */ public interface PersistenceHandler { public List getAllUsers(); + public void saveUsers(List users); + public List getAllRestrictionPolicies(); } diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java index 1ff487f4b..c84978e3f 100644 --- a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -16,14 +16,12 @@ package ch.eitchnet.privilege.i18n; */ public class AccessDeniedException extends PrivilegeException { + private static final long serialVersionUID = 1L; + /** * @param string */ public AccessDeniedException(String string) { super(string); - // TODO Auto-generated constructor stub } - - private static final long serialVersionUID = 1L; - } diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index bb92989fa..d7269cde2 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -19,7 +19,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * @author rvonburg * */ -public class Certificate implements Serializable { +public final class Certificate implements Serializable { private static final long serialVersionUID = 1L; diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 4e1d7201b..ccb7a65c0 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -17,21 +17,39 @@ import java.util.List; * @author rvonburg * */ -public class Privilege { +public final class Privilege { + private final String name; + private final String policy; private final boolean allAllowed; - private final List allowList; private final List denyList; + private final List allowList; /** * @param allAllowed - * @param allowList * @param denyList + * @param allowList */ - public Privilege(boolean allAllowed, List allowList, List denyList) { + public Privilege(String name, String policy, boolean allAllowed, List denyList, List allowList) { + this.name = name; + this.policy = policy; this.allAllowed = allAllowed; - this.allowList = Collections.unmodifiableList(allowList); this.denyList = Collections.unmodifiableList(denyList); + this.allowList = Collections.unmodifiableList(allowList); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @return the policy + */ + public String getPolicy() { + return policy; } /** diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index be6c7e841..4032e9d8b 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -17,17 +17,26 @@ import java.util.Map; * @author rvonburg * */ -public class Role { +public final class Role { + private final String roleName; private final Map privilegeMap; /** * @param privilegeMap */ - public Role(Map privilegeMap) { + public Role(String roleName, Map privilegeMap) { + this.roleName = roleName; this.privilegeMap = Collections.unmodifiableMap(privilegeMap); } + /** + * @return the roleName + */ + public String getRoleName() { + return roleName; + } + /** * @param key * @return diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index d648a6fb1..cdcd1cda5 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -14,7 +14,7 @@ package ch.eitchnet.privilege.model.internal; * @author rvonburg * */ -public class Session { +public final class Session { private final String sessionId; private final String username; diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index fad6be185..df66c3bea 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -14,13 +14,14 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.UserState; /** * @author rvonburg * */ -public class User { +public final class User { private final String username; private final String password; @@ -112,6 +113,18 @@ public class User { locale = Locale.getDefault(); // TODO validate who is creating this User object + + if (username.length() < 3) { + throw new PrivilegeException("The given username is shorter than 3 characters"); + } + + if (firstname.isEmpty()) { + throw new PrivilegeException("The given firstname is empty"); + } + + if (surname.isEmpty()) { + throw new PrivilegeException("The given firstname is empty"); + } User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableList(roleList), locale); From 756ae1e3e91a17d5f35107580fdb8f2978ade5b6 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 31 May 2010 21:44:15 +0000 Subject: [PATCH 015/457] --- config/PrivilegeContainer.xml | 2 +- config/PrivilegeRoles.xml | 6 +- config/PrivilegeUsers.xml | 12 ++-- .../eitchnet/privilege/base/XmlConstants.java | 16 ++--- .../handler/DefaultEncryptionHandler.java | 36 ++++------- .../handler/DefaultSessionHandler.java | 9 ++- .../privilege/helper/EncryptionHelper.java | 47 +++++++++++++++ .../privilege/helper/PasswordCreator.java | 54 +++++++++++++++++ .../helper/TestConfigurationHelper.java | 59 +++++++++++++++++++ .../eitchnet/privilege/helper/XmlHelper.java | 2 +- 10 files changed, 196 insertions(+), 47 deletions(-) create mode 100644 src/ch/eitchnet/privilege/helper/EncryptionHelper.java create mode 100644 src/ch/eitchnet/privilege/helper/PasswordCreator.java create mode 100644 src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index c2b5beab8..547e13e78 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -9,7 +9,7 @@ - + diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index 49ea61ef9..dab3a4382 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -3,9 +3,9 @@ - true - - + true + + diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index e215250cd..b9e54eb55 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -1,13 +1,13 @@ - - Robert - von Burg - NEW - en_GB + + Robert + von Burg + ENABLED + en_GB - admin + admin diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index a5cee78dc..c98317907 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -20,18 +20,18 @@ public class XmlConstants { public static final String XML_HANDLER_POLICY = "PolicyHandler"; public static final String XML_ROLES = "Roles"; - public static final String XML_ROLE = "role"; + public static final String XML_ROLE = "Role"; public static final String XML_USER = "User"; public static final String XML_PRIVILEGE = "Privilege"; public static final String XML_POLICY = "Policy"; public static final String XML_PARAMETERS = "Parameters"; - public static final String XML_ALL_ALLOWED = "allAllowed"; - public static final String XML_DENY = "deny"; - public static final String XML_ALLOW = "allow"; - public static final String XML_FIRSTNAME = "firstname"; - public static final String XML_SURNAME = "surname"; - public static final String XML_STATE = "state"; - public static final String XML_LOCALE = "locale"; + public static final String XML_ALL_ALLOWED = "AllAllowed"; + public static final String XML_DENY = "Deny"; + public static final String XML_ALLOW = "Allow"; + public static final String XML_FIRSTNAME = "Firstname"; + public static final String XML_SURNAME = "Surname"; + public static final String XML_STATE = "State"; + public static final String XML_LOCALE = "Locale"; public static final String XML_ATTR_CLASS = "class"; public static final String XML_ATTR_NAME = "name"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 2810ef699..8cb679885 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -11,8 +11,6 @@ package ch.eitchnet.privilege.handler; import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; @@ -22,6 +20,7 @@ import org.dom4j.Element; import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.EncryptionHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** @@ -31,14 +30,8 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; public class DefaultEncryptionHandler implements EncryptionHandler { private static final Logger logger = Logger.getLogger(DefaultEncryptionHandler.class); - public String hashAlgorithm; - - /** - * Hex char table for fast calculating of hex value - */ - 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' }; + private SecureRandom secureRandom; + private String hashAlgorithm; /** * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) @@ -47,19 +40,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String convertToHash(String string) { try { - MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); - byte[] hashArray = digest.digest(string.getBytes()); - - byte[] hex = new byte[2 * hashArray.length]; - int index = 0; - - for (byte b : hashArray) { - int v = b & 0xFF; - hex[index++] = HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = HEX_CHAR_TABLE[v & 0xF]; - } - - return new String(hex, "ASCII"); + return EncryptionHelper.encryptString(hashAlgorithm, string); } catch (NoSuchAlgorithmException e) { throw new PrivilegeException("Algorithm " + hashAlgorithm + " was not found!", e); @@ -73,9 +54,10 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ @Override public String nextToken() { - SecureRandom secureRandom = new SecureRandom(); - String randomString = new BigInteger(130, secureRandom).toString(32); - logger.info("Token: " + randomString); // XXX remove this line after testing!!! + byte[] bytes = new byte[16]; + secureRandom.nextBytes(bytes); + String randomString = new String(bytes); + //String randomString = new BigInteger(80, secureRandom).toString(32); // 80 big integer bits = 16 chars return randomString; } @@ -84,6 +66,8 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ public void initialize(Element element) { + secureRandom = new SecureRandom(); + // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 714fddc43..7452e71ba 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -184,6 +184,11 @@ public class DefaultSessionHandler implements SessionHandler { */ public void initialize(Element element) { + lastSessionId = 0l; + roleMap = new HashMap(); + userMap = new HashMap(); + sessionMap = new HashMap(); + // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); @@ -259,8 +264,8 @@ public class DefaultSessionHandler implements SessionHandler { List roleList = new LinkedList(); for (Element roleElement : rolesElementList) { String roleName = roleElement.getTextTrim(); - if (roleList.isEmpty()) { - logger.warn("User " + username + " has an role defined with empty name, Skipped."); + if (roleName.isEmpty()) { + logger.warn("User " + username + " has a role defined with no name, Skipped."); } else { roleList.add(roleName); } diff --git a/src/ch/eitchnet/privilege/helper/EncryptionHelper.java b/src/ch/eitchnet/privilege/helper/EncryptionHelper.java new file mode 100644 index 000000000..afc52c1f2 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/EncryptionHelper.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author rvonburg + * + */ +public class EncryptionHelper { + + /** + * Hex char table for fast calculating of hex value + */ + 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' }; + + public static String encryptString(String hashAlgorithm, String string) throws NoSuchAlgorithmException, + UnsupportedEncodingException { + + MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); + byte[] hashArray = digest.digest(string.getBytes()); + + byte[] hex = new byte[2 * hashArray.length]; + int index = 0; + + for (byte b : hashArray) { + int v = b & 0xFF; + hex[index++] = HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = HEX_CHAR_TABLE[v & 0xF]; + } + + return new String(hex, "ASCII"); + } +} diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java new file mode 100644 index 000000000..77b4cf677 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.security.MessageDigest; + +/** + * @author rvonburg + * + */ +public class PasswordCreator { + + /** + * @param args + */ + public static void main(String[] args) throws Exception { + + BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); + + String hashAlgorithm = null; + while (hashAlgorithm == null) { + System.out.print("Hash Algorithm [SHA-256]: "); + String readLine = r.readLine().trim(); + + if (readLine.isEmpty()) { + hashAlgorithm = "SHA-256"; + } else { + + try { + MessageDigest.getInstance(readLine); + hashAlgorithm = readLine; + } catch (Exception e) { + System.out.println(e.getLocalizedMessage()); + hashAlgorithm = null; + } + } + } + + System.out.print("Password: "); + String password = r.readLine(); + System.out.print("Hash is: " + EncryptionHelper.encryptString(hashAlgorithm, password)); + } + +} diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java new file mode 100644 index 000000000..ff384bb7c --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.File; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author rvonburg + * + */ +public class TestConfigurationHelper { + private static final Logger logger = Logger.getLogger(TestConfigurationHelper.class); + + /** + * @param args + */ + public static void main(String[] args) { + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); + Logger.getRootLogger().setLevel(Level.INFO); + + // initialize container + String pwd = System.getProperty("user.dir"); + File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); + PrivilegeContainer.getInstance().initialize(privilegeContainerXml); + + for (int i = 0; i < 10; i++) { + // let's authenticate a session + auth("eitch", "592038"); + } + } + + /** + * + */ + private static void auth(String username, String password) { + long start = System.currentTimeMillis(); + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate(username, password); + logger.info("Auth took " + (System.currentTimeMillis() - start)); + logger.info("Authenticated with certificate: " + certificate); + } +} diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index 3a8f2d4b4..693cfee3b 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -38,7 +38,7 @@ public class XmlHelper { SAXReader reader = new SAXReader(); Document document = reader.read(inStream); - logger.info("Read Xml document " + document.getName()); + logger.info("Read Xml document " + document.getRootElement().getName()); return document; } catch (FileNotFoundException e) { From 152d58d5154dc2dc06f98d3ff8c2e0ee373d95df Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 31 May 2010 21:55:52 +0000 Subject: [PATCH 016/457] --- config/PrivilegeUsers.xml | 2 +- src/ch/eitchnet/privilege/helper/PasswordCreator.java | 2 +- src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index b9e54eb55..b0e7a7604 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -1,7 +1,7 @@ - + Robert von Burg ENABLED diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java index 77b4cf677..701e58a94 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -47,7 +47,7 @@ public class PasswordCreator { } System.out.print("Password: "); - String password = r.readLine(); + String password = r.readLine().trim(); System.out.print("Hash is: " + EncryptionHelper.encryptString(hashAlgorithm, password)); } diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index ff384bb7c..df0fbb9ae 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -43,7 +43,7 @@ public class TestConfigurationHelper { for (int i = 0; i < 10; i++) { // let's authenticate a session - auth("eitch", "592038"); + auth("eitch", "1234567890"); } } From ddb1aa279ae73509beed702615124a314c7ad617 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 3 Jun 2010 20:37:00 +0000 Subject: [PATCH 017/457] - remodelled privilege to be its own object and put all models into the persistence handler --- config/PrivilegeContainer.xml | 6 +- config/PrivilegeRoles.xml | 6 +- config/Privileges.xml | 10 + .../privilege/base/PrivilegeContainer.java | 22 ++ .../eitchnet/privilege/base/XmlConstants.java | 2 + .../handler/DefaultPersistenceHandler.java | 309 ++++++++++++++++++ .../handler/DefaultSessionHandler.java | 167 +--------- .../privilege/handler/PersistenceHandler.java | 23 +- .../privilege/model/internal/Role.java | 20 +- .../privilege/model/internal/User.java | 20 +- .../privilege/policy/DefaultRestriction.java | 3 +- 11 files changed, 397 insertions(+), 191 deletions(-) create mode 100644 config/Privileges.xml create mode 100644 src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index 547e13e78..dad421977 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -1,12 +1,14 @@ - + + - + + diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index dab3a4382..c78b0ca4e 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -2,11 +2,7 @@ - - true - - - + \ No newline at end of file diff --git a/config/Privileges.xml b/config/Privileges.xml new file mode 100644 index 000000000..e1fb1de4e --- /dev/null +++ b/config/Privileges.xml @@ -0,0 +1,10 @@ + + + + + true + + + + + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 5b737c4d9..945721e83 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -16,6 +16,7 @@ import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; import ch.eitchnet.privilege.helper.ClassHelper; @@ -37,6 +38,7 @@ public class PrivilegeContainer { private SessionHandler sessionHandler; private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; + private PersistenceHandler persistenceHandler; private String basePath; @@ -72,6 +74,13 @@ public class PrivilegeContainer { return encryptionHandler; } + /** + * @return the persistenceHandler + */ + public PersistenceHandler getPersistenceHandler() { + return persistenceHandler; + } + /** * @return the basePath */ @@ -93,6 +102,11 @@ public class PrivilegeContainer { // parse container xml file to XML document Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); + // instantiate persistence handler + Element persistenceHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); + String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); + // instantiate session handler Element sessionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_SESSION); String sessionHandlerClassName = sessionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); @@ -108,6 +122,13 @@ public class PrivilegeContainer { String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); + try { + persistenceHandler.initialize(persistenceHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + + " could not be initialized"); + } try { sessionHandler.initialize(sessionHandlerElement); } catch (Exception e) { @@ -129,6 +150,7 @@ public class PrivilegeContainer { } // keep references to the handlers + this.persistenceHandler = persistenceHandler; this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; this.policyHandler = policyHandler; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index c98317907..b21a5322b 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -15,6 +15,7 @@ package ch.eitchnet.privilege.base; * */ public class XmlConstants { + public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; @@ -43,4 +44,5 @@ public class XmlConstants { public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; + public static final String XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile"; } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java new file mode 100644 index 000000000..f11f28956 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.dom4j.Element; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public class DefaultPersistenceHandler implements PersistenceHandler { + + private static final Logger logger = Logger.getLogger(DefaultPersistenceHandler.class); + + private Map userMap; + private Map roleMap; + private Map privilegesMap; + + private Map transientUserMap; + private Map transientRoleMap; + private Map transientPrivilegesMap; + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addPrivilege(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.internal.Privilege) + */ + @Override + public void addPrivilege(Certificate certificate, Privilege privilege) { + // TODO validate who is doing this + + privilegesMap.put(privilege.getName(), privilege); + transientPrivilegesMap.put(privilege.getName(), privilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addRole(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.internal.Role) + */ + @Override + public void addRole(Certificate certificate, Role role) { + // TODO validate who is doing this + + roleMap.put(role.getRoleName(), role); + transientRoleMap.put(role.getRoleName(), role); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.internal.User) + */ + @Override + public void addUser(Certificate certificate, User user) { + // TODO Auto-generated method stub + + userMap.put(user.getUsername(), user); + transientUserMap.put(user.getUsername(), user); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPrivilege(java.lang.String) + */ + @Override + public Privilege getPrivilege(String privilegeName) { + return privilegesMap.get(privilegeName); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getRole(java.lang.String) + */ + @Override + public Role getRole(String roleName) { + return roleMap.get(roleName); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getUser(java.lang.String) + */ + @Override + public User getUser(String username) { + return userMap.get(username); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() + */ + @Override + public void persist(Certificate certificate) { + + // TODO Auto-generated method stub + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ + @Override + public void initialize(Element element) { + + roleMap = new HashMap(); + userMap = new HashMap(); + privilegesMap = new HashMap(); + + // get parameters + Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + + // get roles file name + String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); + } + + // get roles file + File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + if (!rolesFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " + + rolesFile.getAbsolutePath()); + } + + // parse roles xml file to XML document + Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); + + // read roles + readRoles(rolesRootElement); + + // get users file name + String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + } + + // get users file + File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + if (!usersFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " + + usersFile.getAbsolutePath()); + } + + // parse users xml file to XML document + Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); + + // read users + readUsers(usersRootElement); + + // get privileges file name + String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); + if (privilegesFileName == null || privilegesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); + } + + // get privileges file + File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + if (!privilegesFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " + + privilegesFile.getAbsolutePath()); + } + + // parse privileges xml file to XML document + Element privilegesRootElement = XmlHelper.parseDocument(privilegesFile).getRootElement(); + + // read privileges + readPrivileges(privilegesRootElement); + + logger.info("Read " + userMap.size() + " Users"); + logger.info("Read " + roleMap.size() + " Roles"); + logger.info("Read " + privilegesMap.size() + " Privileges"); + } + + /** + * @param usersRootElement + */ + private void readUsers(Element usersRootElement) { + + List userElements = usersRootElement.elements(XmlConstants.XML_USER); + for (Element userElement : userElements) { + + String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); + String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); + + String firstname = userElement.element(XmlConstants.XML_FIRSTNAME).getTextTrim(); + String surname = userElement.element(XmlConstants.XML_SURNAME).getTextTrim(); + + UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); + + // TODO better handling needed + String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); + Locale locale = new Locale(localeName); + + Element rolesElement = userElement.element(XmlConstants.XML_ROLES); + List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); + Set roles = new HashSet(); + for (Element roleElement : rolesElementList) { + String roleName = roleElement.getTextTrim(); + if (roleName.isEmpty()) { + logger.warn("User " + username + " has a role defined with no name, Skipped."); + } else { + roles.add(roleName); + } + } + + // create user + User user = User.buildUser(username, password, firstname, surname, userState, roles, locale); + + // put user in map + userMap.put(username, user); + } + } + + /** + * @param rolesRootElement + */ + private void readRoles(Element rolesRootElement) { + + List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); + for (Element roleElement : roleElements) { + + String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); + + List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); + Set privileges = new HashSet(); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + privileges.add(privilegeName); + } + + Role role = new Role(roleName, privileges); + roleMap.put(roleName, role); + } + } + + /** + * @param rolesRootElement + */ + private void readPrivileges(Element privilegesRootElement) { + + List privilegeElements = privilegesRootElement.elements(XmlConstants.XML_PRIVILEGE); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); + + String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); + boolean allAllowed = Boolean.valueOf(allAllowedS); + + List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); + List denyList = new ArrayList(denyElements.size()); + for (Element denyElement : denyElements) { + String denyValue = denyElement.getTextTrim(); + if (denyValue.isEmpty()) { + logger.error("Privilege " + privilegeName + " has an empty deny value!"); + } else { + denyList.add(denyValue); + } + } + + List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); + List allowList = new ArrayList(allowElements.size()); + for (Element allowElement : allowElements) { + String allowValue = allowElement.getTextTrim(); + if (allowValue.isEmpty()) { + logger.error("Privilege " + privilegeName + " has an empty allow value!"); + } else { + allowList.add(allowValue); + } + } + + Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); + privilegesMap.put(privilegeName, privilege); + } + } +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 7452e71ba..807eceb4e 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -10,27 +10,18 @@ package ch.eitchnet.privilege.handler; -import java.io.File; -import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.base.XmlConstants; -import ch.eitchnet.privilege.helper.ConfigurationHelper; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; @@ -45,11 +36,11 @@ public class DefaultSessionHandler implements SessionHandler { private static long lastSessionId; - private Map userMap; - private Map roleMap; private Map sessionMap; /** + * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? + * * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.Restrictable) * @@ -84,7 +75,8 @@ public class DefaultSessionHandler implements SessionHandler { + certificate.getSessionId()); // get user object - User user = userMap.get(certificateSessionPair.session.getUsername()); + User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser( + certificateSessionPair.session.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); @@ -96,9 +88,9 @@ public class DefaultSessionHandler implements SessionHandler { // now iterate roles and validate on policy handler PolicyHandler policyHandler = PrivilegeContainer.getInstance().getPolicyHandler(); - for (String roleName : user.getRoleList()) { + for (String roleName : user.getRoles()) { - Role role = roleMap.get(roleName); + Role role = PrivilegeContainer.getInstance().getPersistenceHandler().getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -135,7 +127,7 @@ public class DefaultSessionHandler implements SessionHandler { String passwordHash = encryptionHandler.convertToHash(password); // get user object - User user = userMap.get(username); + User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser(username); // no user means no authentication if (user == null) throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); @@ -149,7 +141,7 @@ public class DefaultSessionHandler implements SessionHandler { throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); // validate user has at least one role - if (user.getRoleList().isEmpty()) { + if (user.getRoles().isEmpty()) { throw new PrivilegeException("User " + username + " does not have any roles defined!"); } @@ -185,151 +177,8 @@ public class DefaultSessionHandler implements SessionHandler { public void initialize(Element element) { lastSessionId = 0l; - roleMap = new HashMap(); - userMap = new HashMap(); sessionMap = new HashMap(); - // get parameters - Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); - - // get roles file name - String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); - } - - // get roles file - File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); - if (!rolesFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " - + rolesFile.getAbsolutePath()); - } - - // parse roles xml file to XML document - Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); - - // read roles - readRoles(rolesRootElement); - - // get users file name - String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); - } - - // get users file - File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); - if (!usersFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " - + usersFile.getAbsolutePath()); - } - - // parse users xml file to XML document - Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); - - // read users - readUsers(usersRootElement); - - logger.info("Read " + userMap.size() + " Users"); - logger.info("Read " + roleMap.size() + " Roles"); - } - - /** - * @param usersRootElement - */ - private void readUsers(Element usersRootElement) { - - List userElements = usersRootElement.elements(XmlConstants.XML_USER); - for (Element userElement : userElements) { - - String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); - String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); - - String firstname = userElement.element(XmlConstants.XML_FIRSTNAME).getTextTrim(); - String surname = userElement.element(XmlConstants.XML_SURNAME).getTextTrim(); - - UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); - - // TODO better handling needed - String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); - Locale locale = new Locale(localeName); - - Element rolesElement = userElement.element(XmlConstants.XML_ROLES); - List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); - List roleList = new LinkedList(); - for (Element roleElement : rolesElementList) { - String roleName = roleElement.getTextTrim(); - if (roleName.isEmpty()) { - logger.warn("User " + username + " has a role defined with no name, Skipped."); - } else { - roleList.add(roleName); - } - } - - // create user - User user = User.buildUser(username, password, firstname, surname, userState, roleList, locale); - - // put user in map - userMap.put(username, user); - } - } - - /** - * @param rolesRootElement - */ - private void readRoles(Element rolesRootElement) { - - List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); - for (Element roleElement : roleElements) { - - String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); - - List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); - Map privilegeMap = new HashMap(); - for (Element privilegeElement : privilegeElements) { - - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - - String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); - boolean allAllowed = Boolean.valueOf(allAllowedS); - - List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - List denyList = new ArrayList(denyElements.size()); - for (Element denyElement : denyElements) { - String denyValue = denyElement.getTextTrim(); - if (denyValue.isEmpty()) { - logger.error("Role " + roleName + " has privilege " + privilegeName - + " with an empty deny value!"); - } else { - denyList.add(denyValue); - } - } - - List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - List allowList = new ArrayList(allowElements.size()); - for (Element allowElement : allowElements) { - String allowValue = allowElement.getTextTrim(); - if (allowValue.isEmpty()) { - logger.error("Role " + roleName + " has privilege " + privilegeName - + " with an empty allow value!"); - } else { - allowList.add(allowValue); - } - } - - Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); - } - - Role role = new Role(roleName, privilegeMap); - roleMap.put(roleName, role); - } } private class CertificateSessionPair { diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index ba73ff7ea..5807f27d2 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,20 +10,29 @@ package ch.eitchnet.privilege.handler; -import java.util.List; - +import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.policy.RestrictionPolicy; /** * @author rvonburg * */ -public interface PersistenceHandler { +public interface PersistenceHandler extends PrivilegeContainerObject { - public List getAllUsers(); + public User getUser(String username); - public void saveUsers(List users); + public void addUser(Certificate certificate, User user); - public List getAllRestrictionPolicies(); + public Role getRole(String roleName); + + public void addRole(Certificate certificate, Role role); + + public Privilege getPrivilege(String privilegeName); + + public void addPrivilege(Certificate certificate, Privilege privilege); + + public void persist(Certificate certificate); } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 4032e9d8b..d4e2d57ad 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -11,7 +11,7 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; -import java.util.Map; +import java.util.Set; /** * @author rvonburg @@ -20,14 +20,14 @@ import java.util.Map; public final class Role { private final String roleName; - private final Map privilegeMap; + private final Set privileges; /** * @param privilegeMap */ - public Role(String roleName, Map privilegeMap) { + public Role(String roleName, Set privileges) { this.roleName = roleName; - this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + this.privileges = Collections.unmodifiableSet(privileges); } /** @@ -37,12 +37,18 @@ public final class Role { return roleName; } + /** + * @return + */ + public Set getPrivileges() { + return privileges; + } + /** * @param key * @return - * @see java.util.Map#get(java.lang.Object) */ - public Privilege getPrivilege(String key) { - return privilegeMap.get(key); + public boolean hasPrivilege(String key) { + return privileges.contains(key); } } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index df66c3bea..d9c4fd634 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -11,8 +11,8 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; -import java.util.List; import java.util.Locale; +import java.util.Set; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.UserState; @@ -31,7 +31,7 @@ public final class User { private final UserState userState; - private final List roleList; + private final Set roles; private final Locale locale; @@ -39,7 +39,7 @@ public final class User { * The {@link User} constructor is private to ensure no unauthorized creation of {@link User} objects */ private User(String username, String password, String firstname, String surname, UserState userState, - List roleList, Locale locale) { + Set roles, Locale locale) { this.username = username; this.password = password; @@ -48,7 +48,7 @@ public final class User { this.firstname = firstname; this.surname = surname; - this.roleList = roleList; + this.roles = roles; this.locale = locale; } @@ -89,10 +89,10 @@ public final class User { } /** - * @return the roleList + * @return the roles */ - public List getRoleList() { - return roleList; + public Set getRoles() { + return roles; } /** @@ -106,14 +106,14 @@ public final class User { * @return a new {@link User} object which is authenticated on the current Java Virtual Machine */ public static User buildUser(String username, String password, String firstname, String surname, - UserState userState, List roleList, Locale locale) { + UserState userState, Set roles, Locale locale) { // set a default locale if (locale == null) locale = Locale.getDefault(); // TODO validate who is creating this User object - + if (username.length() < 3) { throw new PrivilegeException("The given username is shorter than 3 characters"); } @@ -126,7 +126,7 @@ public final class User { throw new PrivilegeException("The given firstname is empty"); } - User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableList(roleList), + User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), locale); return user; diff --git a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java index 33ff8709d..77e7ba980 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java +++ b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.policy; +import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; @@ -39,7 +40,7 @@ public class DefaultRestriction implements RestrictionPolicy { } // get restriction object for users role - Privilege privilege = role.getPrivilege(restrictionKey); + Privilege privilege = PrivilegeContainer.getInstance().getPersistenceHandler().getPrivilege(restrictionKey); // no restriction object means no privilege // TODO should default deny/allow policy be configurable? From 0ca04da0533bb547a3add4468f760b225a0abb32 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 3 Jun 2010 20:41:31 +0000 Subject: [PATCH 018/457] --- .../eitchnet/privilege/handler/DefaultPersistenceHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index f11f28956..65d1c8cbe 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -79,7 +79,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public void addUser(Certificate certificate, User user) { - // TODO Auto-generated method stub + // TODO validate who is doing this userMap.put(user.getUsername(), user); transientUserMap.put(user.getUsername(), user); @@ -115,7 +115,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { @Override public void persist(Certificate certificate) { - // TODO Auto-generated method stub + // TODO validate who is doing this // TODO Auto-generated method stub From dc414218e6546d1dd26e3323319b60ce8a8098ff Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 5 Jun 2010 21:06:03 +0000 Subject: [PATCH 019/457] --- config/PrivilegeUsers.xml | 1 + .../privilege/base/PrivilegeContainer.java | 6 + .../eitchnet/privilege/base/XmlConstants.java | 4 + .../handler/DefaultPersistenceHandler.java | 270 ++++++++++++++++-- .../handler/DefaultSessionHandler.java | 82 ++++-- .../privilege/handler/SessionHandler.java | 16 ++ .../privilege/helper/ConfigurationHelper.java | 8 +- .../privilege/helper/PrivilegeHelper.java | 45 +++ .../eitchnet/privilege/helper/XmlHelper.java | 49 +++- .../privilege/model/internal/Role.java | 16 +- .../privilege/model/internal/User.java | 10 + 11 files changed, 448 insertions(+), 59 deletions(-) create mode 100644 src/ch/eitchnet/privilege/helper/PrivilegeHelper.java diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index b0e7a7604..cc8867f25 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -8,6 +8,7 @@ en_GB admin + PrivilegeAdmin diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 945721e83..42d17de2d 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -27,6 +27,12 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * @author rvonburg */ public class PrivilegeContainer { + + /** + * This is the role users must have, if they can modify the {@link PrivilegeContainer} and its objects + */ + public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; + private static final Logger logger = Logger.getLogger(PrivilegeContainer.class); private static final PrivilegeContainer instance; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index b21a5322b..0377adcb7 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -22,10 +22,13 @@ public class XmlConstants { public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; + public static final String XML_USERS = "Users"; public static final String XML_USER = "User"; + public static final String XML_PRIVILEGES = "Privileges"; public static final String XML_PRIVILEGE = "Privilege"; public static final String XML_POLICY = "Policy"; public static final String XML_PARAMETERS = "Parameters"; + public static final String XML_PARAMETER = "Parameter"; public static final String XML_ALL_ALLOWED = "AllAllowed"; public static final String XML_DENY = "Deny"; public static final String XML_ALLOW = "Allow"; @@ -36,6 +39,7 @@ public class XmlConstants { public static final String XML_ATTR_CLASS = "class"; public static final String XML_ATTR_NAME = "name"; + public static final String XML_ATTR_VALUE = "value"; public static final String XML_ATTR_POLICY = "policy"; public static final String XML_ATTR_USERNAME = "username"; public static final String XML_ATTR_PASSWORD = "password"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index 65d1c8cbe..4a7b31ab5 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -20,11 +20,13 @@ import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; +import org.dom4j.DocumentFactory; import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.PrivilegeHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -43,11 +45,13 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private Map userMap; private Map roleMap; - private Map privilegesMap; + private Map privilegeMap; - private Map transientUserMap; - private Map transientRoleMap; - private Map transientPrivilegesMap; + private boolean userMapDirty; + private boolean roleMapDirty; + private boolean privilegeMapDirty; + + private Map parameterMap; /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#addPrivilege(ch.eitchnet.privilege.model.Certificate, @@ -55,10 +59,12 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public void addPrivilege(Certificate certificate, Privilege privilege) { - // TODO validate who is doing this - privilegesMap.put(privilege.getName(), privilege); - transientPrivilegesMap.put(privilege.getName(), privilege); + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + privilegeMap.put(privilege.getName(), privilege); + privilegeMapDirty = true; } /** @@ -67,10 +73,12 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public void addRole(Certificate certificate, Role role) { - // TODO validate who is doing this - roleMap.put(role.getRoleName(), role); - transientRoleMap.put(role.getRoleName(), role); + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + roleMap.put(role.getName(), role); + roleMapDirty = true; } /** @@ -79,10 +87,12 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public void addUser(Certificate certificate, User user) { - // TODO validate who is doing this + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); userMap.put(user.getUsername(), user); - transientUserMap.put(user.getUsername(), user); + userMapDirty = true; } /** @@ -90,7 +100,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public Privilege getPrivilege(String privilegeName) { - return privilegesMap.get(privilegeName); + return privilegeMap.get(privilegeName); } /** @@ -115,10 +125,93 @@ public class DefaultPersistenceHandler implements PersistenceHandler { @Override public void persist(Certificate certificate) { - // TODO validate who is doing this + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); - // TODO Auto-generated method stub + // USERS + if (!userMapDirty) { + logger.warn("No users unpersisted."); + } else { + logger.info("Persisting users..."); + // build XML DOM of users + List users = toDomUsers(); + Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_USERS); + for (Element userElement : users) { + rootElement.add(userElement); + } + + // get users file name + String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + } + + // get users file + File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + + // write DOM to file + XmlHelper.writeDocument(rootElement, usersFile); + } + + // ROLES + if (!roleMapDirty) { + logger.warn("No roles unpersisted."); + } else { + logger.info("Persisting roles..."); + + // build XML DOM of roles + List roles = toDomRoles(); + Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_ROLES); + for (Element roleElement : roles) { + rootElement.add(roleElement); + } + + // get roles file name + String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); + } + + // get roles file + File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + + // write DOM to file + XmlHelper.writeDocument(rootElement, rolesFile); + } + + // PRIVILEGES + if (!privilegeMapDirty) { + logger.warn("No privileges unpersisted."); + } else { + logger.info("Persisting privileges..."); + + // build XML DOM of privileges + List privileges = toDomPrivileges(); + Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_PRIVILEGES); + for (Element privilegeElement : privileges) { + rootElement.add(privilegeElement); + } + + // get privileges file name + String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); + if (privilegesFileName == null || privilegesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); + } + + // get privileges file + File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + + // write DOM to file + XmlHelper.writeDocument(rootElement, privilegesFile); + } + + userMapDirty = false; + roleMapDirty = false; + privilegeMapDirty = false; } /** @@ -129,11 +222,11 @@ public class DefaultPersistenceHandler implements PersistenceHandler { roleMap = new HashMap(); userMap = new HashMap(); - privilegesMap = new HashMap(); + privilegeMap = new HashMap(); // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); @@ -198,9 +291,26 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // read privileges readPrivileges(privilegesRootElement); + userMapDirty = false; + roleMapDirty = false; + privilegeMapDirty = false; + logger.info("Read " + userMap.size() + " Users"); logger.info("Read " + roleMap.size() + " Roles"); - logger.info("Read " + privilegesMap.size() + " Privileges"); + logger.info("Read " + privilegeMap.size() + " Privileges"); + + // validate we have a user with PrivilegeAdmin access + boolean privilegeAdminExists = false; + for (String username : userMap.keySet()) { + User user = userMap.get(username); + if (user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { + privilegeAdminExists = true; + break; + } + } + if (!privilegeAdminExists) { + logger.warn("No User with PrivilegeAdmin role exists. Privilege modifications will not be possible!"); + } } /** @@ -229,7 +339,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { for (Element roleElement : rolesElementList) { String roleName = roleElement.getTextTrim(); if (roleName.isEmpty()) { - logger.warn("User " + username + " has a role defined with no name, Skipped."); + logger.error("User " + username + " has a role defined with no name, Skipped."); } else { roles.add(roleName); } @@ -303,7 +413,127 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegesMap.put(privilegeName, privilege); + privilegeMap.put(privilegeName, privilege); } } + + private List toDomPrivileges() { + + List privilegesAsElements = new ArrayList(privilegeMap.size()); + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + for (String privilegeName : privilegeMap.keySet()) { + + // get the privilege object + Privilege privilege = privilegeMap.get(privilegeName); + + // create the privilege element + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + + // add the all allowed element + Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.add(allAllowedElement); + + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); + denyValueElement.setText(denyValue); + privilegeElement.add(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setText(allowValue); + privilegeElement.add(allowValueElement); + } + + // add element to return list + privilegesAsElements.add(privilegeElement); + } + + return privilegesAsElements; + } + + private List toDomRoles() { + + List rolesAsElements = new ArrayList(roleMap.size()); + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + for (String roleName : roleMap.keySet()) { + + // get the role object + Role role = roleMap.get(roleName); + + // create the role element + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + + // add all the privileges + for (String privilegeName : role.getPrivileges()) { + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilegeName); + roleElement.add(privilegeElement); + } + + // add element to return list + rolesAsElements.add(roleElement); + } + + return rolesAsElements; + } + + private List toDomUsers() { + + List usersAsElements = new ArrayList(userMap.size()); + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + for (String userName : userMap.keySet()) { + + // get the user object + User user = userMap.get(userName); + + // create the user element + Element userElement = documentFactory.createElement(XmlConstants.XML_USER); + userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); + userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + + // add first name element + Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setText(user.getFirstname()); + userElement.add(firstnameElement); + + // add surname element + Element surnameElement = documentFactory.createElement(XmlConstants.XML_SURNAME); + surnameElement.setText(user.getSurname()); + userElement.add(surnameElement); + + // add state element + Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); + stateElement.setText(user.getState().toString()); + userElement.add(stateElement); + + // add locale element + Element localeElement = documentFactory.createElement(XmlConstants.XML_LOCALE); + localeElement.setText(user.getLocale().toString()); + userElement.add(localeElement); + + // add all the role elements + Element rolesElement = documentFactory.createElement(XmlConstants.XML_ROLES); + userElement.add(rolesElement); + for (String roleName : user.getRoles()) { + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.setText(roleName); + rolesElement.add(roleElement); + } + + // add element to return list + usersAsElements.add(userElement); + } + + return usersAsElements; + } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 807eceb4e..f63492ffb 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -51,11 +51,60 @@ public class DefaultSessionHandler implements SessionHandler { @Override public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { - // certificate and restrictable must not be null + // first validate certificate + if (!isCertificateValid(certificate)) { + logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " + + restrictable); + return false; + } + + // restrictable must not be null + if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); + PersistenceHandler persistenceHandler = privilegeContainer.getPersistenceHandler(); + + // get user object + User user = persistenceHandler.getUser(certificate.getUsername()); + if (user == null) { + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } + + // default is to not allow the action + // TODO should default deny/allow policy be configurable? + boolean actionAllowed = false; + + // now iterate roles and validate on policy handler + PolicyHandler policyHandler = privilegeContainer.getPolicyHandler(); + for (String roleName : user.getRoles()) { + + Role role = privilegeContainer.getPersistenceHandler().getRole(roleName); + if (role == null) { + logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + continue; + } + + actionAllowed = policyHandler.actionAllowed(role, restrictable); + + // if action is allowed, then break iteration as a privilege match has been made + if (actionAllowed) + break; + } + + return actionAllowed; + } + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public boolean isCertificateValid(Certificate certificate) { + + // certificate must not be null if (certificate == null) throw new PrivilegeException("Certificate may not be null!"); - else if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); // first see if a session exists for this certificate CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); @@ -77,33 +126,14 @@ public class DefaultSessionHandler implements SessionHandler { // get user object User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser( certificateSessionPair.session.getUsername()); + + // if user exists, then certificate is valid if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } else { + return true; } - - // default is to not allow the action - // TODO should default deny/allow policy be configurable? - boolean actionAllowed = false; - - // now iterate roles and validate on policy handler - PolicyHandler policyHandler = PrivilegeContainer.getInstance().getPolicyHandler(); - for (String roleName : user.getRoles()) { - - Role role = PrivilegeContainer.getInstance().getPersistenceHandler().getRole(roleName); - if (role == null) { - logger.error("No role is defined with name " + roleName + " which is configured for user " + user); - continue; - } - - actionAllowed = policyHandler.actionAllowed(role, restrictable); - - // if action is allowed, then break iteration as a privilege match has been made - if (actionAllowed) - break; - } - - return actionAllowed; } /** diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java index 3c46a11f9..d019d0dbe 100644 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -12,6 +12,7 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.User; @@ -25,17 +26,32 @@ public interface SessionHandler extends PrivilegeContainerObject { /** * @param certificate * @param restrictable + * * @return * * @throws AccessDeniedException * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not * perform the action defined by the {@link Restrictable} implementation + * @throws PrivilegeException + * if there is anything wrong with this certificate */ public boolean actionAllowed(Certificate certificate, Restrictable restrictable); + /** + * @param certificate + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public boolean isCertificateValid(Certificate certificate); + /** * @param user * @param password + * * @return * * @throws AccessDeniedException diff --git a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java index 177c5a3d7..34e6d642d 100644 --- a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java @@ -16,6 +16,8 @@ import java.util.Map; import org.dom4j.Element; +import ch.eitchnet.privilege.base.XmlConstants; + /** * @author rvonburg * @@ -27,10 +29,10 @@ public class ConfigurationHelper { Map parameterMap = new HashMap(); - List elements = element.elements("Parameter"); + List elements = element.elements(XmlConstants.XML_PARAMETER); for (Element parameter : elements) { - String name = parameter.attributeValue("name"); - String value = parameter.attributeValue("value"); + String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); + String value = parameter.attributeValue(XmlConstants.XML_ATTR_VALUE); parameterMap.put(name, value); } diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java new file mode 100644 index 000000000..720df2b4d --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public class PrivilegeHelper { + + public static boolean isUserPrivilegeAdmin(Certificate certificate) { + // validate certificate + if (!PrivilegeContainer.getInstance().getSessionHandler().isCertificateValid(certificate)) { + throw new PrivilegeException("Certificate " + certificate + " is not valid!"); + } + + // get user object + User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser(certificate.getUsername()); + if (user == null) { + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } + + // validate user has PrivilegeAdmin role + if (!user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { + throw new AccessDeniedException("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role!"); + } else { + return true; + } + } +} diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index 693cfee3b..05c75ac17 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -13,12 +13,19 @@ package ch.eitchnet.privilege.helper; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentException; +import org.dom4j.DocumentFactory; +import org.dom4j.Element; +import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -31,6 +38,7 @@ public class XmlHelper { private static final Logger logger = Logger.getLogger(XmlHelper.class); public static Document parseDocument(File xmlFile) { + try { InputStream inStream = new FileInputStream(xmlFile); @@ -38,13 +46,48 @@ public class XmlHelper { SAXReader reader = new SAXReader(); Document document = reader.read(inStream); - logger.info("Read Xml document " + document.getRootElement().getName()); + logger.info("Read XML document " + document.getRootElement().getName()); return document; } catch (FileNotFoundException e) { - throw new PrivilegeException("The Xml file does not exist or is not readable: " + xmlFile.getAbsolutePath()); + throw new PrivilegeException("The XML file does not exist or is not readable: " + xmlFile.getAbsolutePath()); } catch (DocumentException e) { - throw new PrivilegeException("the Xml file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + throw new PrivilegeException("the XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } + } + + public static void writeDocument(Element rootElement, File file) { + + logger.info("Exporting root element " + rootElement.getName() + " to " + file.getAbsolutePath()); + + OutputStream fileOutputStream = null; + + try { + Document document = DocumentFactory.getInstance().createDocument(); + document.setRootElement(rootElement); + + fileOutputStream = new FileOutputStream(file); + + String aEncodingScheme = "UTF-8"; + OutputFormat outformat = OutputFormat.createPrettyPrint(); + outformat.setEncoding(aEncodingScheme); + XMLWriter writer = new XMLWriter(fileOutputStream, outformat); + writer.write(document); + writer.flush(); + + } catch (Exception e) { + + throw new PrivilegeException("Exception while exporting to file: " + e, e); + + } finally { + + if (fileOutputStream != null) { + try { + fileOutputStream.close(); + } catch (IOException e) { + logger.error("Could not close file output stream: " + e, e); + } + } } } } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index d4e2d57ad..0dc6322d0 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -19,22 +19,24 @@ import java.util.Set; */ public final class Role { - private final String roleName; + private final String name; private final Set privileges; /** - * @param privilegeMap + * + * @param name + * @param privileges */ - public Role(String roleName, Set privileges) { - this.roleName = roleName; + public Role(String name, Set privileges) { + this.name = name; this.privileges = Collections.unmodifiableSet(privileges); } /** - * @return the roleName + * @return the name */ - public String getRoleName() { - return roleName; + public String getName() { + return name; } /** diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index d9c4fd634..9efc5bb2d 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -95,6 +95,16 @@ public final class User { return roles; } + /** + * Returns true if this user has the specified role + * + * @param role + * @return true if the this user has the specified role + */ + public boolean hasRole(String role) { + return roles.contains(role); + } + /** * @return the locale */ From aa28ab0fc31ba5d3b06ac5e0b107640b0c0cde08 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 5 Jun 2010 21:33:30 +0000 Subject: [PATCH 020/457] --- .../handler/DefaultPersistenceHandler.java | 4 +- .../privilege/handler/PersistenceHandler.java | 6 +- .../helper/TestConfigurationHelper.java | 14 +- .../eitchnet/privilege/model/Certificate.java | 11 +- .../privilege/model/PrivilegeRep.java | 120 ++++++++++++++++++ src/ch/eitchnet/privilege/model/RoleRep.java | 35 +++++ src/ch/eitchnet/privilege/model/UserRep.java | 49 +++++++ .../privilege/model/internal/Privilege.java | 96 ++++++++++++-- .../privilege/model/internal/Role.java | 61 +++++++++ .../privilege/model/internal/Session.java | 11 +- .../privilege/model/internal/User.java | 30 +++++ 11 files changed, 423 insertions(+), 14 deletions(-) create mode 100644 src/ch/eitchnet/privilege/model/PrivilegeRep.java create mode 100644 src/ch/eitchnet/privilege/model/RoleRep.java create mode 100644 src/ch/eitchnet/privilege/model/UserRep.java diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index 4a7b31ab5..fd07228eb 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -391,7 +391,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { boolean allAllowed = Boolean.valueOf(allAllowedS); List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - List denyList = new ArrayList(denyElements.size()); + Set denyList = new HashSet(denyElements.size()); for (Element denyElement : denyElements) { String denyValue = denyElement.getTextTrim(); if (denyValue.isEmpty()) { @@ -402,7 +402,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - List allowList = new ArrayList(allowElements.size()); + Set allowList = new HashSet(allowElements.size()); for (Element allowElement : allowElements) { String allowValue = allowElement.getTextTrim(); if (allowValue.isEmpty()) { diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 5807f27d2..f0efacc8f 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.internal.Privilege; @@ -17,13 +18,16 @@ import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; /** + * TODO {@link PersistenceHandler} may not be freely accessible via {@link PrivilegeContainer} + * * @author rvonburg * */ public interface PersistenceHandler extends PrivilegeContainerObject { public User getUser(String username); - + // public void setUserPassword(String username, String password); + // public void setUserState(String username, UserState state); public void addUser(Certificate certificate, User user); public Role getRole(String roleName); diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index df0fbb9ae..3119acd76 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -19,6 +19,7 @@ import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.model.Certificate; /** @@ -39,12 +40,23 @@ public class TestConfigurationHelper { // initialize container String pwd = System.getProperty("user.dir"); File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); - PrivilegeContainer.getInstance().initialize(privilegeContainerXml); + PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); + privilegeContainer.initialize(privilegeContainerXml); + + PersistenceHandler persistenceHandler = privilegeContainer.getPersistenceHandler(); for (int i = 0; i < 10; i++) { // let's authenticate a session auth("eitch", "1234567890"); } + + // TODO let's add a user + // persistenceHandler.addUser(certificate, user); + + // TODO let's add a role + + // TODO let's add a privilege + } /** diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index d7269cde2..c7f3bbf85 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -161,6 +161,15 @@ public final class Certificate implements Serializable { */ @Override public String toString() { - return "Certificate [sessionId=" + sessionId + ", username=" + username + ", locale=" + locale + "]"; + StringBuilder builder = new StringBuilder(); + builder.append("Certificate [sessionId="); + builder.append(sessionId); + builder.append(", username="); + builder.append(username); + builder.append(", locale="); + builder.append(locale); + builder.append("]"); + return builder.toString(); } + } diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java new file mode 100644 index 000000000..3ac250dda --- /dev/null +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +/** + * @author rvonburg + * + */ +public class PrivilegeRep implements Serializable { + + private static final long serialVersionUID = 1L; + + private String name; + private String policy; + private boolean allAllowed; + private Set denyList; + private Set allowList; + + /** + * @param name + * @param policy + * @param allAllowed + * @param denyList + * @param allowList + */ + public PrivilegeRep(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + this.name = name; + this.policy = policy; + this.allAllowed = allAllowed; + this.denyList = new HashSet(denyList); + this.allowList = new HashSet(allowList); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the policy + */ + public String getPolicy() { + return policy; + } + + /** + * @param policy + * the policy to set + */ + public void setPolicy(String policy) { + this.policy = policy; + } + + /** + * @return the allAllowed + */ + public boolean isAllAllowed() { + return allAllowed; + } + + /** + * @param allAllowed + * the allAllowed to set + */ + public void setAllAllowed(boolean allAllowed) { + this.allAllowed = allAllowed; + } + + /** + * @return the denyList + */ + public Set getDenyList() { + return denyList; + } + + /** + * @param denyList + * the denyList to set + */ + public void setDenyList(Set denyList) { + this.denyList = denyList; + } + + /** + * @return the allowList + */ + public Set getAllowList() { + return allowList; + } + + /** + * @param allowList + * the allowList to set + */ + public void setAllowList(Set allowList) { + this.allowList = allowList; + } +} diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java new file mode 100644 index 000000000..92577e24e --- /dev/null +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.Set; + +/** + * @author rvonburg + * + */ +public class RoleRep implements Serializable { + + private static final long serialVersionUID = 1L; + + public final String name; + public final Set privileges; + + /** + * @param name + * @param privileges + */ + public RoleRep(String name, Set privileges) { + this.name = name; + this.privileges = privileges; + } +} diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java new file mode 100644 index 000000000..4e3575895 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.Locale; +import java.util.Set; + +/** + * @author rvonburg + * + */ +public class UserRep implements Serializable { + + private static final long serialVersionUID = 1L; + + public final String username; + public final String firstname; + public final String surname; + public final UserState userState; + public final Set roles; + public final Locale locale; + + /** + * @param username + * @param firstname + * @param surname + * @param userState + * @param roles + * @param locale + */ + public UserRep(String username, String firstname, String surname, UserState userState, Set roles, + Locale locale) { + this.username = username; + this.firstname = firstname; + this.surname = surname; + this.userState = userState; + this.roles = roles; + this.locale = locale; + } +} diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index ccb7a65c0..cf89a1f26 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -11,7 +11,9 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; -import java.util.List; +import java.util.Set; + +import ch.eitchnet.privilege.model.PrivilegeRep; /** * @author rvonburg @@ -22,20 +24,20 @@ public final class Privilege { private final String name; private final String policy; private final boolean allAllowed; - private final List denyList; - private final List allowList; + private final Set denyList; + private final Set allowList; /** * @param allAllowed * @param denyList * @param allowList */ - public Privilege(String name, String policy, boolean allAllowed, List denyList, List allowList) { + public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = name; this.policy = policy; this.allAllowed = allAllowed; - this.denyList = Collections.unmodifiableList(denyList); - this.allowList = Collections.unmodifiableList(allowList); + this.denyList = Collections.unmodifiableSet(denyList); + this.allowList = Collections.unmodifiableSet(allowList); } /** @@ -62,15 +64,93 @@ public final class Privilege { /** * @return the allowList */ - public List getAllowList() { + public Set getAllowList() { return allowList; } /** * @return the denyList */ - public List getDenyList() { + public Set getDenyList() { return denyList; } + /** + * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + */ + public PrivilegeRep asPrivilegeRep() { + return new PrivilegeRep(name, policy, allAllowed, denyList, allowList); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Privilege [name="); + builder.append(name); + builder.append(", policy="); + builder.append(policy); + builder.append(", allAllowed="); + builder.append(allAllowed); + builder.append(", denyList="); + builder.append(denyList); + builder.append(", allowList="); + builder.append(allowList); + builder.append("]"); + return builder.toString(); + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (allAllowed ? 1231 : 1237); + result = prime * result + ((allowList == null) ? 0 : allowList.hashCode()); + result = prime * result + ((denyList == null) ? 0 : denyList.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((policy == null) ? 0 : policy.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Privilege other = (Privilege) obj; + if (allAllowed != other.allAllowed) + return false; + if (allowList == null) { + if (other.allowList != null) + return false; + } else if (!allowList.equals(other.allowList)) + return false; + if (denyList == null) { + if (other.denyList != null) + return false; + } else if (!denyList.equals(other.denyList)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (policy == null) { + if (other.policy != null) + return false; + } else if (!policy.equals(other.policy)) + return false; + return true; + } } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 0dc6322d0..17705febf 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -13,6 +13,8 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; import java.util.Set; +import ch.eitchnet.privilege.model.RoleRep; + /** * @author rvonburg * @@ -53,4 +55,63 @@ public final class Role { public boolean hasPrivilege(String key) { return privileges.contains(key); } + + /** + * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients + */ + public RoleRep asRoleRep() { + return new RoleRep(name, privileges); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Role [name="); + builder.append(name); + builder.append(", privileges="); + builder.append(privileges); + builder.append("]"); + return builder.toString(); + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((privileges == null) ? 0 : privileges.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Role other = (Role) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (privileges == null) { + if (other.privileges != null) + return false; + } else if (!privileges.equals(other.privileges)) + return false; + return true; + } + } diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index cdcd1cda5..4d4ff4355 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -125,6 +125,15 @@ public final class Session { */ @Override public String toString() { - return "Session [username=" + username + ", sessionId=" + sessionId + ", loginTime=" + loginTime + "]"; + StringBuilder builder = new StringBuilder(); + builder.append("Session [sessionId="); + builder.append(sessionId); + builder.append(", username="); + builder.append(username); + builder.append(", loginTime="); + builder.append(loginTime); + builder.append("]"); + return builder.toString(); } + } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 9efc5bb2d..1ff0d208f 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -15,6 +15,7 @@ import java.util.Locale; import java.util.Set; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; /** @@ -112,6 +113,35 @@ public final class User { return locale; } + /** + * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients + */ + public UserRep asUserRep() { + return new UserRep(username, firstname, surname, userState, roles, locale); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("User [username="); + builder.append(username); + builder.append(", firstname="); + builder.append(firstname); + builder.append(", surname="); + builder.append(surname); + builder.append(", locale="); + builder.append(locale); + builder.append(", userState="); + builder.append(userState); + builder.append(", roles="); + builder.append(roles); + builder.append("]"); + return builder.toString(); + } + /** * @return a new {@link User} object which is authenticated on the current Java Virtual Machine */ From 672768e0b246215133d68f91afcc86fd5d01d4fa Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 5 Jun 2010 21:34:02 +0000 Subject: [PATCH 021/457] --- src/ch/eitchnet/privilege/base/PrivilegeContainer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 42d17de2d..608593d11 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -24,6 +24,8 @@ import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** + * TODO make persistence handle not accessible + * * @author rvonburg */ public class PrivilegeContainer { From c653edd6797457f01fd08ba3dcab6fe0302558a9 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 6 Jun 2010 19:11:36 +0000 Subject: [PATCH 022/457] --- .../privilege/base/PrivilegeContainer.java | 35 ++++++++-------- .../eitchnet/privilege/base/XmlConstants.java | 1 + .../handler/DefaultPersistenceHandler.java | 13 +++--- .../handler/DefaultPolicyHandler.java | 1 + .../handler/DefaultSessionHandler.java | 9 ++-- .../handler/ModificationHandler.java | 41 +++++++++++++++++++ .../privilege/handler/PersistenceHandler.java | 9 ++-- 7 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 src/ch/eitchnet/privilege/handler/ModificationHandler.java diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 608593d11..efc490dd7 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -16,6 +16,7 @@ import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.handler.ModificationHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; @@ -46,9 +47,7 @@ public class PrivilegeContainer { private SessionHandler sessionHandler; private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; - private PersistenceHandler persistenceHandler; - - private String basePath; + private ModificationHandler modificationHandler; public static PrivilegeContainer getInstance() { return instance; @@ -83,17 +82,10 @@ public class PrivilegeContainer { } /** - * @return the persistenceHandler + * @return the modificationHandler */ - public PersistenceHandler getPersistenceHandler() { - return persistenceHandler; - } - - /** - * @return the basePath - */ - public String getBasePath() { - return basePath; + public ModificationHandler getModificationHandler() { + return modificationHandler; } public void initialize(File privilegeContainerXml) { @@ -105,7 +97,7 @@ public class PrivilegeContainer { } // set base path from privilege container xml - basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); + String basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); // parse container xml file to XML document Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); @@ -129,9 +121,14 @@ public class PrivilegeContainer { Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); + + // instantiate modification handler + Element modificationHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODIFICATION); + String modificationHandlerClassName = modificationHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + ModificationHandler modificationHandler = ClassHelper.instantiateClass(policyHandlerClassName); try { - persistenceHandler.initialize(persistenceHandlerElement); + persistenceHandler.initialize(basePath, persistenceHandlerElement); } catch (Exception e) { logger.error(e, e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement @@ -156,9 +153,15 @@ public class PrivilegeContainer { logger.error(e, e); throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); } + try { + modificationHandler.initialize(modificationHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName + " could not be initialized"); + } // keep references to the handlers - this.persistenceHandler = persistenceHandler; + this.modificationHandler = modificationHandler; this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; this.policyHandler = policyHandler; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 0377adcb7..cc7904f3e 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,6 +19,7 @@ public class XmlConstants { public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; + public static final String XML_HANDLER_MODIFICATION = "ModificationHandler"; public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index fd07228eb..a49094302 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -51,14 +51,15 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private boolean roleMapDirty; private boolean privilegeMapDirty; + private PersistenceHandler persistenceHandler; private Map parameterMap; /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addPrivilege(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.Privilege) */ @Override - public void addPrivilege(Certificate certificate, Privilege privilege) { + public void addOrReplacePrivilege(Certificate certificate, Privilege privilege) { // validate who is doing this PrivilegeHelper.isUserPrivilegeAdmin(certificate); @@ -68,11 +69,11 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.Role) */ @Override - public void addRole(Certificate certificate, Role role) { + public void addOrReplaceRole(Certificate certificate, Role role) { // validate who is doing this PrivilegeHelper.isUserPrivilegeAdmin(certificate); @@ -82,11 +83,11 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.User) */ @Override - public void addUser(Certificate certificate, User user) { + public void addOrReplaceUser(Certificate certificate, User user) { // validate who is doing this PrivilegeHelper.isUserPrivilegeAdmin(certificate); diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index a72f5c996..d789197f9 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -33,6 +33,7 @@ import ch.eitchnet.privilege.policy.RestrictionPolicy; */ public class DefaultPolicyHandler implements PolicyHandler { + private PersistenceHandler persistenceHandler; private Map> policyMap; /** diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index f63492ffb..c074e6154 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -36,6 +36,7 @@ public class DefaultSessionHandler implements SessionHandler { private static long lastSessionId; + private PersistenceHandler persistenceHandler; private Map sessionMap; /** @@ -63,7 +64,6 @@ public class DefaultSessionHandler implements SessionHandler { throw new PrivilegeException("Restrictable may not be null!"); PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); - PersistenceHandler persistenceHandler = privilegeContainer.getPersistenceHandler(); // get user object User user = persistenceHandler.getUser(certificate.getUsername()); @@ -80,7 +80,7 @@ public class DefaultSessionHandler implements SessionHandler { PolicyHandler policyHandler = privilegeContainer.getPolicyHandler(); for (String roleName : user.getRoles()) { - Role role = privilegeContainer.getPersistenceHandler().getRole(roleName); + Role role = persistenceHandler.getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -124,8 +124,7 @@ public class DefaultSessionHandler implements SessionHandler { + certificate.getSessionId()); // get user object - User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser( - certificateSessionPair.session.getUsername()); + User user = persistenceHandler.getUser(certificateSessionPair.session.getUsername()); // if user exists, then certificate is valid if (user == null) { @@ -157,7 +156,7 @@ public class DefaultSessionHandler implements SessionHandler { String passwordHash = encryptionHandler.convertToHash(password); // get user object - User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser(username); + User user = persistenceHandler.getUser(username); // no user means no authentication if (user == null) throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); diff --git a/src/ch/eitchnet/privilege/handler/ModificationHandler.java b/src/ch/eitchnet/privilege/handler/ModificationHandler.java new file mode 100644 index 000000000..b5574c734 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/ModificationHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; + +/** + * @author rvonburg + * + */ +public interface ModificationHandler extends PrivilegeContainerObject { + + public void addOrReplaceUser(Certificate certificate, UserRep userRep); + + public void setUserPassword(Certificate certificate, String username, String password); + + public void setUserState(Certificate certificate, String username, UserState state); + + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); + + public void addRoleToUser(Certificate certificate, String username, String role); + + public void addOrReplacePrivilege(Certificate certificate, Privilege privilege); + + public void addPrivilegeToRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep); + +} diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index f0efacc8f..a8bb169c0 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -26,17 +26,16 @@ import ch.eitchnet.privilege.model.internal.User; public interface PersistenceHandler extends PrivilegeContainerObject { public User getUser(String username); - // public void setUserPassword(String username, String password); - // public void setUserState(String username, UserState state); - public void addUser(Certificate certificate, User user); + + public void addOrReplaceUser(Certificate certificate, User user); public Role getRole(String roleName); - public void addRole(Certificate certificate, Role role); + public void addOrReplaceRole(Certificate certificate, Role role); public Privilege getPrivilege(String privilegeName); - public void addPrivilege(Certificate certificate, Privilege privilege); + public void addOrReplacePrivilege(Certificate certificate, Privilege privilege); public void persist(Certificate certificate); } From 9daaccd31865353b1e660bac912c5ee5f2e37329 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 20 Jun 2010 20:11:53 +0000 Subject: [PATCH 023/457] --- .../privilege/base/PrivilegeContainer.java | 36 +- .../eitchnet/privilege/base/XmlConstants.java | 2 +- .../handler/DefaultModelHandler.java | 345 ++++++++++++++++++ .../handler/DefaultPersistenceHandler.java | 160 ++++---- .../handler/DefaultSessionHandler.java | 12 +- .../privilege/handler/ModelHandler.java | 77 ++++ .../handler/ModificationHandler.java | 41 --- .../privilege/handler/PersistenceHandler.java | 14 +- .../privilege/helper/PrivilegeHelper.java | 4 +- .../helper/TestConfigurationHelper.java | 4 +- .../privilege/model/internal/User.java | 16 +- .../privilege/policy/DefaultRestriction.java | 2 +- 12 files changed, 577 insertions(+), 136 deletions(-) create mode 100644 src/ch/eitchnet/privilege/handler/DefaultModelHandler.java create mode 100644 src/ch/eitchnet/privilege/handler/ModelHandler.java delete mode 100644 src/ch/eitchnet/privilege/handler/ModificationHandler.java diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index efc490dd7..1bece4bb5 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -16,7 +16,7 @@ import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.ModificationHandler; +import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; @@ -47,7 +47,9 @@ public class PrivilegeContainer { private SessionHandler sessionHandler; private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; - private ModificationHandler modificationHandler; + private ModelHandler modelHandler; + + private String basePath; public static PrivilegeContainer getInstance() { return instance; @@ -82,10 +84,17 @@ public class PrivilegeContainer { } /** - * @return the modificationHandler + * @return the modelHandler */ - public ModificationHandler getModificationHandler() { - return modificationHandler; + public ModelHandler getModelHandler() { + return modelHandler; + } + + /** + * @return the basePath + */ + public String getBasePath() { + return basePath; } public void initialize(File privilegeContainerXml) { @@ -97,7 +106,7 @@ public class PrivilegeContainer { } // set base path from privilege container xml - String basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); + basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); // parse container xml file to XML document Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); @@ -121,14 +130,14 @@ public class PrivilegeContainer { Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); - + // instantiate modification handler - Element modificationHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODIFICATION); + Element modificationHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); String modificationHandlerClassName = modificationHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - ModificationHandler modificationHandler = ClassHelper.instantiateClass(policyHandlerClassName); + ModelHandler modelHandler = ClassHelper.instantiateClass(modificationHandlerClassName); try { - persistenceHandler.initialize(basePath, persistenceHandlerElement); + persistenceHandler.initialize(persistenceHandlerElement); } catch (Exception e) { logger.error(e, e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement @@ -154,14 +163,15 @@ public class PrivilegeContainer { throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); } try { - modificationHandler.initialize(modificationHandlerElement); + modelHandler.initialize(modificationHandlerElement); } catch (Exception e) { logger.error(e, e); - throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName + " could not be initialized"); + throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName + + " could not be initialized"); } // keep references to the handlers - this.modificationHandler = modificationHandler; + this.modelHandler = modelHandler; this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; this.policyHandler = policyHandler; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index cc7904f3e..95566e5bb 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,7 +19,7 @@ public class XmlConstants { public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; - public static final String XML_HANDLER_MODIFICATION = "ModificationHandler"; + public static final String XML_HANDLER_MODEL = "ModificationHandler"; public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java new file mode 100644 index 000000000..f4bfacc5c --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.util.List; +import java.util.Locale; + +import org.dom4j.Element; + +import ch.eitchnet.privilege.helper.PrivilegeHelper; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public class DefaultModelHandler implements ModelHandler { + + private PersistenceHandler persistenceHandler; + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#setPersistenceHandler(ch.eitchnet.privilege.handler.PersistenceHandler) + */ + public void setPersistenceHandler(PersistenceHandler persistenceHandler) { + this.persistenceHandler = persistenceHandler; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.PrivilegeRep) + */ + @Override + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.RoleRep) + */ + @Override + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.UserRep) + */ + @Override + public void addOrReplaceUser(Certificate certificate, UserRep userRep) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void addRoleToUser(Certificate certificate, String username, String rolename) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#persist(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public boolean persist(Certificate certificate) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + return false; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removeRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public RoleRep removeRole(Certificate certificate, String roleName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void removeRoleFromUser(Certificate certificate, String username, String rolename) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removeUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public UserRep removeUser(Certificate certificate, String username) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, boolean) + */ + @Override + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.List) + */ + @Override + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, List allowList) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.List) + */ + @Override + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, List denyList) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.Locale) + */ + @Override + public void setUserLocaleState(Certificate certificate, String username, Locale locale) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserNamePassword(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void setUserNamePassword(Certificate certificate, String username, String firstname, String surname) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void setUserPassword(Certificate certificate, String username, String password) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserState(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, ch.eitchnet.privilege.model.UserState) + */ + @Override + public void setUserState(Certificate certificate, String username, UserState state) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ + @Override + public void initialize(Element element) { + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#getPrivilege(java.lang.String) + */ + @Override + public Privilege getPrivilege(String privilegeName) { + return persistenceHandler.getPrivilege(privilegeName); + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#getRole(java.lang.String) + */ + @Override + public Role getRole(String roleName) { + return persistenceHandler.getRole(roleName); + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#getUser(java.lang.String) + */ + @Override + public User getUser(String username) { + return persistenceHandler.getUser(username); + } + +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index a49094302..820a7b1a9 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -26,7 +26,6 @@ import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ConfigurationHelper; -import ch.eitchnet.privilege.helper.PrivilegeHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -47,11 +46,13 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private Map roleMap; private Map privilegeMap; + private long usersFileDate; private boolean userMapDirty; + private long rolesFileDate; private boolean roleMapDirty; + private long privilegesFileDate; private boolean privilegeMapDirty; - private PersistenceHandler persistenceHandler; private Map parameterMap; /** @@ -59,43 +60,61 @@ public class DefaultPersistenceHandler implements PersistenceHandler { * ch.eitchnet.privilege.model.internal.Privilege) */ @Override - public void addOrReplacePrivilege(Certificate certificate, Privilege privilege) { - - // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); - + public void addOrReplacePrivilege(Privilege privilege) { privilegeMap.put(privilege.getName(), privilege); privilegeMapDirty = true; } + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#removePrivilege(java.lang.String) + */ + @Override + public Privilege removePrivilege(String privilegeName) { + Privilege privilege = privilegeMap.remove(privilegeName); + privilegeMapDirty = privilege != null; + return privilege; + } + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.Role) */ @Override - public void addOrReplaceRole(Certificate certificate, Role role) { - - // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); - + public void addOrReplaceRole(Role role) { roleMap.put(role.getName(), role); roleMapDirty = true; } + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeRole(java.lang.String) + */ + @Override + public Role removeRole(String roleName) { + Role role = roleMap.remove(roleName); + roleMapDirty = role != null; + return role; + } + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.User) */ @Override - public void addOrReplaceUser(Certificate certificate, User user) { - - // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); - + public void addOrReplaceUser(User user) { userMap.put(user.getUsername(), user); userMapDirty = true; } + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeUser(java.lang.String) + */ + @Override + public User removeUser(String username) { + User user = userMap.remove(username); + userMapDirty = user != null; + return user; + } + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPrivilege(java.lang.String) */ @@ -121,44 +140,50 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() + * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist(ch.eitchnet.privilege.model.Certificate) */ @Override - public void persist(Certificate certificate) { - - // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + public boolean persist(Certificate certificate) { // USERS - if (!userMapDirty) { - logger.warn("No users unpersisted."); + // get users file name + String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + } + // get users file + File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == usersFileDate; + if (!userMapDirty && usersFileUnchanged) { + logger.warn("No users unpersisted and user file unchanged on file system"); } else { logger.info("Persisting users..."); // build XML DOM of users - List users = toDomUsers(); + List users = toDomUsers(certificate); Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_USERS); for (Element userElement : users) { rootElement.add(userElement); } - // get users file name - String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); - } - - // get users file - File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); - // write DOM to file XmlHelper.writeDocument(rootElement, usersFile); + userMapDirty = true; } // ROLES - if (!roleMapDirty) { - logger.warn("No roles unpersisted."); + // get roles file name + String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); + } + // get roles file + File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == rolesFileDate; + if (!roleMapDirty && rolesFileUnchanged) { + logger.warn("No roles unpersisted and roles file unchanged on file system"); } else { logger.info("Persisting roles..."); @@ -169,23 +194,24 @@ public class DefaultPersistenceHandler implements PersistenceHandler { rootElement.add(roleElement); } - // get roles file name - String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); - } - - // get roles file - File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); - // write DOM to file XmlHelper.writeDocument(rootElement, rolesFile); + roleMapDirty = true; } // PRIVILEGES - if (!privilegeMapDirty) { - logger.warn("No privileges unpersisted."); + // get privileges file name + String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); + if (privilegesFileName == null || privilegesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); + } + // get privileges file + File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + boolean privilegesFileUnchanged = privilegesFile.exists() + && privilegesFile.lastModified() == privilegesFileDate; + if (!privilegeMapDirty && privilegesFileUnchanged) { + logger.warn("No privileges unpersisted and privileges file unchanged on file system"); } else { logger.info("Persisting privileges..."); @@ -196,23 +222,26 @@ public class DefaultPersistenceHandler implements PersistenceHandler { rootElement.add(privilegeElement); } - // get privileges file name - String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); - if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); - } - - // get privileges file - File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); - // write DOM to file XmlHelper.writeDocument(rootElement, privilegesFile); + privilegeMapDirty = true; } - userMapDirty = false; - roleMapDirty = false; - privilegeMapDirty = false; + // reset dirty states and return if something was dirty, false otherwise + if (userMapDirty || roleMapDirty || privilegeMapDirty) { + userMapDirty = false; + roleMapDirty = false; + privilegeMapDirty = false; + + return true; + + } else { + userMapDirty = false; + roleMapDirty = false; + privilegeMapDirty = false; + + return false; + } } /** @@ -249,6 +278,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // read roles readRoles(rolesRootElement); + rolesFileDate = rolesFile.lastModified(); // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); @@ -270,6 +300,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // read users readUsers(usersRootElement); + usersFileDate = usersFile.lastModified(); // get privileges file name String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); @@ -291,6 +322,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // read privileges readPrivileges(privilegesRootElement); + privilegesFileDate = privilegesFile.lastModified(); userMapDirty = false; roleMapDirty = false; @@ -487,7 +519,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { return rolesAsElements; } - private List toDomUsers() { + private List toDomUsers(Certificate certificate) { List usersAsElements = new ArrayList(userMap.size()); @@ -500,7 +532,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword(certificate)); // add first name element Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index c074e6154..4e38fc69a 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -36,7 +36,6 @@ public class DefaultSessionHandler implements SessionHandler { private static long lastSessionId; - private PersistenceHandler persistenceHandler; private Map sessionMap; /** @@ -66,7 +65,7 @@ public class DefaultSessionHandler implements SessionHandler { PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); // get user object - User user = persistenceHandler.getUser(certificate.getUsername()); + User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); @@ -80,7 +79,7 @@ public class DefaultSessionHandler implements SessionHandler { PolicyHandler policyHandler = privilegeContainer.getPolicyHandler(); for (String roleName : user.getRoles()) { - Role role = persistenceHandler.getRole(roleName); + Role role = PrivilegeContainer.getInstance().getModelHandler().getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -124,7 +123,8 @@ public class DefaultSessionHandler implements SessionHandler { + certificate.getSessionId()); // get user object - User user = persistenceHandler.getUser(certificateSessionPair.session.getUsername()); + User user = PrivilegeContainer.getInstance().getModelHandler().getUser( + certificateSessionPair.session.getUsername()); // if user exists, then certificate is valid if (user == null) { @@ -156,13 +156,13 @@ public class DefaultSessionHandler implements SessionHandler { String passwordHash = encryptionHandler.convertToHash(password); // get user object - User user = persistenceHandler.getUser(username); + User user = PrivilegeContainer.getInstance().getModelHandler().getUser(username); // no user means no authentication if (user == null) throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); // validate password - if (!user.getPassword().equals(passwordHash)) + if (!user.isPassword(passwordHash)) throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); // validate if user is allowed to login diff --git a/src/ch/eitchnet/privilege/handler/ModelHandler.java b/src/ch/eitchnet/privilege/handler/ModelHandler.java new file mode 100644 index 000000000..c464ebb2f --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/ModelHandler.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.util.List; +import java.util.Locale; + +import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public interface ModelHandler extends PrivilegeContainerObject { + + public void setPersistenceHandler(PersistenceHandler persistenceHandler); + + public User getUser(String username); + + public void addOrReplaceUser(Certificate certificate, UserRep userRep); + + public UserRep removeUser(Certificate certificate, String username); + + public void setUserPassword(Certificate certificate, String username, String password); + + public void setUserNamePassword(Certificate certificate, String username, String firstname, String surname); + + public void setUserState(Certificate certificate, String username, UserState state); + + public void setUserLocaleState(Certificate certificate, String username, Locale locale); + + public void addRoleToUser(Certificate certificate, String username, String rolename); + + public void removeRoleFromUser(Certificate certificate, String username, String rolename); + + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); + + public Role getRole(String roleName); + + public RoleRep removeRole(Certificate certificate, String roleName); + + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); + + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); + + public Privilege getPrivilege(String privilegeName); + + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); + + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); + + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName); + + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); + + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, List denyList); + + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, List allowList); + + public boolean persist(Certificate certificate); +} diff --git a/src/ch/eitchnet/privilege/handler/ModificationHandler.java b/src/ch/eitchnet/privilege/handler/ModificationHandler.java deleted file mode 100644 index b5574c734..000000000 --- a/src/ch/eitchnet/privilege/handler/ModificationHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import ch.eitchnet.privilege.base.PrivilegeContainerObject; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.RoleRep; -import ch.eitchnet.privilege.model.UserRep; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; - -/** - * @author rvonburg - * - */ -public interface ModificationHandler extends PrivilegeContainerObject { - - public void addOrReplaceUser(Certificate certificate, UserRep userRep); - - public void setUserPassword(Certificate certificate, String username, String password); - - public void setUserState(Certificate certificate, String username, UserState state); - - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); - - public void addRoleToUser(Certificate certificate, String username, String role); - - public void addOrReplacePrivilege(Certificate certificate, Privilege privilege); - - public void addPrivilegeToRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep); - -} diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index a8bb169c0..ded4736ed 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -27,15 +27,21 @@ public interface PersistenceHandler extends PrivilegeContainerObject { public User getUser(String username); - public void addOrReplaceUser(Certificate certificate, User user); + public void addOrReplaceUser(User user); + + public User removeUser(String username); public Role getRole(String roleName); - public void addOrReplaceRole(Certificate certificate, Role role); + public void addOrReplaceRole(Role role); + + public Role removeRole(String roleName); public Privilege getPrivilege(String privilegeName); - public void addOrReplacePrivilege(Certificate certificate, Privilege privilege); + public void addOrReplacePrivilege(Privilege privilege); - public void persist(Certificate certificate); + public Privilege removePrivilege(String privilegeName); + + public boolean persist(Certificate certificate); } diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java index 720df2b4d..fff3c1114 100644 --- a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java +++ b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java @@ -18,7 +18,7 @@ import ch.eitchnet.privilege.model.internal.User; /** * @author rvonburg - * + * */ public class PrivilegeHelper { @@ -29,7 +29,7 @@ public class PrivilegeHelper { } // get user object - User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser(certificate.getUsername()); + User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index 3119acd76..41662774c 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -19,7 +19,7 @@ import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.handler.PersistenceHandler; +import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.model.Certificate; /** @@ -43,7 +43,7 @@ public class TestConfigurationHelper { PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); privilegeContainer.initialize(privilegeContainerXml); - PersistenceHandler persistenceHandler = privilegeContainer.getPersistenceHandler(); + ModelHandler modelHandler = privilegeContainer.getModelHandler(); for (int i = 0; i < 10; i++) { // let's authenticate a session diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 1ff0d208f..3edc0575a 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -14,7 +14,9 @@ import java.util.Collections; import java.util.Locale; import java.util.Set; +import ch.eitchnet.privilege.helper.PrivilegeHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -64,8 +66,18 @@ public final class User { /** * @return the password */ - public String getPassword() { - return password; + public String getPassword(Certificate certificate) { + if (PrivilegeHelper.isUserPrivilegeAdmin(certificate)) + return password; + else + return null; + } + + /** + * @return the password + */ + public boolean isPassword(String password) { + return this.password.equals(password); } /** diff --git a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java index 77e7ba980..d0d27d1e8 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java +++ b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java @@ -40,7 +40,7 @@ public class DefaultRestriction implements RestrictionPolicy { } // get restriction object for users role - Privilege privilege = PrivilegeContainer.getInstance().getPersistenceHandler().getPrivilege(restrictionKey); + Privilege privilege = PrivilegeContainer.getInstance().getModelHandler().getPrivilege(restrictionKey); // no restriction object means no privilege // TODO should default deny/allow policy be configurable? From bfb7082a47f1411cec1afb240ae2f3ee8ef2c753 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 20 Jun 2010 20:12:19 +0000 Subject: [PATCH 024/457] --- src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index d789197f9..a72f5c996 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -33,7 +33,6 @@ import ch.eitchnet.privilege.policy.RestrictionPolicy; */ public class DefaultPolicyHandler implements PolicyHandler { - private PersistenceHandler persistenceHandler; private Map> policyMap; /** From f491dd7293311600fb83ec62a2231f0060f04aff Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 20 Jun 2010 20:13:58 +0000 Subject: [PATCH 025/457] --- config/PrivilegeContainer.xml | 1 + src/ch/eitchnet/privilege/base/PrivilegeContainer.java | 1 + src/ch/eitchnet/privilege/base/XmlConstants.java | 2 +- src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index dad421977..0c64f7a94 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -9,6 +9,7 @@ + diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 1bece4bb5..582fa2579 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -164,6 +164,7 @@ public class PrivilegeContainer { } try { modelHandler.initialize(modificationHandlerElement); + modelHandler.setPersistenceHandler(persistenceHandler); } catch (Exception e) { logger.error(e, e); throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 95566e5bb..fbf3e9dda 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,7 +19,7 @@ public class XmlConstants { public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; - public static final String XML_HANDLER_MODEL = "ModificationHandler"; + public static final String XML_HANDLER_MODEL = "ModelHandler"; public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index 41662774c..0d987bed0 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -43,7 +43,7 @@ public class TestConfigurationHelper { PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); privilegeContainer.initialize(privilegeContainerXml); - ModelHandler modelHandler = privilegeContainer.getModelHandler(); + // ModelHandler modelHandler = privilegeContainer.getModelHandler(); for (int i = 0; i < 10; i++) { // let's authenticate a session From b523f680f9ad59047534410d72642292912752b9 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 21 Jun 2010 21:45:55 +0000 Subject: [PATCH 026/457] - implemented default model handler - added JUnit 4 test case --- .classpath | 2 + .../privilege/base/PrivilegeContainer.java | 2 - .../handler/DefaultModelHandler.java | 402 +++++++++++++++--- .../handler/DefaultPersistenceHandler.java | 4 +- .../privilege/handler/ModelHandler.java | 14 +- .../privilege/handler/PersistenceHandler.java | 3 - .../privilege/helper/PrivilegeHelper.java | 6 +- .../helper/TestConfigurationHelper.java | 16 +- src/ch/eitchnet/privilege/model/RoleRep.java | 34 +- src/ch/eitchnet/privilege/model/UserRep.java | 96 ++++- .../privilege/model/internal/User.java | 43 +- .../privilege/test/PrivilegeTest.java | 136 ++++++ 12 files changed, 640 insertions(+), 118 deletions(-) create mode 100644 test/ch/eitchnet/privilege/test/PrivilegeTest.java diff --git a/.classpath b/.classpath index bba4c498c..f52b649dc 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,10 @@ + + diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 582fa2579..48ca8f866 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -25,8 +25,6 @@ import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** - * TODO make persistence handle not accessible - * * @author rvonburg */ public class PrivilegeContainer { diff --git a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java index f4bfacc5c..9dfd0b806 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java @@ -10,12 +10,16 @@ package ch.eitchnet.privilege.handler; -import java.util.List; +import java.util.HashSet; import java.util.Locale; +import java.util.Set; +import org.apache.log4j.Logger; import org.dom4j.Element; +import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.helper.PrivilegeHelper; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.RoleRep; @@ -31,6 +35,8 @@ import ch.eitchnet.privilege.model.internal.User; */ public class DefaultModelHandler implements ModelHandler { + private static final Logger logger = Logger.getLogger(DefaultModelHandler.class); + private PersistenceHandler persistenceHandler; /** @@ -48,10 +54,18 @@ public class DefaultModelHandler implements ModelHandler { public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // create a new privilege + Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep + .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); + // delegate to persistence handler + persistenceHandler.addOrReplacePrivilege(privilege); } /** @@ -62,24 +76,46 @@ public class DefaultModelHandler implements ModelHandler { public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // create new role + Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); + // delegate to persistence handler + persistenceHandler.addOrReplaceRole(role); } /** * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep) + * ch.eitchnet.privilege.model.UserRep, java.lang.String) */ @Override - public void addOrReplaceUser(Certificate certificate, UserRep userRep) { + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // hash password + String passwordHash; + if (password == null) + passwordHash = null; + else + passwordHash = PrivilegeContainer.getInstance().getEncryptionHandler().convertToHash(password); + // create new user + User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep + .getUserState(), userRep.getRoles(), userRep.getLocale()); + + // delegate to persistence handler + persistenceHandler.addOrReplaceUser(user); } /** @@ -90,10 +126,39 @@ public class DefaultModelHandler implements ModelHandler { public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get role + Role role = getRole(roleName); + if (role == null) { + throw new PrivilegeException("Role " + roleName + " does not exist!"); + } + // ignore if role already has this privilege + Set currentPrivileges = role.getPrivileges(); + if (currentPrivileges.contains(roleName)) { + logger.error("Role " + roleName + " already has privilege " + privilegeName); + return; + } + + // validate that privilege exists + if (getPrivilege(privilegeName) == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist and can not be added to role " + + roleName); + } + + // create new role with the additional privilege + Set newPrivileges = new HashSet(currentPrivileges); + newPrivileges.add(roleName); + + Role newRole = new Role(role.getName(), newPrivileges); + + // delegate role replacement to persistence handler + persistenceHandler.addOrReplaceRole(newRole); } /** @@ -101,13 +166,42 @@ public class DefaultModelHandler implements ModelHandler { * java.lang.String, java.lang.String) */ @Override - public void addRoleToUser(Certificate certificate, String username, String rolename) { + public void addRoleToUser(Certificate certificate, String username, String roleName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get user + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // ignore if user already has role + Set currentRoles = user.getRoles(); + if (currentRoles.contains(roleName)) { + logger.error("User " + username + " already has role " + roleName); + return; + } + + // validate that role exists + if (getRole(roleName) == null) { + throw new PrivilegeException("Role " + roleName + " doest not exist!"); + } + + // create new user + Set newRoles = new HashSet(currentRoles); + currentRoles.add(roleName); + + User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user + .getSurname(), user.getState(), newRoles, user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -117,11 +211,13 @@ public class DefaultModelHandler implements ModelHandler { public boolean persist(Certificate certificate) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return false; + } - // TODO Auto-generated method stub - - return false; + return persistenceHandler.persist(certificate); } /** @@ -132,11 +228,20 @@ public class DefaultModelHandler implements ModelHandler { public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return null; + } - // TODO Auto-generated method stub + // delegate privilege removal to persistence handler + Privilege removedPrivilege = persistenceHandler.removePrivilege(privilegeName); - return null; + // return privilege rep if it was removed + if (removedPrivilege != null) + return removedPrivilege.asPrivilegeRep(); + else + return null; } /** @@ -147,10 +252,32 @@ public class DefaultModelHandler implements ModelHandler { public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get role + Role role = getRole(roleName); + if (role == null) { + throw new PrivilegeException("Role " + roleName + " does not exist!"); + } + // ignore if role does not have privilege + Set currentPrivileges = role.getPrivileges(); + if (!currentPrivileges.contains(privilegeName)) { + logger.error("Role " + roleName + " doest not have privilege " + privilegeName); + return; + } + + // create new role + Set newPrivileges = new HashSet(currentPrivileges); + newPrivileges.remove(privilegeName); + Role newRole = new Role(role.getName(), newPrivileges); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceRole(newRole); } /** @@ -161,11 +288,20 @@ public class DefaultModelHandler implements ModelHandler { public RoleRep removeRole(Certificate certificate, String roleName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return null; + } - // TODO Auto-generated method stub + // delegate role removal to persistence handler + Role removedRole = persistenceHandler.removeRole(roleName); - return null; + // return role rep if it was removed + if (removedRole != null) + return removedRole.asRoleRep(); + else + return null; } /** @@ -173,13 +309,36 @@ public class DefaultModelHandler implements ModelHandler { * java.lang.String, java.lang.String) */ @Override - public void removeRoleFromUser(Certificate certificate, String username, String rolename) { + public void removeRoleFromUser(Certificate certificate, String username, String roleName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // ignore if user does not have role + Set currentRoles = user.getRoles(); + if (!currentRoles.contains(roleName)) { + logger.error("User " + user + " does not have role " + roleName); + return; + } + + // create new user + Set newRoles = new HashSet(currentRoles); + newRoles.remove(roleName); + User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user + .getSurname(), user.getState(), newRoles, user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -190,11 +349,20 @@ public class DefaultModelHandler implements ModelHandler { public UserRep removeUser(Certificate certificate, String username) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return null; + } - // TODO Auto-generated method stub + // delegate user removal to persistence handler + User removedUser = persistenceHandler.removeUser(username); - return null; + // return user rep if it was removed + if (removedUser != null) + return removedUser.asUserRep(); + else + return null; } /** @@ -205,38 +373,87 @@ public class DefaultModelHandler implements ModelHandler { public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get Privilege + Privilege privilege = getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + // ignore if privilege is already set to argument + if (privilege.isAllAllowed() == allAllowed) { + logger.error("Privilege " + privilegeName + " is already set to " + + (allAllowed ? "all allowed" : "not all allowed")); + return; + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, privilege + .getDenyList(), privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.List) + * java.lang.String, java.util.Set) */ @Override - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, List allowList) { + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get Privilege + Privilege privilege = getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), + privilege.getDenyList(), allowList); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.List) + * java.lang.String, java.util.Set) */ @Override - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, List denyList) { + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get Privilege + Privilege privilege = getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), + denyList, privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -247,10 +464,24 @@ public class DefaultModelHandler implements ModelHandler { public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get Privilege + Privilege privilege = getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), privilege + .getDenyList(), privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -261,24 +492,52 @@ public class DefaultModelHandler implements ModelHandler { public void setUserLocaleState(Certificate certificate, String username, Locale locale) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // create new user + User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user + .getSurname(), user.getState(), user.getRoles(), locale); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserNamePassword(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserName(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String, java.lang.String) */ @Override - public void setUserNamePassword(Certificate certificate, String username, String firstname, String surname) { + public void setUserName(Certificate certificate, String username, String firstname, String surname) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // create new user + User newUser = new User(user.getUsername(), user.getPassword(certificate), firstname, surname, user.getState(), + user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -289,10 +548,27 @@ public class DefaultModelHandler implements ModelHandler { public void setUserPassword(Certificate certificate, String username, String password) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // hash password + String passwordHash = PrivilegeContainer.getInstance().getEncryptionHandler().convertToHash(password); + + // create new user + User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user + .getState(), user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -303,10 +579,24 @@ public class DefaultModelHandler implements ModelHandler { public void setUserState(Certificate certificate, String username, UserState state) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // create new user + User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user + .getSurname(), state, user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -314,8 +604,7 @@ public class DefaultModelHandler implements ModelHandler { */ @Override public void initialize(Element element) { - // TODO Auto-generated method stub - + // nothing to initialize } /** @@ -341,5 +630,4 @@ public class DefaultModelHandler implements ModelHandler { public User getUser(String username) { return persistenceHandler.getUser(username); } - } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index 820a7b1a9..427c6322d 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -12,6 +12,7 @@ package ch.eitchnet.privilege.handler; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -379,7 +380,8 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // create user - User user = User.buildUser(username, password, firstname, surname, userState, roles, locale); + User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), + locale); // put user in map userMap.put(username, user); diff --git a/src/ch/eitchnet/privilege/handler/ModelHandler.java b/src/ch/eitchnet/privilege/handler/ModelHandler.java index c464ebb2f..e8fa1256e 100644 --- a/src/ch/eitchnet/privilege/handler/ModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/ModelHandler.java @@ -10,8 +10,8 @@ package ch.eitchnet.privilege.handler; -import java.util.List; import java.util.Locale; +import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Certificate; @@ -33,21 +33,21 @@ public interface ModelHandler extends PrivilegeContainerObject { public User getUser(String username); - public void addOrReplaceUser(Certificate certificate, UserRep userRep); + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); public UserRep removeUser(Certificate certificate, String username); public void setUserPassword(Certificate certificate, String username, String password); - public void setUserNamePassword(Certificate certificate, String username, String firstname, String surname); + public void setUserName(Certificate certificate, String username, String firstname, String surname); public void setUserState(Certificate certificate, String username, UserState state); public void setUserLocaleState(Certificate certificate, String username, Locale locale); - public void addRoleToUser(Certificate certificate, String username, String rolename); + public void addRoleToUser(Certificate certificate, String username, String roleName); - public void removeRoleFromUser(Certificate certificate, String username, String rolename); + public void removeRoleFromUser(Certificate certificate, String username, String roleName); public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); @@ -69,9 +69,9 @@ public interface ModelHandler extends PrivilegeContainerObject { public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, List denyList); + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList); - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, List allowList); + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); public boolean persist(Certificate certificate); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index ded4736ed..117130490 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,7 +10,6 @@ package ch.eitchnet.privilege.handler; -import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.internal.Privilege; @@ -18,8 +17,6 @@ import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; /** - * TODO {@link PersistenceHandler} may not be freely accessible via {@link PrivilegeContainer} - * * @author rvonburg * */ diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java index fff3c1114..32be4f883 100644 --- a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java +++ b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java @@ -32,12 +32,14 @@ public class PrivilegeHelper { User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " + + certificate); } // validate user has PrivilegeAdmin role if (!user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { - throw new AccessDeniedException("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role!"); + throw new AccessDeniedException("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + + " role! Certificate: " + certificate); } else { return true; } diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index 0d987bed0..8b0b4d80c 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.helper; import java.io.File; +import java.util.HashSet; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.ConsoleAppender; @@ -21,6 +22,8 @@ import org.apache.log4j.PatternLayout; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; /** * @author rvonburg @@ -43,15 +46,19 @@ public class TestConfigurationHelper { PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); privilegeContainer.initialize(privilegeContainerXml); - // ModelHandler modelHandler = privilegeContainer.getModelHandler(); + ModelHandler modelHandler = privilegeContainer.getModelHandler(); + + Certificate certificate = auth("eitch", "1234567890"); for (int i = 0; i < 10; i++) { // let's authenticate a session auth("eitch", "1234567890"); } - // TODO let's add a user - // persistenceHandler.addUser(certificate, user); + // let's add a new user bob + UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + modelHandler.addOrReplaceUser(certificate, userRep, null); + logger.info("Added user bob"); // TODO let's add a role @@ -62,10 +69,11 @@ public class TestConfigurationHelper { /** * */ - private static void auth(String username, String password) { + private static Certificate auth(String username, String password) { long start = System.currentTimeMillis(); Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate(username, password); logger.info("Auth took " + (System.currentTimeMillis() - start)); logger.info("Authenticated with certificate: " + certificate); + return certificate; } } diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index 92577e24e..12c3d755d 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -21,8 +21,8 @@ public class RoleRep implements Serializable { private static final long serialVersionUID = 1L; - public final String name; - public final Set privileges; + private String name; + private Set privileges; /** * @param name @@ -32,4 +32,34 @@ public class RoleRep implements Serializable { this.name = name; this.privileges = privileges; } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the privileges + */ + public Set getPrivileges() { + return privileges; + } + + /** + * @param privileges + * the privileges to set + */ + public void setPrivileges(Set privileges) { + this.privileges = privileges; + } } diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index 4e3575895..d2222f423 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -22,12 +22,12 @@ public class UserRep implements Serializable { private static final long serialVersionUID = 1L; - public final String username; - public final String firstname; - public final String surname; - public final UserState userState; - public final Set roles; - public final Locale locale; + private String username; + private String firstname; + private String surname; + private UserState userState; + private Set roles; + private Locale locale; /** * @param username @@ -46,4 +46,88 @@ public class UserRep implements Serializable { this.roles = roles; this.locale = locale; } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * @param username the username to set + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return the firstname + */ + public String getFirstname() { + return firstname; + } + + /** + * @param firstname the firstname to set + */ + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + /** + * @return the surname + */ + public String getSurname() { + return surname; + } + + /** + * @param surname the surname to set + */ + public void setSurname(String surname) { + this.surname = surname; + } + + /** + * @return the userState + */ + public UserState getUserState() { + return userState; + } + + /** + * @param userState the userState to set + */ + public void setUserState(UserState userState) { + this.userState = userState; + } + + /** + * @return the roles + */ + public Set getRoles() { + return roles; + } + + /** + * @param roles the roles to set + */ + public void setRoles(Set roles) { + this.roles = roles; + } + + /** + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** + * @param locale the locale to set + */ + public void setLocale(Locale locale) { + this.locale = locale; + } } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 3edc0575a..e2f0b9ea0 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -10,12 +10,10 @@ package ch.eitchnet.privilege.model.internal; -import java.util.Collections; import java.util.Locale; import java.util.Set; import ch.eitchnet.privilege.helper.PrivilegeHelper; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -39,9 +37,16 @@ public final class User { private final Locale locale; /** - * The {@link User} constructor is private to ensure no unauthorized creation of {@link User} objects + * + * @param username + * @param password + * @param firstname + * @param surname + * @param userState + * @param roles + * @param locale */ - private User(String username, String password, String firstname, String surname, UserState userState, + public User(String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale) { this.username = username; @@ -153,34 +158,4 @@ public final class User { builder.append("]"); return builder.toString(); } - - /** - * @return a new {@link User} object which is authenticated on the current Java Virtual Machine - */ - public static User buildUser(String username, String password, String firstname, String surname, - UserState userState, Set roles, Locale locale) { - - // set a default locale - if (locale == null) - locale = Locale.getDefault(); - - // TODO validate who is creating this User object - - if (username.length() < 3) { - throw new PrivilegeException("The given username is shorter than 3 characters"); - } - - if (firstname.isEmpty()) { - throw new PrivilegeException("The given firstname is empty"); - } - - if (surname.isEmpty()) { - throw new PrivilegeException("The given firstname is empty"); - } - - User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), - locale); - - return user; - } } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java new file mode 100644 index 000000000..6dcba0a80 --- /dev/null +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.test; + +import java.io.File; +import java.util.HashSet; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; +import org.junit.Before; +import org.junit.Test; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.handler.ModelHandler; +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; + +/** + * @author rvonburg + * + */ +public class PrivilegeTest { + + private static final Logger logger = Logger.getLogger(PrivilegeTest.class); + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + + // set up log4j + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); + Logger.getRootLogger().setLevel(Level.INFO); + + // initialize container + String pwd = System.getProperty("user.dir"); + File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); + PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); + privilegeContainer.initialize(privilegeContainerXml); + } + + @Test + public void testAuthenticationOk() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + } + + @Test(expected = AccessDeniedException.class) + public void testAuthenticationNOk() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", "123"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + } + + @Test(expected = PrivilegeException.class) + public void testAuthenticationPWNull() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", null); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + } + + @Test + public void testAddUserBobWithPW() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + + ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + + // let's add a new user bob + UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + modelHandler.addOrReplaceUser(certificate, userRep, null); + logger.info("Added user bob"); + + // set bob's password + modelHandler.setUserPassword(certificate, "bob", "12345678901"); + logger.info("Set Bob's password"); + } + + /** + * Will fail because user bob is not yet enabled + * + * @throws Exception + */ + @Test(expected = AccessDeniedException.class) + public void testAuthAsBob() throws Exception { + + PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); + } + + @Test + public void testEnableUserBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + + ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + modelHandler.setUserState(certificate, "bob", UserState.ENABLED); + } + + /** + * Will fail because user bob does not have admin rights + * + * @throws Exception + */ + @Test(expected = AccessDeniedException.class) + public void testAddUserTedAsBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + "12345678901"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // let's add a new user bob + UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); + logger.info("Added user bob"); + } +} From 57f0164e38f464a871d3a295ac04e76e86dd085c Mon Sep 17 00:00:00 2001 From: eitch Date: Wed, 14 Jul 2010 21:26:52 +0000 Subject: [PATCH 027/457] --- config/PrivilegeRoles.xml | 2 + .../privilege/base/PrivilegeContainer.java | 1 + .../eitchnet/privilege/base/XmlConstants.java | 6 + .../handler/DefaultModelHandler.java | 2 +- .../helper/BootstrapConfigurationHelper.java | 179 ++++++++++++++++++ .../helper/TestConfigurationHelper.java | 79 -------- .../eitchnet/privilege/helper/XmlHelper.java | 22 ++- .../privilege/model/internal/User.java | 3 +- .../privilege/test/PrivilegeTest.java | 66 ++++++- 9 files changed, 267 insertions(+), 93 deletions(-) create mode 100644 src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java delete mode 100644 src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index c78b0ca4e..a09f1a393 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -1,8 +1,10 @@ + + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 48ca8f866..1ffc36d3b 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -33,6 +33,7 @@ public class PrivilegeContainer { * This is the role users must have, if they can modify the {@link PrivilegeContainer} and its objects */ public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; + public static final String PRIVILEGE_CONTAINER_FILE = "PrivilegeContainer.xml"; private static final Logger logger = Logger.getLogger(PrivilegeContainer.class); diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index fbf3e9dda..727841b53 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -15,6 +15,12 @@ package ch.eitchnet.privilege.base; * */ public class XmlConstants { + public static final String XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer"; + public static final String XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles"; + public static final String XML_ROOT_PRIVILEGES = "Privileges"; + public static final String XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers"; + public static final String XML_ROOT_RESTRICTION_POLICIES = "RestrictionPolicies"; + public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java index 9dfd0b806..cbbeface2 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java @@ -195,7 +195,7 @@ public class DefaultModelHandler implements ModelHandler { // create new user Set newRoles = new HashSet(currentRoles); - currentRoles.add(roleName); + newRoles.add(roleName); User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user .getSurname(), user.getState(), newRoles, user.getLocale()); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java new file mode 100644 index 000000000..14001dc08 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.File; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; +import org.dom4j.Document; +import org.dom4j.DocumentFactory; +import org.dom4j.Element; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.base.XmlConstants; + +/** + *

    + * This class is a simple application which can be used to bootstrap a new configuration for the + * {@link PrivilegeContainer} + *

    + * + *

    + * Simple execute the application and it will ask a few questions and then write a new set of configuration files which + * can be used to run the {@link PrivilegeContainer} + *

    + * + * @author rvonburg + * + */ +public class BootstrapConfigurationHelper { +// private static final Logger logger = Logger.getLogger(BootstrapConfigurationHelper.class); + + private static String path; + + private static String usersFileName = "PrivilegeUsers.xml"; + private static String rolesFileName = "PrivilegeRoles.xml"; + private static String privilegesFileName = "Privileges.xml"; + + private static String hashAlgorithm = "SHA-256"; + + private static String policyXmlFile = "RestrictionPolicies.xml"; + + private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; + private static String defaultSessionHandler = "ch.eitchnet.privilege.handler.DefaultSessionHandler"; + private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; + private static String defaultPolicyHandler = "ch.eitchnet.privilege.handler.DefaultPolicyHandler"; + + /** + * @param args + */ + public static void main(String[] args) { + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); + Logger.getRootLogger().setLevel(Level.INFO); + + // get current directory + path = System.getProperty("user.dir") + "/newConfig"; + + // ask user where to save configuration, default is pwd/newConfig/.... + + // see if path already exists + File pathF = new File(path); + if (pathF.exists()) { + throw new RuntimeException("Path already exists: " + pathF.getAbsolutePath()); + } else { + if (!pathF.mkdirs()) { + throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); + } + } + + // ask other questions... + + // now perform work: + createXmlPrivilegeContainer(); + createPolicyConfiguration(); + createModel(); + } + + /** + * + */ + private static void createModel() { + // TODO Auto-generated method stub + + } + + /** + * + */ + private static void createPolicyConfiguration() { + // TODO Auto-generated method stub + + } + + /** + * + */ + private static void createXmlPrivilegeContainer() { + + // create document root + DocumentFactory factory = DocumentFactory.getInstance(); + Document doc = factory.createDocument(XmlHelper.DEFAULT_ENCODING); + doc.setName(XmlConstants.XML_ROOT_PRIVILEGE_CONTAINER); + Element rootElement = factory.createElement(XmlConstants.XML_ROOT_PRIVILEGE_CONTAINER); + doc.setRootElement(rootElement); + + Element parameterElement; + Element parametersElement; + + // create PersistenceHandler + Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); + rootElement.add(persistenceHandlerElem); + persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPersistenceHandler); + parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); + persistenceHandlerElem.add(parametersElement); + // Parameter usersXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_USERS_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, usersFileName); + parametersElement.add(parameterElement); + // Parameter rolesXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_ROLES_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, rolesFileName); + parametersElement.add(parameterElement); + // Parameter privilegesXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_PRIVILEGES_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, privilegesFileName); + parametersElement.add(parameterElement); + + // create SessionHandler + Element sessionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_SESSION); + sessionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultSessionHandler); + + // create ModelHandler + Element modelHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_MODEL); + rootElement.add(modelHandlerElem); + modelHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, "ch.eitchnet.privilege.handler.DefaultModelHandler"); + + // create EncryptionHandler + Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); + rootElement.add(encryptionHandlerElem); + encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultEncryptionHandler); + parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); + encryptionHandlerElem.add(parametersElement); + // Parameter hashAlgorithm + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_HASH_ALGORITHM); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, hashAlgorithm); + parametersElement.add(parameterElement); + + // create PolicyHandler + Element policyHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_POLICY); + rootElement.add(policyHandlerElem); + policyHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPolicyHandler); + parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); + policyHandlerElem.add(parametersElement); + // Parameter policyXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_POLICY_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, policyXmlFile); + parametersElement.add(parameterElement); + + File privilegeContainerFile = new File(path + "/" + PrivilegeContainer.PRIVILEGE_CONTAINER_FILE); + XmlHelper.writeDocument(doc, privilegeContainerFile); + } +} diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java deleted file mode 100644 index 8b0b4d80c..000000000 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.helper; - -import java.io.File; -import java.util.HashSet; - -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.ConsoleAppender; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PatternLayout; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.handler.ModelHandler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.UserRep; -import ch.eitchnet.privilege.model.UserState; - -/** - * @author rvonburg - * - */ -public class TestConfigurationHelper { - private static final Logger logger = Logger.getLogger(TestConfigurationHelper.class); - - /** - * @param args - */ - public static void main(String[] args) { - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); - Logger.getRootLogger().setLevel(Level.INFO); - - // initialize container - String pwd = System.getProperty("user.dir"); - File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); - PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); - privilegeContainer.initialize(privilegeContainerXml); - - ModelHandler modelHandler = privilegeContainer.getModelHandler(); - - Certificate certificate = auth("eitch", "1234567890"); - - for (int i = 0; i < 10; i++) { - // let's authenticate a session - auth("eitch", "1234567890"); - } - - // let's add a new user bob - UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); - modelHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user bob"); - - // TODO let's add a role - - // TODO let's add a privilege - - } - - /** - * - */ - private static Certificate auth(String username, String password) { - long start = System.currentTimeMillis(); - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate(username, password); - logger.info("Auth took " + (System.currentTimeMillis() - start)); - logger.info("Authenticated with certificate: " + certificate); - return certificate; - } -} diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index 05c75ac17..f4f57a87a 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -35,6 +35,8 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; */ public class XmlHelper { + public static final String DEFAULT_ENCODING = "UTF-8"; + private static final Logger logger = Logger.getLogger(XmlHelper.class); public static Document parseDocument(File xmlFile) { @@ -56,19 +58,20 @@ public class XmlHelper { } } - public static void writeDocument(Element rootElement, File file) { + public static void writeDocument(Document document, File file) { - logger.info("Exporting root element " + rootElement.getName() + " to " + file.getAbsolutePath()); + logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); OutputStream fileOutputStream = null; try { - Document document = DocumentFactory.getInstance().createDocument(); - document.setRootElement(rootElement); fileOutputStream = new FileOutputStream(file); - String aEncodingScheme = "UTF-8"; + String aEncodingScheme = document.getXMLEncoding(); + if (aEncodingScheme == null || aEncodingScheme.isEmpty()) { + aEncodingScheme = DEFAULT_ENCODING; + } OutputFormat outformat = OutputFormat.createPrettyPrint(); outformat.setEncoding(aEncodingScheme); XMLWriter writer = new XMLWriter(fileOutputStream, outformat); @@ -90,4 +93,13 @@ public class XmlHelper { } } } + + public static void writeDocument(Element rootElement, File file) { + + Document document = DocumentFactory.getInstance().createDocument(DEFAULT_ENCODING); + document.setRootElement(rootElement); + document.setName(rootElement.getName()); + + writeDocument(document, file); + } } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index e2f0b9ea0..b14dad127 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.model.internal; +import java.util.Collections; import java.util.Locale; import java.util.Set; @@ -56,7 +57,7 @@ public final class User { this.firstname = firstname; this.surname = surname; - this.roles = roles; + this.roles = Collections.unmodifiableSet(roles); this.locale = locale; } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 6dcba0a80..d7a6e748f 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -18,7 +18,7 @@ import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; -import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import ch.eitchnet.privilege.base.PrivilegeContainer; @@ -40,8 +40,8 @@ public class PrivilegeTest { /** * @throws java.lang.Exception */ - @Before - public void setUp() throws Exception { + @BeforeClass + public static void init() throws Exception { // set up log4j BasicConfigurator.resetConfiguration(); @@ -64,14 +64,14 @@ public class PrivilegeTest { } @Test(expected = AccessDeniedException.class) - public void testAuthenticationNOk() throws Exception { + public void testFailAuthenticationNOk() throws Exception { Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", "123"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test(expected = PrivilegeException.class) - public void testAuthenticationPWNull() throws Exception { + public void testFailAuthenticationPWNull() throws Exception { Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); @@ -101,7 +101,7 @@ public class PrivilegeTest { * @throws Exception */ @Test(expected = AccessDeniedException.class) - public void testAuthAsBob() throws Exception { + public void testFailAuthAsBob() throws Exception { PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); } @@ -116,13 +116,42 @@ public class PrivilegeTest { modelHandler.setUserState(certificate, "bob", UserState.ENABLED); } + /** + * Will fail as user bob has no role + * + * @throws Exception + */ + @Test(expected = PrivilegeException.class) + public void testFailAuthUserBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + "12345678901"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + } + + @Test + public void testAddUserRoleToBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + + ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + modelHandler.addRoleToUser(certificate, "bob", "user"); + } + + @Test + public void testAuthAsBob() throws Exception { + + PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); + } + /** * Will fail because user bob does not have admin rights * * @throws Exception */ @Test(expected = AccessDeniedException.class) - public void testAddUserTedAsBob() throws Exception { + public void testFailAddUserTedAsBob() throws Exception { Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); @@ -133,4 +162,27 @@ public class PrivilegeTest { PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } + + @Test + public void testAddAdminRoleToBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + + ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + modelHandler.addRoleToUser(certificate, "bob", PrivilegeContainer.PRIVILEGE_ADMIN_ROLE); + } + + @Test + public void testAddUserTedAsBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + "12345678901"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // let's add a new user ted + UserRep userRep = new UserRep("ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); + PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); + logger.info("Added user bob"); + } } From 28f1e4a66292ad75f09d63af9a017dca46a3c870 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 2 Aug 2010 23:20:54 +0000 Subject: [PATCH 028/457] --- config/PrivilegeContainer.xml | 2 +- config/PrivilegePolicies.xml | 6 ++ config/PrivilegeRoles.xml | 5 +- config/PrivilegeUsers.xml | 3 +- config/Privileges.xml | 8 +- config/RestrictionPolicies.xml | 6 -- docs/PrivilegeHandlers.dia | Bin 0 -> 2034 bytes docs/PrivilegeModelPrivilege.dia | Bin 0 -> 2063 bytes docs/PrivilegeModelUser.dia | Bin 0 -> 1752 bytes .../privilege/base/PrivilegeContainer.java | 12 +-- .../eitchnet/privilege/base/XmlConstants.java | 2 +- .../handler/DefaultPolicyHandler.java | 47 ++++++---- .../privilege/handler/SessionHandler.java | 4 +- .../helper/BootstrapConfigurationHelper.java | 2 +- .../privilege/model/Restrictable.java | 4 +- .../privilege/policy/DefaultPrivilege.java | 71 ++++++++++++++++ .../privilege/policy/DefaultRestriction.java | 80 ------------------ ...ictionPolicy.java => PrivilegePolicy.java} | 5 +- .../privilege/test/PrivilegeTest.java | 15 ++++ .../privilege/test/TestRestrictable.java | 35 ++++++++ 20 files changed, 186 insertions(+), 121 deletions(-) create mode 100644 config/PrivilegePolicies.xml delete mode 100644 config/RestrictionPolicies.xml create mode 100644 docs/PrivilegeHandlers.dia create mode 100644 docs/PrivilegeModelPrivilege.dia create mode 100644 docs/PrivilegeModelUser.dia create mode 100644 src/ch/eitchnet/privilege/policy/DefaultPrivilege.java delete mode 100644 src/ch/eitchnet/privilege/policy/DefaultRestriction.java rename src/ch/eitchnet/privilege/policy/{RestrictionPolicy.java => PrivilegePolicy.java} (60%) create mode 100644 test/ch/eitchnet/privilege/test/TestRestrictable.java diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index 0c64f7a94..e87acad3c 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -17,7 +17,7 @@ - + diff --git a/config/PrivilegePolicies.xml b/config/PrivilegePolicies.xml new file mode 100644 index 000000000..2b2c6f486 --- /dev/null +++ b/config/PrivilegePolicies.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index a09f1a393..89724934b 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -3,8 +3,11 @@ - + + + + \ No newline at end of file diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index cc8867f25..f03340020 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -7,8 +7,9 @@ ENABLED en_GB - admin PrivilegeAdmin + admin + serviceExecutor diff --git a/config/Privileges.xml b/config/Privileges.xml index e1fb1de4e..baec28f6c 100644 --- a/config/Privileges.xml +++ b/config/Privileges.xml @@ -1,10 +1,16 @@ - + true + + + false + + ch.eitchnet.privilege.test.TestRestrictable + \ No newline at end of file diff --git a/config/RestrictionPolicies.xml b/config/RestrictionPolicies.xml deleted file mode 100644 index acda375fe..000000000 --- a/config/RestrictionPolicies.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/docs/PrivilegeHandlers.dia b/docs/PrivilegeHandlers.dia new file mode 100644 index 0000000000000000000000000000000000000000..4f8da83b765831d300996f1c1f64ca25a1fdaabb GIT binary patch literal 2034 zcmaKqc{~#iAIA&1N^_>)k`0mHOha=H&3zPdOHq_%axcP6IdT_~99whW{2bG$jSRUm z=ah_=4o9vW)11FwJ%2o}=XpJ^*Yo|nUf)07f4u*CgHrep{XQ=4Wj8m>q?R~tweCi* zlB!028MB16ykrfU^egw<$`T=~O!B`~N-3c(1zi6_Dn@9wbD~5&AvYb_rI!Jn#)PD$ zv#9&3-(K)Rj1>0pue@^DK=$bwH5R3kh5LJB@~_veICi(!*B1wCXL8?dp50T42(iys zl%V=+fIuK2eL#}v=qF!PT+=pah5!#KMD1@m){_0(<&%w9->iz3o;gJquid_78Sq{1 z^Ay%@AhNj)Dv^!hk!;*Ta;)}!s9_9zLoTXg@Hq4E+Pt}4aK7IpBaH@5TX@jl^Z^Yv zEK8{fueUwZX}w8xQaC5^-LUN*mi+``@@>4)bqznXMRwE>Gq+0G%z2$ux|;w)6noue z#?FtBmi(E%Y^KeAusLYNOS_0WX#xNMx_y0lJQWeT6_N3hayFpII6lj4wyN)knZ@*rCVg1n4)@UyiDP~* zZnr@O_NXT}7OF`jBKak<22DSR^NyoJk&)xIx zfwC+`wzy(4wX+(w?+rP)rN9E0f_%Dcmu42mUGo!1E--9^Zm#O%YTVv`#E8bT=fK7Z z;DOf4`x=>gS^ju($@5R9&PP1eiLTG%v;23;zVtTg0ZXkex?w(J3Ei{I?gFL?dn{m? zJOYBhGA+jg(t>qP>lp6f*9>HquS{I$q|r6>wjW22tmZ7Hy%TT|>!&95^DdsWfRZu+ zPzhyu&(aH#W`o?NJq*6j6kR!A9bRQdgQUdB1igdtKDl2tXui)S;<0sQUE>35vW@8u zDdJh#;n0RvTb%k)E{o2PhFq(3frCp*M;%Mx3P!xbb&&F6(}@TzqL} z5wm`kgp|3dCy+20=c_fArL>&0z0}hRXV|Kq4*rhCOS82J3;FSdiF5L?tA?=NA_1%x z4PH}%*Qgb1&+u^Uo@5S$dv&L7?DO|^(SrDs4=#Tr(5vcrbr{HW^KXctyBGqpdGV?#(U=OsU{>R=;i(?fED2XsU(H*L;dCRP9w zJg(?gmC2;iK(4K`UOhZ*ssOeq$fiLw=h40 z;pcrV%K<3+HV8Er{Q-Z+T3IcU9>#j3$38DQh-ifO)D#@QI`uW0$+zjZMp0=ofzq|? zH~b11`Bq;(ML{~JLQgtnTMUvb+h&w-#d+nW*wylAbl#H%DUJ46QH452;q_Z)^YrJB=;yaYv8C)Ag| zsGO(X&qj^(l{+BFl=-Z`Ozz4l7maD?I5m5)k5PQpi=2r-524i1I;?_vhuWhkwIZ1N zBaW!hpH{Au@&=C|NFCPZ3g-FB-z$nqY&1tq zUbwlyZ%v{aP|^=tfoYhsF4_=5+we#7 z7J9Z~yaYWdZQ(y+WPw4`L+eUHE$(&>xjY6Bz)0a6G#-xZ*L%*qDiy2i6*i-lVFfPR z(HvwXSifcud0*1xzO>_Y_EHVxr^KX2AgBc}Rc?fuE_vOK`_v}rrB{6^=9dJ_Uw?xu zQe=JC^|!{3MEp-h_A~aL><8{IjB4oN6D(vs*hLr`hLzBzLodrz)LU8%_hV!$gdO-H z(AZxs^1gl=e_VmeASk|j2sTQW=@niyte=r5dDQFL)>J`H?t%irTJ_0NVQXeSLIT@z@^Ehs1?4pf9+_AVGk7DgL#Kji&iU?mV9m|*#) zJ8yjaZ3&H)otOsI+62z4pRBwvr@7(MGP`@AK#&PFT8f_#YB=Sosq)#2=%HaRHoZ&X zW55OEd)*kLe<;5J#5aFD|msue6SJHKsb% zd4wFJEY+6D6OVX)Fnz1@{tCFYi8Bq0s$MudWB2$`*@SoU|9RMe=1@sg4ITofi%)8J zs}0{O=tE{23{upZfp7fCG~6fsK#)I4XrHqN7#@Qd zPSuI9AEM#IDyda=;9uQ2o2*n%v>IpEY3JI9@A_;T{vGz}WpmvDUq8B5uVMbsp??7W CZ2*h_ literal 0 HcmV?d00001 diff --git a/docs/PrivilegeModelPrivilege.dia b/docs/PrivilegeModelPrivilege.dia new file mode 100644 index 0000000000000000000000000000000000000000..f46a49ea6d6e8ef2aef5ea370e9d46035577ac02 GIT binary patch literal 2063 zcmZ{Yc{mde1IJM!#$34@epZIujmBI#W`+1(v^Lf5fshlkTjP2AW+9`cXlkdS$o$5P-B1xw#B`NC0 z8Dz>!gBxJ8!uR4$#(ln?U(44*rxTBNZ4!a#H_Fo+~iJpu>YvVa92-B7xHZInR@}B&oBmgPKL7 zY0l(LUsEwUTsms9-RV)CzrOJ3^&XgPbeb!CRWwFXxTbG1#2K+I{;P>HUa)Vh$xjL; zAaVG)Rb*jnj*3L&*3kgs(&-f~>y`yzI+LmG!U?udB7N?=jXWAQ6C=3u5ITJBTD-#f zKjDaOw@oZ`6M1q|RH`A1~Z{rCdAY9Az;iL#e0pcqZA3a;V~)8u8jK_75d;eBU6G9-eNP1Tahro;)g(gXVw= zpMsxbOznKa9Ir2agXl6}%|ZlpUsPNOrUosw2_Z%hw6Pyf%>u;$h21-xmEybrGL}K{x+NBR1${o{Y6R9nzlI#!CZ{^s?h$pK+YKg^ee1Hohy&yy zjc$K|BL~p%gC$4OCb&4FU4C>rx3u}->m!J#%s{ZaiglL{2)lx+lhdCVu{iS~!nA(X zJ!VzSTTH8yuO~EHEv#BlfOkIb_axLPfGnGc`Lb3ymsB%}f5u+2t$sC2ikKk%GrS}W z0Ltz*(RO+LY8I%serY&mcm^F#hOg9RDs{@cZNyQ`6HGIcfWDn)S9X5>j=uUFWO1=l z`CZfy@t2p?m)X#?$bmI~@?I2j!&%onoM`Q_AuY;t{BHW36!Ii&jeN`dLB}>L_w7=Y z29F0ZEd1jBj2EGha>s7N{Eb{%RM2f*5m`%gVVUIA>*vrY-K9tMH-VaMKb056Yifh- z0xP&%A1NS4m#SCxtFI(kNF8e_MtS0wk;>}i%0{D~F|0SR2{+|w=du%v0TlW?ePV72 z;5)d`obXilKb4(a4+F0zQHdX1`9$?<&dp7-JMaFRc?8}EW%lkk zx|hQGd_}9g+>O#BS4xy;A4AbO_oF#|#!4sP|5m*eMA&kk5LVxsdIV*w_e9d2P5 z(2Lac<4s}Sp0-a|oG;+qX_O($zX^ph99D_$|6tK_UcAMiB|fp@jfQ?(1tj$G*tnqO z1u@lj8O2Z&8+w7S1S2os7D$(G51Rs=j@XmUlge!UsE9;Q{CoRH?-?M~+#A%-od%N5FIN9*> ziM68bJq}E{0=iOR6n0(pnucdVxmp74wMP{eQ*12isH=g!TMQevDD|+qe8Y=C_*&cx z_PI!-m`fU7>BdnUpt87jtY(#S-mx&gw(bi4P2G^>W@(Q{feU?RG!$}+S^bci_Tt)$lbaa zZ}cska`}v8-sGLA2)rbwXZ>(jj26ey3rF9r_UP8JlU2TDic3^PS+?j-lFF}S6$rr zO#HK`Zq7SzU0XgCHaP6ou=xswb;*un8yIBfZ@UKAn}h@!O#tLa550}podjL9@5Dn8 z=`Yw}-k!sl6#mtY>vn>YgP#r+8c|8Nn_xV_Iiso$#))~C9L|F1P`rYY!`ZDR&c@QW zdaY0dEo4lCKx@_fQ7Z57Wv!MtbLCz8lB-Uo@Jv%x6QE(>fQ8E*03{1EdWe+JLFi>z8Nf$!NT%4t55!) literal 0 HcmV?d00001 diff --git a/docs/PrivilegeModelUser.dia b/docs/PrivilegeModelUser.dia new file mode 100644 index 0000000000000000000000000000000000000000..86d5b1149545da120c81855694cad0df40d5ba97 GIT binary patch literal 1752 zcmV;}1}FI+iwFP!000021MOW~Z`(E)eb28jT%b>hEX9^DO_O3PFl<1E7Hjit&@wG^ zrA3vb;`(L3eU#+oHtvs^AMzEVUBX>k@=igx}X zoRf|z)M?+GwOUUDOo=A(xy@OkwPce>(^fzh^twGE-Zx*0D44dBQk!y~;6k?U3BPW? znXQj@XA`EAit|kDkoa^WDfyNk+BU~%R3vmrWj5?06pEQ5dKK;z$%zyHeo|&rN|{1| zfAi*+YE9*vq*|Mk+j#pbn+v2DMEWe)#WMZFlg_xb-0mno`p(ep+LwdwL%*C3H)$4* zHw!15g=1L`aUA6lZid)%&2oxIpE0e)f%?w~SI0CUIsM4E zQ`zfFHhrGFsS-I+mTao$;~zD3R}y_yQ}lbL*o4!4wP%5@Ec~jP@Y$=gp24?oFF6{L zXt_(obVyz5p1wr*5t~wVV3?)L5T9(>;}Kn~7v5!JV!O z#dbW|=NH{<+jnPZ;*Y83%GvPtEEHsh_fo?A*1*58^n3Yi&tj=eeAnKvJ2&7NH>{-&44CIzNrP6K~; z?%dAIgK`*}_h!ui^~LBuGh!k0O$c%3}BrYeJD442PL2 zT>L8d^wWg1??uTR3cgwh8P1tks7Bcem8y&vr=;;e4_8GIGrv97;_=e_PWe66%p>cb zDS3qoS7*4tI_3IiGh>}tmED?@s>Kub9LzLrYnYkXdew~4=8#ilZ4Nd)@DAcSm5%ee z;GPcPBf7Fbs$`pzpG0OQfM~%V3-78W2le92-XStuptwwqX8erMlD9IZH$pprU)Fys zgELnldKRxTq}@$eeOT*6oN=aC``~C0E%r!ar^D>o_Z=y-7U;g1ykq5w{llX$++NA5 zA@YQ#NlPK8AP>j`@|;MX8Iww5=>dDdo&xMq5z;nr2iz&T=lP7l_P>cAka@E zkTi8x%`H7tzvcQWS%KdA`U)fhiC#X5xbO&XZV9@r77o+_b@ox`Bp21z0nj;cw{;O3 z>e0Gxe?P=mY13X}Ml0q{b1HDB_3JjLx#riEGqMJw^HKfQw0?cgG}DA$>W0>3jn1^L z)vv?{@tia5FOf4>!g{!K4%(kZ$ce|uw2BI zCfPmX)WY{F%~Vb<32-eNFHPJSw#gi6w{xBEcZ= z>J0+j3O_H57-0t6kyM1e9a9A1dBk1`4nT?16y- u1OkCzpn!n_1_~G`V4(O328!f0Co3v%&XO1A$2ZNrIr|qZGas^Rs{jBt5M;ps literal 0 HcmV?d00001 diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 1ffc36d3b..acd6ee315 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -130,10 +130,10 @@ public class PrivilegeContainer { String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); - // instantiate modification handler - Element modificationHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); - String modificationHandlerClassName = modificationHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - ModelHandler modelHandler = ClassHelper.instantiateClass(modificationHandlerClassName); + // instantiate model handler + Element modelHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); + String modelHandlerClassName = modelHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + ModelHandler modelHandler = ClassHelper.instantiateClass(modelHandlerClassName); try { persistenceHandler.initialize(persistenceHandlerElement); @@ -162,11 +162,11 @@ public class PrivilegeContainer { throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); } try { - modelHandler.initialize(modificationHandlerElement); + modelHandler.initialize(modelHandlerElement); modelHandler.setPersistenceHandler(persistenceHandler); } catch (Exception e) { logger.error(e, e); - throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName + throw new PrivilegeException("ModificationHandler " + modelHandlerClassName + " could not be initialized"); } diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 727841b53..d2c7400e4 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,7 +19,7 @@ public class XmlConstants { public static final String XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles"; public static final String XML_ROOT_PRIVILEGES = "Privileges"; public static final String XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers"; - public static final String XML_ROOT_RESTRICTION_POLICIES = "RestrictionPolicies"; + public static final String XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies"; public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index a72f5c996..27dff956d 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -24,8 +24,9 @@ import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.policy.RestrictionPolicy; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg @@ -33,7 +34,7 @@ import ch.eitchnet.privilege.policy.RestrictionPolicy; */ public class DefaultPolicyHandler implements PolicyHandler { - private Map> policyMap; + private Map> policyMap; /** * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, @@ -48,26 +49,38 @@ public class DefaultPolicyHandler implements PolicyHandler { else if (restrictable == null) throw new PrivilegeException("Restrictable may not be null!"); - // validate restriction key for this restrictable - String restrictionKey = restrictable.getRestrictionKey(); - if (restrictionKey == null || restrictionKey.length() < 3) { + // validate PrivilegeName for this restrictable + String privilegeName = restrictable.getPrivilegeName(); + if (privilegeName == null || privilegeName.length() < 3) { throw new PrivilegeException( - "The RestrictionKey may not be shorter than 3 characters. Invalid Restrictable " + "The PrivilegeName may not be shorter than 3 characters. Invalid Restrictable " + restrictable.getClass().getName()); } - // get restriction policy class - Class policyClazz = policyMap.get(restrictionKey); - if (policyClazz == null) { - throw new PrivilegeException("No RestrictionPolicy exists for the RestrictionKey " + restrictionKey - + " for Restrictable " + restrictable.getClass().getName()); + // If the role does not have this privilege, then stop as another role might have this privilege + if (!role.hasPrivilege(privilegeName)) { + return false; } - // instantiate policy - RestrictionPolicy policy = ClassHelper.instantiateClass(policyClazz); + // get the privilege for this restrictable + Privilege privilege = PrivilegeContainer.getInstance().getModelHandler().getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " + + restrictable.getClass().getName()); + } - // delegate checking to restriction policy - return policy.actionAllowed(role, restrictable); + // get the policy class configured for this privilege + Class policyClazz = policyMap.get(privilege.getPolicy()); + if (policyClazz == null) { + throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " + + privilegeName); + } + + // instantiate the policy + PrivilegePolicy policy = ClassHelper.instantiateClass(policyClazz); + + // delegate checking to privilege policy + return policy.actionAllowed(role, privilege, restrictable); } /** @@ -95,7 +108,7 @@ public class DefaultPolicyHandler implements PolicyHandler { + policyFile.getAbsolutePath()); } - policyMap = new HashMap>(); + policyMap = new HashMap>(); // parse policy xml file to XML document Element containerRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); @@ -105,7 +118,7 @@ public class DefaultPolicyHandler implements PolicyHandler { String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - Class clazz = ClassHelper.loadClass(policyClass); + Class clazz = ClassHelper.loadClass(policyClass); policyMap.put(policyName, clazz); } diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java index d019d0dbe..10e0b4fa4 100644 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -49,7 +49,7 @@ public interface SessionHandler extends PrivilegeContainerObject { public boolean isCertificateValid(Certificate certificate); /** - * @param user + * @param username * @param password * * @return @@ -57,5 +57,5 @@ public interface SessionHandler extends PrivilegeContainerObject { * @throws AccessDeniedException * if the user credentials are not valid */ - public Certificate authenticate(String user, String password); + public Certificate authenticate(String username, String password); } diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 14001dc08..db99ea0be 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -49,7 +49,7 @@ public class BootstrapConfigurationHelper { private static String hashAlgorithm = "SHA-256"; - private static String policyXmlFile = "RestrictionPolicies.xml"; + private static String policyXmlFile = "PrivilegePolicies.xml"; private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; private static String defaultSessionHandler = "ch.eitchnet.privilege.handler.DefaultSessionHandler"; diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index 9a2952fac..155876e23 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -16,7 +16,7 @@ package ch.eitchnet.privilege.model; */ public interface Restrictable { - public String getRestrictionKey(); + public String getPrivilegeName(); - public Object getRestrictionValue(); + public Object getPrivilegeValue(); } diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java new file mode 100644 index 000000000..097214169 --- /dev/null +++ b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.policy; + +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; + +/** + * @author rvonburg + * + */ +public class DefaultPrivilege implements PrivilegePolicy { + + /** + * @see ch.eitchnet.privilege.policy.PrivilegePolicy#actionAllowed(ch.eitchnet.privilege.model.internal.Role, + * ch.eitchnet.privilege.model.internal.Privilege, ch.eitchnet.privilege.model.Restrictable) + */ + @Override + public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable) { + + // validate user is not null + if (role == null) + throw new PrivilegeException("Role may not be null!"); + + // get the PrivilegeName + String privilegeName = restrictable.getPrivilegeName(); + if (privilegeName == null || privilegeName.isEmpty()) { + throw new PrivilegeException("The PrivilegeName for the Restrictable is null or empty: " + restrictable); + } + + // does this role have privilege for any values? + if (privilege.isAllAllowed()) + return true; + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // DefaultPrivilege policy expects the privilege value to be a string + if (!(object instanceof String)) { + throw new PrivilegeException(Restrictable.class.getName() + " " + restrictable.getClass().getSimpleName() + + " has returned a non-string privilege value!"); + } + + String privilegeValue = (String) object; + + // first check values not allowed + for (String denied : privilege.getDenyList()) { + if (denied.equals(privilegeValue)) + return false; + } + + // now check values allowed + for (String allowed : privilege.getAllowList()) { + if (allowed.equals(privilegeValue)) + return true; + } + + // default is not allowed + return false; + } +} diff --git a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java deleted file mode 100644 index d0d27d1e8..000000000 --- a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.policy; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Privilege; -import ch.eitchnet.privilege.model.internal.Role; - -/** - * @author rvonburg - * - */ -public class DefaultRestriction implements RestrictionPolicy { - - /** - * @see ch.eitchnet.privilege.policy.RestrictionPolicy#actionAllowed(java.lang.String, - * ch.eitchnet.privilege.model.internal.Role, ch.eitchnet.privilege.model.Restrictable) - */ - @Override - public boolean actionAllowed(Role role, Restrictable restrictable) { - - // validate user is not null - if (role == null) - throw new PrivilegeException("Role may not be null!"); - - // get the restriction key - String restrictionKey = restrictable.getRestrictionKey(); - if (restrictionKey == null || restrictionKey.isEmpty()) { - throw new PrivilegeException("The restriction key for the Restrictable is null or empty: " + restrictable); - } - - // get restriction object for users role - Privilege privilege = PrivilegeContainer.getInstance().getModelHandler().getPrivilege(restrictionKey); - - // no restriction object means no privilege - // TODO should default deny/allow policy be configurable? - if (privilege == null) - return false; - - // does this role have privilege for any values? - if (privilege.isAllAllowed()) - return true; - - // get the value on which the action is to be performed - Object object = restrictable.getRestrictionValue(); - - // DefaultRestriction policy expects the restriction value to be a string - if (!(object instanceof String)) { - throw new PrivilegeException(Restrictable.class.getName() + " " + restrictable.getClass().getSimpleName() - + " has returned a non-string restriction value!"); - } - - String restrictionValue = (String) object; - - // first check values not allowed - for (String denied : privilege.getDenyList()) { - if (denied.equals(restrictionValue)) - return false; - } - - // now check values allowed - for (String allowed : privilege.getAllowList()) { - if (allowed.equals(restrictionValue)) - return true; - } - - // default is not allowed - return false; - } -} diff --git a/src/ch/eitchnet/privilege/policy/RestrictionPolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java similarity index 60% rename from src/ch/eitchnet/privilege/policy/RestrictionPolicy.java rename to src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 093ca1468..44c679e5b 100644 --- a/src/ch/eitchnet/privilege/policy/RestrictionPolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -11,13 +11,14 @@ package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg * */ -public interface RestrictionPolicy { +public interface PrivilegePolicy { - public boolean actionAllowed(Role role, Restrictable restrictable); + public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable); } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index d7a6e748f..bfa8a7beb 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -26,6 +26,7 @@ import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -185,4 +186,18 @@ public class PrivilegeTest { PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } + + @Test + public void testPerformRestrictable() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // see if eitch can perform restrictable + Restrictable restrictable = new TestRestrictable(); + boolean actionAllowed = PrivilegeContainer.getInstance().getSessionHandler().actionAllowed(certificate, + restrictable); + org.junit.Assert.assertTrue("eitch may not perform restrictable!", actionAllowed); + } } diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/test/ch/eitchnet/privilege/test/TestRestrictable.java new file mode 100644 index 000000000..1f65beef6 --- /dev/null +++ b/test/ch/eitchnet/privilege/test/TestRestrictable.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.test; + +import ch.eitchnet.privilege.model.Restrictable; + +/** + * @author rvonburg + * + */ +public class TestRestrictable implements Restrictable { + + /**@see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + */ + @Override + public String getPrivilegeName() { + return "Service"; + } + + /**@see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + */ + @Override + public Object getPrivilegeValue() { + return TestRestrictable.class.getName(); + } + +} From 9a6637429fc0ad2e38d3c7a9b228c293f3bf4918 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 2 Aug 2010 23:30:39 +0000 Subject: [PATCH 029/457] --- docs/PrivilegeModelPrivilege.dia | Bin 2063 -> 2230 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/PrivilegeModelPrivilege.dia b/docs/PrivilegeModelPrivilege.dia index f46a49ea6d6e8ef2aef5ea370e9d46035577ac02..85fc14ffb44e591e416dc0963e1dfbbad13aad08 100644 GIT binary patch literal 2230 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 From 55679fc62d2eea4c664fafccb895e2e0c5bf0199 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 8 Aug 2010 20:13:36 +0000 Subject: [PATCH 030/457] --- .../privilege/base/PrivilegeContainer.java | 56 +--- .../base/PrivilegeContainerObject.java | 22 -- .../handler/DefaultPersistenceHandler.java | 77 ++++- .../handler/DefaultPolicyHandler.java | 126 -------- ...dler.java => DefaultPrivilegeHandler.java} | 295 ++++++++++++++++-- .../handler/DefaultSessionHandler.java | 222 ------------- .../privilege/handler/EncryptionHandler.java | 6 +- .../privilege/handler/PersistenceHandler.java | 10 +- .../privilege/handler/PolicyHandler.java | 24 -- ...odelHandler.java => PrivilegeHandler.java} | 62 +++- .../privilege/handler/SessionHandler.java | 61 ---- .../privilege/helper/PrivilegeHelper.java | 2 +- .../privilege/test/PrivilegeTest.java | 38 +-- 13 files changed, 429 insertions(+), 572 deletions(-) delete mode 100644 src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java delete mode 100644 src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java rename src/ch/eitchnet/privilege/handler/{DefaultModelHandler.java => DefaultPrivilegeHandler.java} (60%) delete mode 100644 src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java delete mode 100644 src/ch/eitchnet/privilege/handler/PolicyHandler.java rename src/ch/eitchnet/privilege/handler/{ModelHandler.java => PrivilegeHandler.java} (56%) delete mode 100644 src/ch/eitchnet/privilege/handler/SessionHandler.java diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index acd6ee315..27ae0e4b0 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -16,10 +16,8 @@ import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; -import ch.eitchnet.privilege.handler.PolicyHandler; -import ch.eitchnet.privilege.handler.SessionHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -43,10 +41,8 @@ public class PrivilegeContainer { instance = new PrivilegeContainer(); } - private SessionHandler sessionHandler; - private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; - private ModelHandler modelHandler; + private PrivilegeHandler modelHandler; private String basePath; @@ -61,20 +57,6 @@ public class PrivilegeContainer { // private constructor } - /** - * @return the sessionHandler - */ - public SessionHandler getSessionHandler() { - return sessionHandler; - } - - /** - * @return the policyHandler - */ - public PolicyHandler getPolicyHandler() { - return policyHandler; - } - /** * @return the encryptionHandler */ @@ -85,7 +67,7 @@ public class PrivilegeContainer { /** * @return the modelHandler */ - public ModelHandler getModelHandler() { + public PrivilegeHandler getModelHandler() { return modelHandler; } @@ -115,25 +97,15 @@ public class PrivilegeContainer { String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); - // instantiate session handler - Element sessionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_SESSION); - String sessionHandlerClassName = sessionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - SessionHandler sessionHandler = ClassHelper.instantiateClass(sessionHandlerClassName); - // instantiate encryption handler Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); - // instantiate policy handler - Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); - String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); - - // instantiate model handler + // instantiate privilege handler Element modelHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); String modelHandlerClassName = modelHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - ModelHandler modelHandler = ClassHelper.instantiateClass(modelHandlerClassName); + PrivilegeHandler modelHandler = ClassHelper.instantiateClass(modelHandlerClassName); try { persistenceHandler.initialize(persistenceHandlerElement); @@ -142,12 +114,6 @@ public class PrivilegeContainer { throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + " could not be initialized"); } - try { - sessionHandler.initialize(sessionHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("SessionHandler " + sessionHandlerClassName + " could not be initialized"); - } try { encryptionHandler.initialize(encryptionHandlerElement); } catch (Exception e) { @@ -155,25 +121,15 @@ public class PrivilegeContainer { throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + " could not be initialized"); } - try { - policyHandler.initialize(policyHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); - } try { modelHandler.initialize(modelHandlerElement); - modelHandler.setPersistenceHandler(persistenceHandler); } catch (Exception e) { logger.error(e, e); - throw new PrivilegeException("ModificationHandler " + modelHandlerClassName - + " could not be initialized"); + throw new PrivilegeException("ModificationHandler " + modelHandlerClassName + " could not be initialized"); } // keep references to the handlers this.modelHandler = modelHandler; - this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; - this.policyHandler = policyHandler; } } diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java b/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java deleted file mode 100644 index 88a8b2c2f..000000000 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.base; - -import org.dom4j.Element; - -/** - * @author rvonburg - * - */ -public interface PrivilegeContainerObject { - - public void initialize(Element element); -} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index 427c6322d..5ea040e1c 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -26,6 +26,7 @@ import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -34,6 +35,7 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg @@ -46,6 +48,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private Map userMap; private Map roleMap; private Map privilegeMap; + private Map> policyMap; private long usersFileDate; private boolean userMapDirty; @@ -140,6 +143,24 @@ public class DefaultPersistenceHandler implements PersistenceHandler { return userMap.get(username); } + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPolicy(java.lang.String) + */ + @Override + public PrivilegePolicy getPolicy(String policyName) { + + // get the policies class + Class policyClazz = policyMap.get(policyName); + if (policyClazz == null) { + return null; + } + + // instantiate the policy + PrivilegePolicy policy = ClassHelper.instantiateClass(policyClazz); + + return policy; + } + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist(ch.eitchnet.privilege.model.Certificate) */ @@ -150,7 +171,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); } // get users file @@ -177,7 +198,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); } // get roles file @@ -204,7 +225,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get privileges file name String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); } // get privileges file @@ -254,6 +275,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { roleMap = new HashMap(); userMap = new HashMap(); privilegeMap = new HashMap(); + policyMap = new HashMap>(); // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); @@ -262,14 +284,14 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); } // get roles file File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); if (!rolesFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " + rolesFile.getAbsolutePath()); } @@ -284,14 +306,14 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); } // get users file File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); if (!usersFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " + usersFile.getAbsolutePath()); } @@ -306,14 +328,14 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get privileges file name String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); } // get privileges file File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); if (!privilegesFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " + privilegesFile.getAbsolutePath()); } @@ -325,6 +347,27 @@ public class DefaultPersistenceHandler implements PersistenceHandler { readPrivileges(privilegesRootElement); privilegesFileDate = privilegesFile.lastModified(); + // get policy file name + String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); + if (policyFileName == null || policyFileName.isEmpty()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid"); + } + + // get policy file + File policyFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + policyFileName); + if (!policyFile.exists()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " + + policyFile.getAbsolutePath()); + } + + // parse policy xml file to XML document + Element policiesRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); + + // read policies + readPolicies(policiesRootElement); + userMapDirty = false; roleMapDirty = false; privilegeMapDirty = false; @@ -452,6 +495,22 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } } + /** + * @param policiesRootElement + */ + private void readPolicies(Element policiesRootElement) { + + List policyElements = policiesRootElement.elements(XmlConstants.XML_POLICY); + for (Element policyElement : policyElements) { + String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + + Class clazz = ClassHelper.loadClass(policyClass); + + policyMap.put(policyName, clazz); + } + } + private List toDomPrivileges() { List privilegesAsElements = new ArrayList(privilegeMap.size()); diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java deleted file mode 100644 index 27dff956d..000000000 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.dom4j.Element; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.base.XmlConstants; -import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.helper.ConfigurationHelper; -import ch.eitchnet.privilege.helper.XmlHelper; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Privilege; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.policy.PrivilegePolicy; - -/** - * @author rvonburg - * - */ -public class DefaultPolicyHandler implements PolicyHandler { - - private Map> policyMap; - - /** - * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, - * ch.eitchnet.privilege.model.Restrictable) - */ - @Override - public boolean actionAllowed(Role role, Restrictable restrictable) { - - // user and restrictable must not be null - if (role == null) - throw new PrivilegeException("Role may not be null!"); - else if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); - - // validate PrivilegeName for this restrictable - String privilegeName = restrictable.getPrivilegeName(); - if (privilegeName == null || privilegeName.length() < 3) { - throw new PrivilegeException( - "The PrivilegeName may not be shorter than 3 characters. Invalid Restrictable " - + restrictable.getClass().getName()); - } - - // If the role does not have this privilege, then stop as another role might have this privilege - if (!role.hasPrivilege(privilegeName)) { - return false; - } - - // get the privilege for this restrictable - Privilege privilege = PrivilegeContainer.getInstance().getModelHandler().getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " - + restrictable.getClass().getName()); - } - - // get the policy class configured for this privilege - Class policyClazz = policyMap.get(privilege.getPolicy()); - if (policyClazz == null) { - throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " - + privilegeName); - } - - // instantiate the policy - PrivilegePolicy policy = ClassHelper.instantiateClass(policyClazz); - - // delegate checking to privilege policy - return policy.actionAllowed(role, privilege, restrictable); - } - - /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) - */ - @SuppressWarnings("unchecked") - public void initialize(Element element) { - - // get parameters - Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); - - // get policy file name - String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); - if (policyFileName == null || policyFileName.isEmpty()) { - throw new PrivilegeException("[" + PolicyHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid"); - } - - // get policy file - File policyFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + policyFileName); - if (!policyFile.exists()) { - throw new PrivilegeException("[" + PolicyHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " - + policyFile.getAbsolutePath()); - } - - policyMap = new HashMap>(); - - // parse policy xml file to XML document - Element containerRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); - - List policyElements = containerRootElement.elements(XmlConstants.XML_POLICY); - for (Element policyElement : policyElements) { - String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - - Class clazz = ClassHelper.loadClass(policyClass); - - policyMap.put(policyName, clazz); - } - } -} diff --git a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java similarity index 60% rename from src/ch/eitchnet/privilege/handler/DefaultModelHandler.java rename to src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index cbbeface2..2fca99624 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -10,8 +10,10 @@ package ch.eitchnet.privilege.handler; +import java.util.HashMap; import java.util.HashSet; import java.util.Locale; +import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; @@ -19,35 +21,234 @@ import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.helper.PrivilegeHelper; +import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg * */ -public class DefaultModelHandler implements ModelHandler { +public class DefaultPrivilegeHandler implements PrivilegeHandler { - private static final Logger logger = Logger.getLogger(DefaultModelHandler.class); + private static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); + + private static long lastSessionId; + + private Map sessionMap; private PersistenceHandler persistenceHandler; + private EncryptionHandler encryptionHandler; + private PrivilegeHandler modelHandler; /** - * @see ch.eitchnet.privilege.handler.SessionHandler#setPersistenceHandler(ch.eitchnet.privilege.handler.PersistenceHandler) + * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? + * + * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.Restrictable) + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation */ - public void setPersistenceHandler(PersistenceHandler persistenceHandler) { - this.persistenceHandler = persistenceHandler; + @Override + public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + + // first validate certificate + if (!isCertificateValid(certificate)) { + logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " + + restrictable); + return false; + } + + // restrictable must not be null + if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + // get user object + User user = modelHandler.getUser(certificate.getUsername()); + if (user == null) { + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } + + // default is to not allow the action + // TODO should default deny/allow policy be configurable? + boolean actionAllowed = false; + + // now iterate roles and validate on policies + for (String roleName : user.getRoles()) { + + Role role = modelHandler.getRole(roleName); + if (role == null) { + logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + continue; + } + + actionAllowed = actionAllowed(role, restrictable); + + // if action is allowed, then break iteration as a privilege match has been made + if (actionAllowed) + break; + } + + return actionAllowed; } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, + * ch.eitchnet.privilege.model.Restrictable) + */ + @Override + public boolean actionAllowed(Role role, Restrictable restrictable) { + + // user and restrictable must not be null + if (role == null) + throw new PrivilegeException("Role may not be null!"); + else if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + // validate PrivilegeName for this restrictable + String privilegeName = restrictable.getPrivilegeName(); + if (privilegeName == null || privilegeName.length() < 3) { + throw new PrivilegeException( + "The PrivilegeName may not be shorter than 3 characters. Invalid Restrictable " + + restrictable.getClass().getName()); + } + + // If the role does not have this privilege, then stop as another role might have this privilege + if (!role.hasPrivilege(privilegeName)) { + return false; + } + + // get the privilege for this restrictable + Privilege privilege = modelHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " + + restrictable.getClass().getName()); + } + + // get the policy configured for this privilege + PrivilegePolicy policy = modelHandler.getPolicy(privilege.getPolicy()); + if (policy == null) { + throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " + + privilegeName); + } + + // delegate checking to privilege policy + return policy.actionAllowed(role, privilege, restrictable); + } + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public boolean isCertificateValid(Certificate certificate) { + + // certificate must not be null + if (certificate == null) + throw new PrivilegeException("Certificate may not be null!"); + + // first see if a session exists for this certificate + CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); + if (certificateSessionPair == null) + throw new AccessDeniedException("There is no session information for " + certificate.toString()); + + // validate certificate has not been tampered with + Certificate sessionCertificate = certificateSessionPair.certificate; + if (!sessionCertificate.equals(certificate)) + throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); + + // TODO is validating authToken overkill since the two certificates have already been checked on equality? + // validate authToken from certificate using the sessions authPassword + String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); + if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) + throw new PrivilegeException("Received illegal certificate data for session id " + + certificate.getSessionId()); + + // get user object + User user = modelHandler.getUser(certificateSessionPair.session.getUsername()); + + // if user exists, then certificate is valid + if (user == null) { + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } else { + return true; + } + } + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + @Override + public Certificate authenticate(String username, String password) { + + // both username and password must at least have 3 characters in length + if (username == null || username.length() < 3) + throw new PrivilegeException("The given username is shorter than 3 characters"); + else if (password == null || password.length() < 3) + throw new PrivilegeException("The given password is shorter than 3 characters"); + + // we only work with hashed passwords + String passwordHash = encryptionHandler.convertToHash(password); + + // get user object + User user = modelHandler.getUser(username); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); + + // validate password + if (!user.isPassword(passwordHash)) + throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + + // validate if user is allowed to login + if (user.getState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + throw new PrivilegeException("User " + username + " does not have any roles defined!"); + } + + // get 2 auth tokens + String authToken = encryptionHandler.nextToken(); + String authPassword = encryptionHandler.nextToken(); + + // get next session id + String sessionId = nextSessionId(); + + // create certificate + Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + + // create and save a new session + Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System + .currentTimeMillis()); + sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + + // log + logger.info("Authenticated: " + session); + + // return the certificate + return certificate; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.PrivilegeRep) */ @Override @@ -69,7 +270,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.RoleRep) */ @Override @@ -90,7 +291,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.UserRep, java.lang.String) */ @Override @@ -108,7 +309,7 @@ public class DefaultModelHandler implements ModelHandler { if (password == null) passwordHash = null; else - passwordHash = PrivilegeContainer.getInstance().getEncryptionHandler().convertToHash(password); + passwordHash = encryptionHandler.convertToHash(password); // create new user User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep @@ -119,7 +320,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -162,7 +363,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -205,7 +406,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#persist(ch.eitchnet.privilege.model.Certificate) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#persist(ch.eitchnet.privilege.model.Certificate) */ @Override public boolean persist(Certificate certificate) { @@ -221,7 +422,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, * java.lang.String) */ @Override @@ -245,7 +446,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -281,7 +482,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removeRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRole(ch.eitchnet.privilege.model.Certificate, * java.lang.String) */ @Override @@ -305,7 +506,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -342,7 +543,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removeUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeUser(ch.eitchnet.privilege.model.Certificate, * java.lang.String) */ @Override @@ -366,7 +567,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, * java.lang.String, boolean) */ @Override @@ -401,7 +602,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Set) */ @Override @@ -429,7 +630,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Set) */ @Override @@ -457,7 +658,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -485,7 +686,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Locale) */ @Override @@ -513,7 +714,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserName(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserName(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String, java.lang.String) */ @Override @@ -541,7 +742,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -561,7 +762,7 @@ public class DefaultModelHandler implements ModelHandler { } // hash password - String passwordHash = PrivilegeContainer.getInstance().getEncryptionHandler().convertToHash(password); + String passwordHash = encryptionHandler.convertToHash(password); // create new user User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user @@ -572,7 +773,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserState(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserState(ch.eitchnet.privilege.model.Certificate, * java.lang.String, ch.eitchnet.privilege.model.UserState) */ @Override @@ -600,15 +801,17 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#initialize(org.dom4j.Element) */ @Override public void initialize(Element element) { - // nothing to initialize + + lastSessionId = 0l; + sessionMap = new HashMap(); } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#getPrivilege(java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) */ @Override public Privilege getPrivilege(String privilegeName) { @@ -616,7 +819,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#getRole(java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) */ @Override public Role getRole(String roleName) { @@ -624,10 +827,40 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#getUser(java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getUser(java.lang.String) */ @Override public User getUser(String username) { return persistenceHandler.getUser(username); } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPolicy(java.lang.String) + */ + @Override + public PrivilegePolicy getPolicy(String policyName) { + return persistenceHandler.getPolicy(policyName); + } + + /** + * @return a new session id + */ + private synchronized String nextSessionId() { + return Long.toString(++lastSessionId % Long.MAX_VALUE); + } + + /** + * An internal class used to keep a record of sessions with the certificate + * + * @author rvonburg + */ + private class CertificateSessionPair { + private Session session; + private Certificate certificate; + + public CertificateSessionPair(Session session, Certificate certificate) { + this.session = session; + this.certificate = certificate; + } + } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java deleted file mode 100644 index 4e38fc69a..000000000 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.log4j.Logger; -import org.dom4j.Element; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.Session; -import ch.eitchnet.privilege.model.internal.User; - -/** - * @author rvonburg - * - */ -public class DefaultSessionHandler implements SessionHandler { - - private static final Logger logger = Logger.getLogger(DefaultSessionHandler.class); - - private static long lastSessionId; - - private Map sessionMap; - - /** - * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? - * - * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.Restrictable) - * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation - */ - @Override - public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { - - // first validate certificate - if (!isCertificateValid(certificate)) { - logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " - + restrictable); - return false; - } - - // restrictable must not be null - if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); - - PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); - - // get user object - User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); - if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); - } - - // default is to not allow the action - // TODO should default deny/allow policy be configurable? - boolean actionAllowed = false; - - // now iterate roles and validate on policy handler - PolicyHandler policyHandler = privilegeContainer.getPolicyHandler(); - for (String roleName : user.getRoles()) { - - Role role = PrivilegeContainer.getInstance().getModelHandler().getRole(roleName); - if (role == null) { - logger.error("No role is defined with name " + roleName + " which is configured for user " + user); - continue; - } - - actionAllowed = policyHandler.actionAllowed(role, restrictable); - - // if action is allowed, then break iteration as a privilege match has been made - if (actionAllowed) - break; - } - - return actionAllowed; - } - - /** - * @see ch.eitchnet.privilege.handler.SessionHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) - */ - @Override - public boolean isCertificateValid(Certificate certificate) { - - // certificate must not be null - if (certificate == null) - throw new PrivilegeException("Certificate may not be null!"); - - // first see if a session exists for this certificate - CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); - if (certificateSessionPair == null) - throw new AccessDeniedException("There is no session information for " + certificate.toString()); - - // validate certificate has not been tampered with - Certificate sessionCertificate = certificateSessionPair.certificate; - if (!sessionCertificate.equals(certificate)) - throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - - // TODO is validating authToken overkill since the two certificates have already been checked on equality? - // validate authToken from certificate using the sessions authPassword - String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); - if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) - throw new PrivilegeException("Received illegal certificate data for session id " - + certificate.getSessionId()); - - // get user object - User user = PrivilegeContainer.getInstance().getModelHandler().getUser( - certificateSessionPair.session.getUsername()); - - // if user exists, then certificate is valid - if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); - } else { - return true; - } - } - - /** - * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) - * - * @throws AccessDeniedException - * if the user credentials are not valid - */ - @Override - public Certificate authenticate(String username, String password) { - - // both username and password must at least have 3 characters in length - if (username == null || username.length() < 3) - throw new PrivilegeException("The given username is shorter than 3 characters"); - else if (password == null || password.length() < 3) - throw new PrivilegeException("The given password is shorter than 3 characters"); - - EncryptionHandler encryptionHandler = PrivilegeContainer.getInstance().getEncryptionHandler(); - - // we only work with hashed passwords - String passwordHash = encryptionHandler.convertToHash(password); - - // get user object - User user = PrivilegeContainer.getInstance().getModelHandler().getUser(username); - // no user means no authentication - if (user == null) - throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); - - // validate password - if (!user.isPassword(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); - - // validate if user is allowed to login - if (user.getState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); - - // validate user has at least one role - if (user.getRoles().isEmpty()) { - throw new PrivilegeException("User " + username + " does not have any roles defined!"); - } - - // get 2 auth tokens - String authToken = encryptionHandler.nextToken(); - String authPassword = encryptionHandler.nextToken(); - - // get next session id - String sessionId = nextSessionId(); - - // create certificate - Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); - - // create and save a new session - Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System - .currentTimeMillis()); - sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); - - // log - logger.info("Authenticated: " + session); - - // return the certificate - return certificate; - } - - private synchronized String nextSessionId() { - return Long.toString(++lastSessionId % Long.MAX_VALUE); - } - - /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) - */ - public void initialize(Element element) { - - lastSessionId = 0l; - sessionMap = new HashMap(); - - } - - private class CertificateSessionPair { - private Session session; - private Certificate certificate; - - public CertificateSessionPair(Session session, Certificate certificate) { - this.session = session; - this.certificate = certificate; - } - } -} diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index c26be38a4..83df25437 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -10,15 +10,17 @@ package ch.eitchnet.privilege.handler; -import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import org.dom4j.Element; /** * @author rvonburg * */ -public interface EncryptionHandler extends PrivilegeContainerObject{ +public interface EncryptionHandler { public String nextToken(); public String convertToHash(String string); + + public void initialize(Element element); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 117130490..9f7e2d08b 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,17 +10,19 @@ package ch.eitchnet.privilege.handler; -import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import org.dom4j.Element; + import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg * */ -public interface PersistenceHandler extends PrivilegeContainerObject { +public interface PersistenceHandler { public User getUser(String username); @@ -40,5 +42,9 @@ public interface PersistenceHandler extends PrivilegeContainerObject { public Privilege removePrivilege(String privilegeName); + public PrivilegePolicy getPolicy(String policyName); + public boolean persist(Certificate certificate); + + public void initialize(Element element); } diff --git a/src/ch/eitchnet/privilege/handler/PolicyHandler.java b/src/ch/eitchnet/privilege/handler/PolicyHandler.java deleted file mode 100644 index 5c55ad7a6..000000000 --- a/src/ch/eitchnet/privilege/handler/PolicyHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import ch.eitchnet.privilege.base.PrivilegeContainerObject; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Role; - -/** - * @author rvonburg - * - */ -public interface PolicyHandler extends PrivilegeContainerObject { - - public boolean actionAllowed(Role role, Restrictable restrictable); -} diff --git a/src/ch/eitchnet/privilege/handler/ModelHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java similarity index 56% rename from src/ch/eitchnet/privilege/handler/ModelHandler.java rename to src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index e8fa1256e..ea27acac8 100644 --- a/src/ch/eitchnet/privilege/handler/ModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -13,23 +13,75 @@ package ch.eitchnet.privilege.handler; import java.util.Locale; import java.util.Set; -import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import org.dom4j.Element; + +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg * */ -public interface ModelHandler extends PrivilegeContainerObject { +public interface PrivilegeHandler { - public void setPersistenceHandler(PersistenceHandler persistenceHandler); + /** + * @param certificate + * @param restrictable + * + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public boolean actionAllowed(Certificate certificate, Restrictable restrictable); + + /** + * @param role + * @param restrictable + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public boolean actionAllowed(Role role, Restrictable restrictable); + + /** + * @param certificate + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public boolean isCertificateValid(Certificate certificate); + + /** + * @param username + * @param password + * + * @return + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + public Certificate authenticate(String username, String password); public User getUser(String username); @@ -73,5 +125,9 @@ public interface ModelHandler extends PrivilegeContainerObject { public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); + public PrivilegePolicy getPolicy(String policyName); + public boolean persist(Certificate certificate); + + public void initialize(Element element); } diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java deleted file mode 100644 index 10e0b4fa4..000000000 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import ch.eitchnet.privilege.base.PrivilegeContainerObject; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.User; - -/** - * @author rvonburg - * - */ -public interface SessionHandler extends PrivilegeContainerObject { - - /** - * @param certificate - * @param restrictable - * - * @return - * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public boolean actionAllowed(Certificate certificate, Restrictable restrictable); - - /** - * @param certificate - * @return - * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public boolean isCertificateValid(Certificate certificate); - - /** - * @param username - * @param password - * - * @return - * - * @throws AccessDeniedException - * if the user credentials are not valid - */ - public Certificate authenticate(String username, String password); -} diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java index 32be4f883..06029806e 100644 --- a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java +++ b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java @@ -24,7 +24,7 @@ public class PrivilegeHelper { public static boolean isUserPrivilegeAdmin(Certificate certificate) { // validate certificate - if (!PrivilegeContainer.getInstance().getSessionHandler().isCertificateValid(certificate)) { + if (!PrivilegeContainer.getInstance().getModelHandler().isCertificateValid(certificate)) { throw new PrivilegeException("Certificate " + certificate + " is not valid!"); } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index bfa8a7beb..2f52de634 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -22,7 +22,7 @@ import org.junit.BeforeClass; import org.junit.Test; import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.handler.ModelHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -59,7 +59,7 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -67,24 +67,24 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", "123"); + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "123"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", null); + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test public void testAddUserBobWithPW() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); - ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); // let's add a new user bob UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); @@ -104,16 +104,16 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); + PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); } @Test public void testEnableUserBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); - ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); modelHandler.setUserState(certificate, "bob", UserState.ENABLED); } @@ -125,7 +125,7 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -133,17 +133,17 @@ public class PrivilegeTest { @Test public void testAddUserRoleToBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); - ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); modelHandler.addRoleToUser(certificate, "bob", "user"); } @Test public void testAuthAsBob() throws Exception { - PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); + PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); } /** @@ -154,7 +154,7 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); @@ -167,17 +167,17 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); - ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); modelHandler.addRoleToUser(certificate, "bob", PrivilegeContainer.PRIVILEGE_ADMIN_ROLE); } @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); @@ -190,13 +190,13 @@ public class PrivilegeTest { @Test public void testPerformRestrictable() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); - boolean actionAllowed = PrivilegeContainer.getInstance().getSessionHandler().actionAllowed(certificate, + boolean actionAllowed = PrivilegeContainer.getInstance().getModelHandler().actionAllowed(certificate, restrictable); org.junit.Assert.assertTrue("eitch may not perform restrictable!", actionAllowed); } From 7c0c86fe66487ba6473bd9fb5f1b52d68366e9ac Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 18 Sep 2010 20:00:20 +0000 Subject: [PATCH 031/457] --- .../privilege/base/PrivilegeContainer.java | 135 -- .../handler/DefaultEncryptionHandler.java | 13 +- .../handler/DefaultPrivilegeHandler.java | 1224 ++++++++--------- .../privilege/handler/EncryptionHandler.java | 14 +- .../privilege/handler/PersistenceHandler.java | 50 +- .../privilege/handler/PrivilegeHandler.java | 269 +++- ...andler.java => XmlPersistenceHandler.java} | 52 +- .../helper/BootstrapConfigurationHelper.java | 43 +- .../privilege/helper/ConfigurationHelper.java | 87 +- .../privilege/helper/PrivilegeHelper.java | 47 - .../{base => helper}/XmlConstants.java | 7 +- .../privilege/model/PrivilegeRep.java | 5 +- src/ch/eitchnet/privilege/model/UserRep.java | 18 +- .../eitchnet/privilege/model/UserState.java | 2 +- .../privilege/model/internal/Privilege.java | 3 +- .../privilege/model/internal/Role.java | 3 +- .../privilege/model/internal/User.java | 23 +- 17 files changed, 1041 insertions(+), 954 deletions(-) delete mode 100644 src/ch/eitchnet/privilege/base/PrivilegeContainer.java rename src/ch/eitchnet/privilege/handler/{DefaultPersistenceHandler.java => XmlPersistenceHandler.java} (92%) delete mode 100644 src/ch/eitchnet/privilege/helper/PrivilegeHelper.java rename src/ch/eitchnet/privilege/{base => helper}/XmlConstants.java (90%) diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java deleted file mode 100644 index 27ae0e4b0..000000000 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.base; - -import java.io.File; - -import org.apache.log4j.Logger; -import org.dom4j.Element; - -import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.PersistenceHandler; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.helper.XmlHelper; -import ch.eitchnet.privilege.i18n.PrivilegeException; - -/** - * @author rvonburg - */ -public class PrivilegeContainer { - - /** - * This is the role users must have, if they can modify the {@link PrivilegeContainer} and its objects - */ - public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; - public static final String PRIVILEGE_CONTAINER_FILE = "PrivilegeContainer.xml"; - - private static final Logger logger = Logger.getLogger(PrivilegeContainer.class); - - private static final PrivilegeContainer instance; - - static { - instance = new PrivilegeContainer(); - } - - private EncryptionHandler encryptionHandler; - private PrivilegeHandler modelHandler; - - private String basePath; - - public static PrivilegeContainer getInstance() { - return instance; - } - - /** - * private constructor to force singleton - */ - private PrivilegeContainer() { - // private constructor - } - - /** - * @return the encryptionHandler - */ - public EncryptionHandler getEncryptionHandler() { - return encryptionHandler; - } - - /** - * @return the modelHandler - */ - public PrivilegeHandler getModelHandler() { - return modelHandler; - } - - /** - * @return the basePath - */ - public String getBasePath() { - return basePath; - } - - public void initialize(File privilegeContainerXml) { - - // make sure file exists - if (!privilegeContainerXml.exists()) { - throw new PrivilegeException("Privilige file does not exist at path " - + privilegeContainerXml.getAbsolutePath()); - } - - // set base path from privilege container xml - basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); - - // parse container xml file to XML document - Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); - - // instantiate persistence handler - Element persistenceHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); - String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); - - // instantiate encryption handler - Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); - String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); - - // instantiate privilege handler - Element modelHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); - String modelHandlerClassName = modelHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PrivilegeHandler modelHandler = ClassHelper.instantiateClass(modelHandlerClassName); - - try { - persistenceHandler.initialize(persistenceHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement - + " could not be initialized"); - } - try { - encryptionHandler.initialize(encryptionHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName - + " could not be initialized"); - } - try { - modelHandler.initialize(modelHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("ModificationHandler " + modelHandlerClassName + " could not be initialized"); - } - - // keep references to the handlers - this.modelHandler = modelHandler; - this.encryptionHandler = encryptionHandler; - } -} diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 8cb679885..5fd3c4664 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -16,11 +16,9 @@ import java.security.SecureRandom; import java.util.Map; import org.apache.log4j.Logger; -import org.dom4j.Element; -import ch.eitchnet.privilege.base.XmlConstants; -import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.helper.EncryptionHelper; +import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.i18n.PrivilegeException; /** @@ -62,16 +60,13 @@ public class DefaultEncryptionHandler implements EncryptionHandler { } /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) */ - public void initialize(Element element) { + @Override + public void initialize(Map parameterMap) { secureRandom = new SecureRandom(); - // get parameters - Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); - // get hash algorithm parameters hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); if (hashAlgorithm == null || hashAlgorithm.isEmpty()) { diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2fca99624..1119e8aff 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -17,10 +17,7 @@ import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; -import org.dom4j.Element; -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.helper.PrivilegeHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -49,7 +46,565 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private PersistenceHandler persistenceHandler; private EncryptionHandler encryptionHandler; - private PrivilegeHandler modelHandler; + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) + */ + @Override + public PrivilegeRep getPrivilege(String privilegeName) { + return persistenceHandler.getPrivilege(privilegeName).asPrivilegeRep(); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) + */ + @Override + public RoleRep getRole(String roleName) { + return persistenceHandler.getRole(roleName).asRoleRep(); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getUser(java.lang.String) + */ + @Override + public UserRep getUser(String username) { + return persistenceHandler.getUser(username).asUserRep(); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPolicy(java.lang.String) + */ + @Override + public PrivilegePolicy getPolicy(String policyName) { + return persistenceHandler.getPolicy(policyName); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.PrivilegeRep) + */ + @Override + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // create a new privilege + Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep + .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); + + // delegate to persistence handler + persistenceHandler.addOrReplacePrivilege(privilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.RoleRep) + */ + @Override + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // create new role + Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); + + // delegate to persistence handler + persistenceHandler.addOrReplaceRole(role); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.UserRep, java.lang.String) + */ + @Override + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // validate password meets basic requirements + validatePassword(password); + + // hash password + String passwordHash; + if (password == null) + passwordHash = null; + else + passwordHash = encryptionHandler.convertToHash(password); + + // create new user + User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep + .getUserState(), userRep.getRoles(), userRep.getLocale()); + + // delegate to persistence handler + persistenceHandler.addOrReplaceUser(user); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get role + Role role = persistenceHandler.getRole(roleName); + if (role == null) { + throw new PrivilegeException("Role " + roleName + " does not exist!"); + } + + // ignore if role already has this privilege + Set currentPrivileges = role.getPrivileges(); + if (currentPrivileges.contains(roleName)) { + logger.error("Role " + roleName + " already has privilege " + privilegeName); + return; + } + + // validate that privilege exists + if (getPrivilege(privilegeName) == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist and can not be added to role " + + roleName); + } + + // create new role with the additional privilege + Set newPrivileges = new HashSet(currentPrivileges); + newPrivileges.add(roleName); + + Role newRole = new Role(role.getName(), newPrivileges); + + // delegate role replacement to persistence handler + persistenceHandler.addOrReplaceRole(newRole); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void addRoleToUser(Certificate certificate, String username, String roleName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get user + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // ignore if user already has role + Set currentRoles = user.getRoles(); + if (currentRoles.contains(roleName)) { + logger.error("User " + username + " already has role " + roleName); + return; + } + + // validate that role exists + if (getRole(roleName) == null) { + throw new PrivilegeException("Role " + roleName + " doest not exist!"); + } + + // create new user + Set newRoles = new HashSet(currentRoles); + newRoles.add(roleName); + + User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user + .getState(), newRoles, user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // delegate privilege removal to persistence handler + Privilege removedPrivilege = persistenceHandler.removePrivilege(privilegeName); + + // return privilege rep if it was removed + if (removedPrivilege != null) + return removedPrivilege.asPrivilegeRep(); + else + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get role + Role role = persistenceHandler.getRole(roleName); + if (role == null) { + throw new PrivilegeException("Role " + roleName + " does not exist!"); + } + + // ignore if role does not have privilege + Set currentPrivileges = role.getPrivileges(); + if (!currentPrivileges.contains(privilegeName)) { + logger.error("Role " + roleName + " doest not have privilege " + privilegeName); + return; + } + + // create new role + Set newPrivileges = new HashSet(currentPrivileges); + newPrivileges.remove(privilegeName); + Role newRole = new Role(role.getName(), newPrivileges); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceRole(newRole); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public RoleRep removeRole(Certificate certificate, String roleName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // delegate role removal to persistence handler + Role removedRole = persistenceHandler.removeRole(roleName); + + // return role rep if it was removed + if (removedRole != null) + return removedRole.asRoleRep(); + else + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void removeRoleFromUser(Certificate certificate, String username, String roleName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // ignore if user does not have role + Set currentRoles = user.getRoles(); + if (!currentRoles.contains(roleName)) { + logger.error("User " + user + " does not have role " + roleName); + return; + } + + // create new user + Set newRoles = new HashSet(currentRoles); + newRoles.remove(roleName); + User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user + .getState(), newRoles, user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public UserRep removeUser(Certificate certificate, String username) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // delegate user removal to persistence handler + User removedUser = persistenceHandler.removeUser(username); + + // return user rep if it was removed + if (removedUser != null) + return removedUser.asUserRep(); + else + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, boolean) + */ + @Override + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get Privilege + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + + // ignore if privilege is already set to argument + if (privilege.isAllAllowed() == allAllowed) { + logger.error("Privilege " + privilegeName + " is already set to " + + (allAllowed ? "all allowed" : "not all allowed")); + return; + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, privilege + .getDenyList(), privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.Set) + */ + @Override + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get Privilege + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), + privilege.getDenyList(), allowList); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.Set) + */ + @Override + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get Privilege + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), + denyList, privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get Privilege + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), privilege + .getDenyList(), privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.Locale) + */ + @Override + public void setUserLocaleState(Certificate certificate, String username, Locale locale) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // create new user + User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user + .getState(), user.getRoles(), locale); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserName(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void setUserName(Certificate certificate, String username, String firstname, String surname) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // create new user + User newUser = new User(user.getUsername(), user.getPassword(), firstname, surname, user.getState(), user + .getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void setUserPassword(Certificate certificate, String username, String password) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // hash password + String passwordHash = encryptionHandler.convertToHash(password); + + // create new user + User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user + .getState(), user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserState(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, ch.eitchnet.privilege.model.UserState) + */ + @Override + public void setUserState(Certificate certificate, String username, UserState state) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // create new user + User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), state, + user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + @Override + public Certificate authenticate(String username, String password) { + + // both username and password must at least have 3 characters in length + if (username == null || username.length() < 3) + throw new PrivilegeException("The given username is shorter than 3 characters"); + else if (password == null || password.length() < 3) + throw new PrivilegeException("The given password is shorter than 3 characters"); + + // we only work with hashed passwords + String passwordHash = encryptionHandler.convertToHash(password); + + // get user object + User user = persistenceHandler.getUser(username); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); + + // validate password + if (!user.isPassword(passwordHash)) + throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + + // validate if user is allowed to login + if (user.getState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + throw new PrivilegeException("User " + username + " does not have any roles defined!"); + } + + // get 2 auth tokens + String authToken = encryptionHandler.nextToken(); + String authPassword = encryptionHandler.nextToken(); + + // get next session id + String sessionId = nextSessionId(); + + // create certificate + Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + + // create and save a new session + Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System + .currentTimeMillis()); + sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + + // log + logger.info("Authenticated: " + session); + + // return the certificate + return certificate; + } /** * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? @@ -76,7 +631,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Restrictable may not be null!"); // get user object - User user = modelHandler.getUser(certificate.getUsername()); + User user = persistenceHandler.getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); @@ -89,7 +644,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // now iterate roles and validate on policies for (String roleName : user.getRoles()) { - Role role = modelHandler.getRole(roleName); + Role role = persistenceHandler.getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -132,14 +687,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get the privilege for this restrictable - Privilege privilege = modelHandler.getPrivilege(privilegeName); + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " + restrictable.getClass().getName()); } // get the policy configured for this privilege - PrivilegePolicy policy = modelHandler.getPolicy(privilege.getPolicy()); + PrivilegePolicy policy = this.getPolicy(privilege.getPolicy()); if (policy == null) { throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " + privilegeName); @@ -177,7 +732,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { + certificate.getSessionId()); // get user object - User user = modelHandler.getUser(certificateSessionPair.session.getUsername()); + User user = persistenceHandler.getUser(certificateSessionPair.session.getUsername()); // if user exists, then certificate is valid if (user == null) { @@ -189,220 +744,40 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) - * - * @throws AccessDeniedException - * if the user credentials are not valid + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validateIsPrivilegeAdmin(ch.eitchnet.privilege.model.Certificate) */ @Override - public Certificate authenticate(String username, String password) { + public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { - // both username and password must at least have 3 characters in length - if (username == null || username.length() < 3) - throw new PrivilegeException("The given username is shorter than 3 characters"); - else if (password == null || password.length() < 3) - throw new PrivilegeException("The given password is shorter than 3 characters"); - - // we only work with hashed passwords - String passwordHash = encryptionHandler.convertToHash(password); + // validate certificate + if (!this.isCertificateValid(certificate)) { + throw new PrivilegeException("Certificate " + certificate + " is not valid!"); + } // get user object - User user = modelHandler.getUser(username); - // no user means no authentication - if (user == null) - throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); - - // validate password - if (!user.isPassword(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); - - // validate if user is allowed to login - if (user.getState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); - - // validate user has at least one role - if (user.getRoles().isEmpty()) { - throw new PrivilegeException("User " + username + " does not have any roles defined!"); - } - - // get 2 auth tokens - String authToken = encryptionHandler.nextToken(); - String authPassword = encryptionHandler.nextToken(); - - // get next session id - String sessionId = nextSessionId(); - - // create certificate - Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); - - // create and save a new session - Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System - .currentTimeMillis()); - sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); - - // log - logger.info("Authenticated: " + session); - - // return the certificate - return certificate; - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.PrivilegeRep) - */ - @Override - public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // create a new privilege - Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep - .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); - - // delegate to persistence handler - persistenceHandler.addOrReplacePrivilege(privilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.RoleRep) - */ - @Override - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // create new role - Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); - - // delegate to persistence handler - persistenceHandler.addOrReplaceRole(role); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep, java.lang.String) - */ - @Override - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // hash password - String passwordHash; - if (password == null) - passwordHash = null; - else - passwordHash = encryptionHandler.convertToHash(password); - - // create new user - User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep - .getUserState(), userRep.getRoles(), userRep.getLocale()); - - // delegate to persistence handler - persistenceHandler.addOrReplaceUser(user); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get role - Role role = getRole(roleName); - if (role == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); - } - - // ignore if role already has this privilege - Set currentPrivileges = role.getPrivileges(); - if (currentPrivileges.contains(roleName)) { - logger.error("Role " + roleName + " already has privilege " + privilegeName); - return; - } - - // validate that privilege exists - if (getPrivilege(privilegeName) == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist and can not be added to role " - + roleName); - } - - // create new role with the additional privilege - Set newPrivileges = new HashSet(currentPrivileges); - newPrivileges.add(roleName); - - Role newRole = new Role(role.getName(), newPrivileges); - - // delegate role replacement to persistence handler - persistenceHandler.addOrReplaceRole(newRole); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void addRoleToUser(Certificate certificate, String username, String roleName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get user - User user = getUser(username); + User user = persistenceHandler.getUser(certificate.getUsername()); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " + + certificate); } - // ignore if user already has role - Set currentRoles = user.getRoles(); - if (currentRoles.contains(roleName)) { - logger.error("User " + username + " already has role " + roleName); - return; + // validate user has PrivilegeAdmin role + if (!user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { + throw new AccessDeniedException("User does not have " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + + " role! Certificate: " + certificate); } + } - // validate that role exists - if (getRole(roleName) == null) { - throw new PrivilegeException("Role " + roleName + " doest not exist!"); + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(java.lang.String) + */ + @Override + public void validatePassword(String password) throws PrivilegeException { + + if (password == null || password.isEmpty()) { + throw new PrivilegeException("A password may not be empty!"); } - - // create new user - Set newRoles = new HashSet(currentRoles); - newRoles.add(roleName); - - User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user - .getSurname(), user.getState(), newRoles, user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); } /** @@ -412,436 +787,26 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public boolean persist(Certificate certificate) { // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return false; - } + validateIsPrivilegeAdmin(certificate); return persistenceHandler.persist(certificate); } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#initialize(java.util.Map, + * ch.eitchnet.privilege.handler.EncryptionHandler, ch.eitchnet.privilege.handler.PersistenceHandler) */ @Override - public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { + public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, + PersistenceHandler persistenceHandler) { - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return null; - } - - // delegate privilege removal to persistence handler - Privilege removedPrivilege = persistenceHandler.removePrivilege(privilegeName); - - // return privilege rep if it was removed - if (removedPrivilege != null) - return removedPrivilege.asPrivilegeRep(); - else - return null; - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get role - Role role = getRole(roleName); - if (role == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); - } - - // ignore if role does not have privilege - Set currentPrivileges = role.getPrivileges(); - if (!currentPrivileges.contains(privilegeName)) { - logger.error("Role " + roleName + " doest not have privilege " + privilegeName); - return; - } - - // create new role - Set newPrivileges = new HashSet(currentPrivileges); - newPrivileges.remove(privilegeName); - Role newRole = new Role(role.getName(), newPrivileges); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceRole(newRole); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ - @Override - public RoleRep removeRole(Certificate certificate, String roleName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return null; - } - - // delegate role removal to persistence handler - Role removedRole = persistenceHandler.removeRole(roleName); - - // return role rep if it was removed - if (removedRole != null) - return removedRole.asRoleRep(); - else - return null; - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void removeRoleFromUser(Certificate certificate, String username, String roleName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // ignore if user does not have role - Set currentRoles = user.getRoles(); - if (!currentRoles.contains(roleName)) { - logger.error("User " + user + " does not have role " + roleName); - return; - } - - // create new user - Set newRoles = new HashSet(currentRoles); - newRoles.remove(roleName); - User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user - .getSurname(), user.getState(), newRoles, user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ - @Override - public UserRep removeUser(Certificate certificate, String username) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return null; - } - - // delegate user removal to persistence handler - User removedUser = persistenceHandler.removeUser(username); - - // return user rep if it was removed - if (removedUser != null) - return removedUser.asUserRep(); - else - return null; - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, boolean) - */ - @Override - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get Privilege - Privilege privilege = getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // ignore if privilege is already set to argument - if (privilege.isAllAllowed() == allAllowed) { - logger.error("Privilege " + privilegeName + " is already set to " - + (allAllowed ? "all allowed" : "not all allowed")); - return; - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, privilege - .getDenyList(), privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Set) - */ - @Override - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get Privilege - Privilege privilege = getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), - privilege.getDenyList(), allowList); - - // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Set) - */ - @Override - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get Privilege - Privilege privilege = getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), - denyList, privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get Privilege - Privilege privilege = getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), privilege - .getDenyList(), privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Locale) - */ - @Override - public void setUserLocaleState(Certificate certificate, String username, Locale locale) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // create new user - User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user - .getSurname(), user.getState(), user.getRoles(), locale); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserName(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String, java.lang.String) - */ - @Override - public void setUserName(Certificate certificate, String username, String firstname, String surname) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // create new user - User newUser = new User(user.getUsername(), user.getPassword(certificate), firstname, surname, user.getState(), - user.getRoles(), user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void setUserPassword(Certificate certificate, String username, String password) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // hash password - String passwordHash = encryptionHandler.convertToHash(password); - - // create new user - User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user - .getState(), user.getRoles(), user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserState(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, ch.eitchnet.privilege.model.UserState) - */ - @Override - public void setUserState(Certificate certificate, String username, UserState state) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // create new user - User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user - .getSurname(), state, user.getRoles(), user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#initialize(org.dom4j.Element) - */ - @Override - public void initialize(Element element) { + this.encryptionHandler = encryptionHandler; + this.persistenceHandler = persistenceHandler; lastSessionId = 0l; sessionMap = new HashMap(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) - */ - @Override - public Privilege getPrivilege(String privilegeName) { - return persistenceHandler.getPrivilege(privilegeName); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) - */ - @Override - public Role getRole(String roleName) { - return persistenceHandler.getRole(roleName); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getUser(java.lang.String) - */ - @Override - public User getUser(String username) { - return persistenceHandler.getUser(username); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPolicy(java.lang.String) - */ - @Override - public PrivilegePolicy getPolicy(String policyName) { - return persistenceHandler.getPolicy(policyName); - } - /** * @return a new session id */ @@ -863,4 +828,5 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.certificate = certificate; } } + } diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index 83df25437..9efa5f337 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -10,7 +10,7 @@ package ch.eitchnet.privilege.handler; -import org.dom4j.Element; +import java.util.Map; /** * @author rvonburg @@ -18,9 +18,19 @@ import org.dom4j.Element; */ public interface EncryptionHandler { + /** + * @return + */ public String nextToken(); + /** + * @param string + * @return + */ public String convertToHash(String string); - public void initialize(Element element); + /** + * @param parameterMap + */ + public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 9f7e2d08b..5fcd9c7a6 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,7 +10,7 @@ package ch.eitchnet.privilege.handler; -import org.dom4j.Element; +import java.util.Map; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.internal.Privilege; @@ -24,27 +24,71 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public interface PersistenceHandler { + /** + * @param username + * @return + */ public User getUser(String username); + /** + * @param user + */ public void addOrReplaceUser(User user); + /** + * @param username + * @return + */ public User removeUser(String username); + /** + * @param roleName + * @return + */ public Role getRole(String roleName); + /** + * @param role + */ public void addOrReplaceRole(Role role); + /** + * @param roleName + * @return + */ public Role removeRole(String roleName); + /** + * @param privilegeName + * @return + */ public Privilege getPrivilege(String privilegeName); + /** + * @param privilege + */ public void addOrReplacePrivilege(Privilege privilege); + /** + * @param privilegeName + * @return + */ public Privilege removePrivilege(String privilegeName); + /** + * @param policyName + * @return + */ public PrivilegePolicy getPolicy(String policyName); + /** + * @param certificate + * @return + */ public boolean persist(Certificate certificate); - - public void initialize(Element element); + + /** + * @param parameterMap + */ + public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index ea27acac8..74f27bd70 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -11,11 +11,9 @@ package ch.eitchnet.privilege.handler; import java.util.Locale; +import java.util.Map; import java.util.Set; -import org.dom4j.Element; - -import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -23,9 +21,7 @@ import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** @@ -34,6 +30,178 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public interface PrivilegeHandler { + /** + * This is the role users must have, if they are allowed to modify objects + */ + public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; + + /** + * @param username + * + * @return + */ + public UserRep getUser(String username); + + /** + * @param roleName + * + * @return + */ + public RoleRep getRole(String roleName); + + /** + * @param privilegeName + * + * @return + */ + public PrivilegeRep getPrivilege(String privilegeName); + + /** + * @param policyName + * + * @return + */ + public PrivilegePolicy getPolicy(String policyName); + + /** + * @param certificate + * @param username + * + * @return + */ + public UserRep removeUser(Certificate certificate, String username); + + /** + * @param certificate + * @param username + * @param roleName + */ + public void removeRoleFromUser(Certificate certificate, String username, String roleName); + + /** + * @param certificate + * @param roleName + * + * @return + */ + public RoleRep removeRole(Certificate certificate, String roleName); + + /** + * @param certificate + * @param roleName + * @param privilegeName + */ + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); + + /** + * @param certificate + * @param privilegeName + * + * @return + */ + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); + + /** + * @param certificate + * @param userRep + * @param password + */ + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); + + /** + * @param certificate + * @param roleRep + */ + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); + + /** + * @param certificate + * @param privilegeRep + */ + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); + + /** + * @param certificate + * @param username + * @param roleName + */ + public void addRoleToUser(Certificate certificate, String username, String roleName); + + /** + * @param certificate + * @param roleName + * @param privilegeName + */ + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); + + /** + * @param certificate + * @param username + * @param password + */ + public void setUserPassword(Certificate certificate, String username, String password); + + /** + * @param certificate + * @param username + * @param firstname + * @param surname + */ + public void setUserName(Certificate certificate, String username, String firstname, String surname); + + /** + * @param certificate + * @param username + * @param state + */ + public void setUserState(Certificate certificate, String username, UserState state); + + /** + * @param certificate + * @param username + * @param locale + */ + public void setUserLocaleState(Certificate certificate, String username, Locale locale); + + /** + * @param certificate + * @param privilegeName + * @param policyName + */ + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName); + + /** + * @param certificate + * @param privilegeName + * @param allAllowed + */ + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); + + /** + * @param certificate + * @param privilegeName + * @param denyList + */ + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList); + + /** + * @param certificate + * @param privilegeName + * @param allowList + */ + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); + + /** + * @param username + * @param password + * + * @return + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + public Certificate authenticate(String username, String password); + /** * @param certificate * @param restrictable @@ -73,61 +241,50 @@ public interface PrivilegeHandler { public boolean isCertificateValid(Certificate certificate); /** - * @param username + *

    + * Validates if this {@link Certificate} is for a {@link ch.eitchnet.privilege.model.internal.User} with + * {@link Role} with name {@link PrivilegeHandler#PRIVILEGE_ADMIN_ROLE} + *

    + * + *

    + * In other words, this method checks if the given certificate is for a user who has the rights to change objects + *

    + * + *

    + * If the user is not the administrator, then a {@link ch.eitchnet.privilege.i18n.PrivilegeException} is thrown + *

    + * + * @param certificate + * the {@link Certificate} for which the role should be validated against + * + * @throws ch.eitchnet.privilege.i18n.PrivilegeException + * if the user does not not have admin privileges + */ + public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException; + + /** + * Validate that the given password meets any requirements. What these requirements are is a decision made by the + * concrete implementation + * * @param password * - * @return - * - * @throws AccessDeniedException - * if the user credentials are not valid + * @throws PrivilegeException */ - public Certificate authenticate(String username, String password); - - public User getUser(String username); - - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); - - public UserRep removeUser(Certificate certificate, String username); - - public void setUserPassword(Certificate certificate, String username, String password); - - public void setUserName(Certificate certificate, String username, String firstname, String surname); - - public void setUserState(Certificate certificate, String username, UserState state); - - public void setUserLocaleState(Certificate certificate, String username, Locale locale); - - public void addRoleToUser(Certificate certificate, String username, String roleName); - - public void removeRoleFromUser(Certificate certificate, String username, String roleName); - - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); - - public Role getRole(String roleName); - - public RoleRep removeRole(Certificate certificate, String roleName); - - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); - - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); - - public Privilege getPrivilege(String privilegeName); - - public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); - - public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); - - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName); - - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); - - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList); - - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); - - public PrivilegePolicy getPolicy(String policyName); + public void validatePassword(String password) throws PrivilegeException; + /** + * @param certificate + * + * @return + */ public boolean persist(Certificate certificate); - public void initialize(Element element); + /** + * + * @param parameterMap + * @param encryptionHandler + * @param persistenceHandler + */ + public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, + PersistenceHandler persistenceHandler); } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java similarity index 92% rename from src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java rename to src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 5ea040e1c..9f97b4377 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -24,10 +24,8 @@ import org.apache.log4j.Logger; import org.dom4j.DocumentFactory; import org.dom4j.Element; -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -41,9 +39,9 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * @author rvonburg * */ -public class DefaultPersistenceHandler implements PersistenceHandler { +public class XmlPersistenceHandler implements PersistenceHandler { - private static final Logger logger = Logger.getLogger(DefaultPersistenceHandler.class); + private static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); private Map userMap; private Map roleMap; @@ -59,6 +57,8 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private Map parameterMap; + private String basePath; + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.Privilege) @@ -175,7 +175,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); } // get users file - File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + File usersFile = new File(basePath + "/" + usersFileName); boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == usersFileDate; if (!userMapDirty && usersFileUnchanged) { logger.warn("No users unpersisted and user file unchanged on file system"); @@ -202,7 +202,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); } // get roles file - File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + File rolesFile = new File(basePath + "/" + rolesFileName); boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == rolesFileDate; if (!roleMapDirty && rolesFileUnchanged) { logger.warn("No roles unpersisted and roles file unchanged on file system"); @@ -229,7 +229,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); } // get privileges file - File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + File privilegesFile = new File(basePath + "/" + privilegesFileName); boolean privilegesFileUnchanged = privilegesFile.exists() && privilegesFile.lastModified() == privilegesFileDate; if (!privilegeMapDirty && privilegesFileUnchanged) { @@ -267,19 +267,23 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) */ @Override - public void initialize(Element element) { + public void initialize(Map parameterMap) { roleMap = new HashMap(); userMap = new HashMap(); privilegeMap = new HashMap(); policyMap = new HashMap>(); - // get parameters - Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + // get and validate base bath + basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + File basePathF = new File(basePath); + if (!basePathF.exists() && !basePathF.isDirectory()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); + } // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); @@ -289,7 +293,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // get roles file - File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + File rolesFile = new File(basePath + "/" + rolesFileName); if (!rolesFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " @@ -311,7 +315,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // get users file - File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + File usersFile = new File(basePath + "/" + usersFileName); if (!usersFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " @@ -333,7 +337,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // get privileges file - File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + File privilegesFile = new File(basePath + "/" + privilegesFileName); if (!privilegesFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " @@ -355,7 +359,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // get policy file - File policyFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + policyFileName); + File policyFile = new File(basePath + "/" + policyFileName); if (!policyFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " @@ -380,7 +384,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { boolean privilegeAdminExists = false; for (String username : userMap.keySet()) { User user = userMap.get(username); - if (user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { + if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { privilegeAdminExists = true; break; } @@ -395,6 +399,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ private void readUsers(Element usersRootElement) { + @SuppressWarnings("unchecked") List userElements = usersRootElement.elements(XmlConstants.XML_USER); for (Element userElement : userElements) { @@ -406,11 +411,12 @@ public class DefaultPersistenceHandler implements PersistenceHandler { UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); - // TODO better handling needed + // TODO better parsing needed String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); Locale locale = new Locale(localeName); Element rolesElement = userElement.element(XmlConstants.XML_ROLES); + @SuppressWarnings("unchecked") List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); Set roles = new HashSet(); for (Element roleElement : rolesElementList) { @@ -436,11 +442,13 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ private void readRoles(Element rolesRootElement) { + @SuppressWarnings("unchecked") List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); for (Element roleElement : roleElements) { String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); + @SuppressWarnings("unchecked") List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); Set privileges = new HashSet(); for (Element privilegeElement : privilegeElements) { @@ -459,6 +467,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ private void readPrivileges(Element privilegesRootElement) { + @SuppressWarnings("unchecked") List privilegeElements = privilegesRootElement.elements(XmlConstants.XML_PRIVILEGE); for (Element privilegeElement : privilegeElements) { @@ -468,6 +477,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); boolean allAllowed = Boolean.valueOf(allAllowedS); + @SuppressWarnings("unchecked") List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); Set denyList = new HashSet(denyElements.size()); for (Element denyElement : denyElements) { @@ -479,6 +489,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } } + @SuppressWarnings("unchecked") List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); Set allowList = new HashSet(allowElements.size()); for (Element allowElement : allowElements) { @@ -500,6 +511,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ private void readPolicies(Element policiesRootElement) { + @SuppressWarnings("unchecked") List policyElements = policiesRootElement.elements(XmlConstants.XML_POLICY); for (Element policyElement : policyElements) { String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); @@ -593,7 +605,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword(certificate)); + userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); // add first name element Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index db99ea0be..4c8a7f7dc 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -21,8 +21,6 @@ import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.base.XmlConstants; /** *

    @@ -43,6 +41,8 @@ public class BootstrapConfigurationHelper { private static String path; + private static String defaultPrivilegeContainerXmlFile = "PrivilegeContainer.xml"; + private static String usersFileName = "PrivilegeUsers.xml"; private static String rolesFileName = "PrivilegeRoles.xml"; private static String privilegesFileName = "Privileges.xml"; @@ -51,10 +51,9 @@ public class BootstrapConfigurationHelper { private static String policyXmlFile = "PrivilegePolicies.xml"; + private static String defaultPrivilegeHandler = "ch.eitchnet.privilege.handler.DefaultPrivilegeHandler"; private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; - private static String defaultSessionHandler = "ch.eitchnet.privilege.handler.DefaultSessionHandler"; private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; - private static String defaultPolicyHandler = "ch.eitchnet.privilege.handler.DefaultPolicyHandler"; /** * @param args @@ -67,7 +66,7 @@ public class BootstrapConfigurationHelper { // get current directory path = System.getProperty("user.dir") + "/newConfig"; - // ask user where to save configuration, default is pwd/newConfig/.... + // TODO ask user where to save configuration, default is pwd/newConfig/.... // see if path already exists File pathF = new File(path); @@ -79,7 +78,7 @@ public class BootstrapConfigurationHelper { } } - // ask other questions... + // TODO ask other questions... // now perform work: createXmlPrivilegeContainer(); @@ -139,15 +138,16 @@ public class BootstrapConfigurationHelper { parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_PRIVILEGES_FILE); parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, privilegesFileName); parametersElement.add(parameterElement); + // Parameter policyXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_POLICY_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, policyXmlFile); + parametersElement.add(parameterElement); - // create SessionHandler - Element sessionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_SESSION); - sessionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultSessionHandler); - - // create ModelHandler - Element modelHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_MODEL); - rootElement.add(modelHandlerElem); - modelHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, "ch.eitchnet.privilege.handler.DefaultModelHandler"); + // create PrivilegeHandler + Element privilegeHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PRIVILEGE); + rootElement.add(privilegeHandlerElem); + privilegeHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPrivilegeHandler); // create EncryptionHandler Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); @@ -161,19 +161,8 @@ public class BootstrapConfigurationHelper { parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, hashAlgorithm); parametersElement.add(parameterElement); - // create PolicyHandler - Element policyHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_POLICY); - rootElement.add(policyHandlerElem); - policyHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPolicyHandler); - parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); - policyHandlerElem.add(parametersElement); - // Parameter policyXmlFile - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_POLICY_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, policyXmlFile); - parametersElement.add(parameterElement); - - File privilegeContainerFile = new File(path + "/" + PrivilegeContainer.PRIVILEGE_CONTAINER_FILE); + // write the container file to disk + File privilegeContainerFile = new File(path + "/" + defaultPrivilegeContainerXmlFile); XmlHelper.writeDocument(doc, privilegeContainerFile); } } diff --git a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java index 34e6d642d..8fc6fda6e 100644 --- a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java @@ -10,13 +10,18 @@ package ch.eitchnet.privilege.helper; +import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.log4j.Logger; import org.dom4j.Element; -import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.handler.PersistenceHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeException; /** * @author rvonburg @@ -24,6 +29,86 @@ import ch.eitchnet.privilege.base.XmlConstants; */ public class ConfigurationHelper { + private static final Logger logger = Logger.getLogger(ConfigurationHelper.class); + + /** + * @param privilegeContainerXmlFile + */ + public static void initializeFromXml(File privilegeContainerXmlFile) { + + // make sure file exists + if (!privilegeContainerXmlFile.exists()) { + throw new PrivilegeException("Privilige file does not exist at path " + + privilegeContainerXmlFile.getAbsolutePath()); + } + + // parse container xml file to XML document + Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXmlFile).getRootElement(); + + // instantiate encryption handler + Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); + String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); + + // instantiate persistence handler + Element persistenceHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); + String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); + + // instantiate privilege handler + Element privilegeHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PRIVILEGE); + String privilegeHandlerClassName = privilegeHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + PrivilegeHandler privilegeHandler = ClassHelper.instantiateClass(privilegeHandlerClassName); + + try { + + // get parameters + Element parameterElement = encryptionHandlerElement.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = convertToParameterMap(parameterElement); + + // initialize encryption handler + encryptionHandler.initialize(parameterMap); + + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + + " could not be initialized"); + } + + try { + + // get parameters + Element parameterElement = persistenceHandlerElement.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = convertToParameterMap(parameterElement); + + // initialize persistence handler + persistenceHandler.initialize(parameterMap); + + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + + " could not be initialized"); + } + + try { + + // get parameters + Element parameterElement = privilegeHandlerElement.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = convertToParameterMap(parameterElement); + + // initialize privilege handler + privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler); + + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("PrivilegeHandler " + privilegeHandlerClassName + " could not be initialized"); + } + } + + /** + * @param element + * @return + */ @SuppressWarnings("unchecked") public static Map convertToParameterMap(Element element) { diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java deleted file mode 100644 index 06029806e..000000000 --- a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.helper; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.internal.User; - -/** - * @author rvonburg - * - */ -public class PrivilegeHelper { - - public static boolean isUserPrivilegeAdmin(Certificate certificate) { - // validate certificate - if (!PrivilegeContainer.getInstance().getModelHandler().isCertificateValid(certificate)) { - throw new PrivilegeException("Certificate " + certificate + " is not valid!"); - } - - // get user object - User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); - if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " - + certificate); - } - - // validate user has PrivilegeAdmin role - if (!user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { - throw new AccessDeniedException("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE - + " role! Certificate: " + certificate); - } else { - return true; - } - } -} diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java similarity index 90% rename from src/ch/eitchnet/privilege/base/XmlConstants.java rename to src/ch/eitchnet/privilege/helper/XmlConstants.java index d2c7400e4..5a42dbe34 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -8,7 +8,7 @@ * */ -package ch.eitchnet.privilege.base; +package ch.eitchnet.privilege.helper; /** * @author rvonburg @@ -23,9 +23,7 @@ public class XmlConstants { public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; - public static final String XML_HANDLER_SESSION = "SessionHandler"; - public static final String XML_HANDLER_POLICY = "PolicyHandler"; - public static final String XML_HANDLER_MODEL = "ModelHandler"; + public static final String XML_HANDLER_PRIVILEGE = "PrivilegeHandler"; public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; @@ -56,4 +54,5 @@ public class XmlConstants { public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; public static final String XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile"; + public static final String XML_PARAM_BASE_PATH = "basePath"; } diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index 3ac250dda..bce742671 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -11,7 +11,6 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; -import java.util.HashSet; import java.util.Set; /** @@ -39,8 +38,8 @@ public class PrivilegeRep implements Serializable { this.name = name; this.policy = policy; this.allAllowed = allAllowed; - this.denyList = new HashSet(denyList); - this.allowList = new HashSet(allowList); + this.denyList = denyList; + this.allowList = allowList; } /** diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index d2222f423..c7310d750 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -55,7 +55,8 @@ public class UserRep implements Serializable { } /** - * @param username the username to set + * @param username + * the username to set */ public void setUsername(String username) { this.username = username; @@ -69,7 +70,8 @@ public class UserRep implements Serializable { } /** - * @param firstname the firstname to set + * @param firstname + * the firstname to set */ public void setFirstname(String firstname) { this.firstname = firstname; @@ -83,7 +85,8 @@ public class UserRep implements Serializable { } /** - * @param surname the surname to set + * @param surname + * the surname to set */ public void setSurname(String surname) { this.surname = surname; @@ -97,7 +100,8 @@ public class UserRep implements Serializable { } /** - * @param userState the userState to set + * @param userState + * the userState to set */ public void setUserState(UserState userState) { this.userState = userState; @@ -111,7 +115,8 @@ public class UserRep implements Serializable { } /** - * @param roles the roles to set + * @param roles + * the roles to set */ public void setRoles(Set roles) { this.roles = roles; @@ -125,7 +130,8 @@ public class UserRep implements Serializable { } /** - * @param locale the locale to set + * @param locale + * the locale to set */ public void setLocale(Locale locale) { this.locale = locale; diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java index 24cb3128e..955bdefaf 100644 --- a/src/ch/eitchnet/privilege/model/UserState.java +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -18,5 +18,5 @@ public enum UserState { NEW, ENABLED, DISABLED, - DEACTIVATED; + EXPIRED; } diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index cf89a1f26..da188f9d8 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -79,7 +80,7 @@ public final class Privilege { * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients */ public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(name, policy, allAllowed, denyList, allowList); + return new PrivilegeRep(name, policy, allAllowed, new HashSet(denyList), new HashSet(allowList)); } /** diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 17705febf..a5390bea5 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import ch.eitchnet.privilege.model.RoleRep; @@ -60,7 +61,7 @@ public final class Role { * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - return new RoleRep(name, privileges); + return new RoleRep(name, new HashSet(privileges)); } /** diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index b14dad127..787a6d8c6 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -11,11 +11,10 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; +import java.util.HashSet; import java.util.Locale; import java.util.Set; -import ch.eitchnet.privilege.helper.PrivilegeHelper; -import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -70,13 +69,19 @@ public final class User { } /** - * @return the password + * + * @param privilegeHandler + * @param certificate + * + * @return */ - public String getPassword(Certificate certificate) { - if (PrivilegeHelper.isUserPrivilegeAdmin(certificate)) - return password; - else - return null; + public String getPassword() { + + // TODO is it possible that there is a hidden way of accessing this + // field even though? The User object should be private, but maybe I + // forgot something? + + return password; } /** @@ -135,7 +140,7 @@ public final class User { * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients */ public UserRep asUserRep() { - return new UserRep(username, firstname, surname, userState, roles, locale); + return new UserRep(username, firstname, surname, userState, new HashSet(roles), locale); } /** From c5fe322c5f7a7e86882cc03577db1006fab8f89d Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 18 Sep 2010 20:03:09 +0000 Subject: [PATCH 032/457] --- config/PrivilegeContainer.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index e87acad3c..c0dd61837 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -3,22 +3,22 @@ + + + + + - - - - - \ No newline at end of file From bef8474114b2f1cc1042f74e696bc76eb4b04939 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 18 Sep 2010 20:03:29 +0000 Subject: [PATCH 033/457] --- src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 9f97b4377..6d6ecb46f 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -285,6 +285,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); } + // ROLES // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); if (rolesFileName == null || rolesFileName.isEmpty()) { @@ -307,6 +308,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { readRoles(rolesRootElement); rolesFileDate = rolesFile.lastModified(); + // USERS // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); if (usersFileName == null || usersFileName.isEmpty()) { @@ -329,6 +331,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { readUsers(usersRootElement); usersFileDate = usersFile.lastModified(); + // PRIVILEGES // get privileges file name String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); if (privilegesFileName == null || privilegesFileName.isEmpty()) { @@ -351,6 +354,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { readPrivileges(privilegesRootElement); privilegesFileDate = privilegesFile.lastModified(); + // POLICIES // get policy file name String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); if (policyFileName == null || policyFileName.isEmpty()) { From 1d410a6edeafda32efe9ee11f3ff23f72ac6af81 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 18 Sep 2010 20:13:31 +0000 Subject: [PATCH 034/457] --- config/PrivilegeContainer.xml | 20 ++--- ...nHelper.java => InitializationHelper.java} | 17 +++- .../privilege/test/PrivilegeTest.java | 87 ++++++++----------- 3 files changed, 61 insertions(+), 63 deletions(-) rename src/ch/eitchnet/privilege/helper/{ConfigurationHelper.java => InitializationHelper.java} (88%) diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index c0dd61837..20b2be359 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -1,7 +1,15 @@ - + + + + + + + + + @@ -11,14 +19,4 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java similarity index 88% rename from src/ch/eitchnet/privilege/helper/ConfigurationHelper.java rename to src/ch/eitchnet/privilege/helper/InitializationHelper.java index 8fc6fda6e..d61e36d04 100644 --- a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -27,14 +27,14 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * @author rvonburg * */ -public class ConfigurationHelper { +public class InitializationHelper { - private static final Logger logger = Logger.getLogger(ConfigurationHelper.class); + private static final Logger logger = Logger.getLogger(InitializationHelper.class); /** * @param privilegeContainerXmlFile */ - public static void initializeFromXml(File privilegeContainerXmlFile) { + public static PrivilegeHandler initializeFromXml(File privilegeContainerXmlFile) { // make sure file exists if (!privilegeContainerXmlFile.exists()) { @@ -103,6 +103,8 @@ public class ConfigurationHelper { logger.error(e, e); throw new PrivilegeException("PrivilegeHandler " + privilegeHandlerClassName + " could not be initialized"); } + + return privilegeHandler; } /** @@ -114,7 +116,16 @@ public class ConfigurationHelper { Map parameterMap = new HashMap(); + // if element is null then there are no parameters, so return empty map + if (element == null) + return parameterMap; + List elements = element.elements(XmlConstants.XML_PARAMETER); + + // if elements is null or empty then there are no parameters, so return empty map + if (elements == null || elements.isEmpty()) + return parameterMap; + for (Element parameter : elements) { String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); String value = parameter.attributeValue(XmlConstants.XML_ATTR_VALUE); diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 2f52de634..90960ab95 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -21,8 +21,8 @@ import org.apache.log4j.PatternLayout; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.InitializationHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -38,61 +38,64 @@ public class PrivilegeTest { private static final Logger logger = Logger.getLogger(PrivilegeTest.class); + private static PrivilegeHandler privilegeHandler; + /** * @throws java.lang.Exception */ @BeforeClass public static void init() throws Exception { - // set up log4j - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); - Logger.getRootLogger().setLevel(Level.INFO); + try { + // set up log4j + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); + Logger.getRootLogger().setLevel(Level.INFO); - // initialize container - String pwd = System.getProperty("user.dir"); - File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); - PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); - privilegeContainer.initialize(privilegeContainerXml); + // initialize container + String pwd = System.getProperty("user.dir"); + File privilegeContainerXmlFile = new File(pwd + "/config/PrivilegeContainer.xml"); + privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); + } catch (Exception e) { + logger.error(e, e); + + throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); + } } @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "123"); + Certificate certificate = privilegeHandler.authenticate("eitch", "123"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", null); + Certificate certificate = privilegeHandler.authenticate("eitch", null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test public void testAddUserBobWithPW() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); - - PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); // let's add a new user bob UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); - modelHandler.addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); // set bob's password - modelHandler.setUserPassword(certificate, "bob", "12345678901"); + privilegeHandler.setUserPassword(certificate, "bob", "12345678901"); logger.info("Set Bob's password"); } @@ -104,17 +107,14 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); + privilegeHandler.authenticate("bob", "12345678901"); } @Test public void testEnableUserBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); - - PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); - modelHandler.setUserState(certificate, "bob", UserState.ENABLED); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + privilegeHandler.setUserState(certificate, "bob", UserState.ENABLED); } /** @@ -125,25 +125,21 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", - "12345678901"); + Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test public void testAddUserRoleToBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); - - PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); - modelHandler.addRoleToUser(certificate, "bob", "user"); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + privilegeHandler.addRoleToUser(certificate, "bob", "user"); } @Test public void testAuthAsBob() throws Exception { - PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); + privilegeHandler.authenticate("bob", "12345678901"); } /** @@ -154,50 +150,43 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", - "12345678901"); + Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user bob UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); - PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); - - PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); - modelHandler.addRoleToUser(certificate, "bob", PrivilegeContainer.PRIVILEGE_ADMIN_ROLE); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + privilegeHandler.addRoleToUser(certificate, "bob", PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); } @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", - "12345678901"); + Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted UserRep userRep = new UserRep("ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); - PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } @Test public void testPerformRestrictable() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); - boolean actionAllowed = PrivilegeContainer.getInstance().getModelHandler().actionAllowed(certificate, - restrictable); + boolean actionAllowed = privilegeHandler.actionAllowed(certificate, restrictable); org.junit.Assert.assertTrue("eitch may not perform restrictable!", actionAllowed); } } From a3e2515f3874fb25bdb72f8f17ff7ebeabbfacd6 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 19 Sep 2010 18:57:23 +0000 Subject: [PATCH 035/457] [Bugfix] cleaned up a lot of warnings --- .settings/org.eclipse.jdt.core.prefs | 77 ++++++- .../handler/DefaultEncryptionHandler.java | 14 +- .../handler/DefaultPrivilegeHandler.java | 190 +++++++++++------- .../privilege/handler/EncryptionHandler.java | 16 +- .../privilege/handler/PersistenceHandler.java | 131 ++++++++---- .../privilege/handler/PrivilegeHandler.java | 2 + .../handler/XmlPersistenceHandler.java | 165 ++++++++------- .../helper/BootstrapConfigurationHelper.java | 9 +- .../helper/InitializationHelper.java | 1 + .../eitchnet/privilege/model/Certificate.java | 51 +++-- .../privilege/model/PrivilegeRep.java | 10 +- .../privilege/model/Restrictable.java | 6 + src/ch/eitchnet/privilege/model/RoleRep.java | 4 +- src/ch/eitchnet/privilege/model/UserRep.java | 12 +- .../eitchnet/privilege/model/UserState.java | 2 +- .../privilege/model/internal/Privilege.java | 54 ++--- .../privilege/model/internal/Role.java | 24 +-- .../privilege/model/internal/Session.java | 50 +++-- .../privilege/model/internal/User.java | 38 ++-- .../privilege/test/TestRestrictable.java | 8 +- 20 files changed, 525 insertions(+), 339 deletions(-) diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 650ae7f9a..e83bae06a 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,4 @@ -#Wed May 19 19:28:29 CEST 2010 +#Sun Sep 19 16:32:10 CEST 2010 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 @@ -7,6 +7,81 @@ org.eclipse.jdt.core.compiler.compliance=1.6 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=enabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=enabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=enabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.source=1.6 diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 5fd3c4664..1ee77872d 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -38,10 +38,10 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String convertToHash(String string) { try { - return EncryptionHelper.encryptString(hashAlgorithm, string); + return EncryptionHelper.encryptString(this.hashAlgorithm, string); } catch (NoSuchAlgorithmException e) { - throw new PrivilegeException("Algorithm " + hashAlgorithm + " was not found!", e); + throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); } catch (UnsupportedEncodingException e) { throw new PrivilegeException("Charset ASCII is not supported!", e); } @@ -53,7 +53,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { @Override public String nextToken() { byte[] bytes = new byte[16]; - secureRandom.nextBytes(bytes); + this.secureRandom.nextBytes(bytes); String randomString = new String(bytes); //String randomString = new BigInteger(80, secureRandom).toString(32); // 80 big integer bits = 16 chars return randomString; @@ -65,11 +65,11 @@ public class DefaultEncryptionHandler implements EncryptionHandler { @Override public void initialize(Map parameterMap) { - secureRandom = new SecureRandom(); + this.secureRandom = new SecureRandom(); // get hash algorithm parameters - hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); - if (hashAlgorithm == null || hashAlgorithm.isEmpty()) { + this.hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); + if (this.hashAlgorithm == null || this.hashAlgorithm.isEmpty()) { throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid"); } @@ -77,7 +77,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { // test hash algorithm try { convertToHash("test"); - logger.info("Using hashing algorithm " + hashAlgorithm); + logger.info("Using hashing algorithm " + this.hashAlgorithm); } catch (Exception e) { throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid because of underlying exception: " diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 1119e8aff..28ee4c489 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -38,13 +38,29 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public class DefaultPrivilegeHandler implements PrivilegeHandler { + /** + * log4j logger + */ private static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); + /** + * last assigned id for the {@link Session}s + */ private static long lastSessionId; + /** + * Map keeping a reference to all active sessions with their certificates + */ private Map sessionMap; + /** + * The persistence handler is used for getting objects and saving changes + */ private PersistenceHandler persistenceHandler; + + /** + * The encryption handler is used for generating hashes and tokens + */ private EncryptionHandler encryptionHandler; /** @@ -52,7 +68,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public PrivilegeRep getPrivilege(String privilegeName) { - return persistenceHandler.getPrivilege(privilegeName).asPrivilegeRep(); + return this.persistenceHandler.getPrivilege(privilegeName).asPrivilegeRep(); } /** @@ -60,7 +76,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public RoleRep getRole(String roleName) { - return persistenceHandler.getRole(roleName).asRoleRep(); + return this.persistenceHandler.getRole(roleName).asRoleRep(); } /** @@ -68,7 +84,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public UserRep getUser(String username) { - return persistenceHandler.getUser(username).asUserRep(); + return this.persistenceHandler.getUser(username).asUserRep(); } /** @@ -76,7 +92,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public PrivilegePolicy getPolicy(String policyName) { - return persistenceHandler.getPolicy(policyName); + return this.persistenceHandler.getPolicy(policyName); } /** @@ -94,7 +110,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); // delegate to persistence handler - persistenceHandler.addOrReplacePrivilege(privilege); + this.persistenceHandler.addOrReplacePrivilege(privilege); } /** @@ -111,7 +127,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); // delegate to persistence handler - persistenceHandler.addOrReplaceRole(role); + this.persistenceHandler.addOrReplaceRole(role); } /** @@ -124,22 +140,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this validateIsPrivilegeAdmin(certificate); - // validate password meets basic requirements - validatePassword(password); + String passwordHash = null; + if (password != null) { - // hash password - String passwordHash; - if (password == null) - passwordHash = null; - else - passwordHash = encryptionHandler.convertToHash(password); + // validate password meets basic requirements + validatePassword(password); + + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } // create new user User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep .getUserState(), userRep.getRoles(), userRep.getLocale()); // delegate to persistence handler - persistenceHandler.addOrReplaceUser(user); + this.persistenceHandler.addOrReplaceUser(user); } /** @@ -153,7 +169,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get role - Role role = persistenceHandler.getRole(roleName); + Role role = this.persistenceHandler.getRole(roleName); if (role == null) { throw new PrivilegeException("Role " + roleName + " does not exist!"); } @@ -178,7 +194,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role newRole = new Role(role.getName(), newPrivileges); // delegate role replacement to persistence handler - persistenceHandler.addOrReplaceRole(newRole); + this.persistenceHandler.addOrReplaceRole(newRole); } /** @@ -192,7 +208,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get user - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -217,7 +233,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -231,13 +247,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // delegate privilege removal to persistence handler - Privilege removedPrivilege = persistenceHandler.removePrivilege(privilegeName); + Privilege removedPrivilege = this.persistenceHandler.removePrivilege(privilegeName); + + if (removedPrivilege == null) + return null; // return privilege rep if it was removed - if (removedPrivilege != null) - return removedPrivilege.asPrivilegeRep(); - else - return null; + return removedPrivilege.asPrivilegeRep(); } /** @@ -251,7 +267,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get role - Role role = persistenceHandler.getRole(roleName); + Role role = this.persistenceHandler.getRole(roleName); if (role == null) { throw new PrivilegeException("Role " + roleName + " does not exist!"); } @@ -269,7 +285,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role newRole = new Role(role.getName(), newPrivileges); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceRole(newRole); + this.persistenceHandler.addOrReplaceRole(newRole); } /** @@ -283,13 +299,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // delegate role removal to persistence handler - Role removedRole = persistenceHandler.removeRole(roleName); + Role removedRole = this.persistenceHandler.removeRole(roleName); - // return role rep if it was removed - if (removedRole != null) - return removedRole.asRoleRep(); - else + if (removedRole == null) return null; + + // return role rep if it was removed + return removedRole.asRoleRep(); } /** @@ -303,7 +319,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -322,7 +338,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -336,13 +352,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // delegate user removal to persistence handler - User removedUser = persistenceHandler.removeUser(username); + User removedUser = this.persistenceHandler.removeUser(username); // return user rep if it was removed - if (removedUser != null) - return removedUser.asUserRep(); - else + if (removedUser == null) return null; + + // return user rep if it was removed + return removedUser.asUserRep(); + } /** @@ -356,7 +374,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get Privilege - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } @@ -373,7 +391,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getDenyList(), privilege.getAllowList()); // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); + this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -387,7 +405,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get Privilege - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } @@ -397,7 +415,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { privilege.getDenyList(), allowList); // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); + this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -411,7 +429,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get Privilege - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } @@ -421,7 +439,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { denyList, privilege.getAllowList()); // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); + this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -435,7 +453,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get Privilege - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } @@ -445,7 +463,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getDenyList(), privilege.getAllowList()); // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); + this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -459,7 +477,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -469,7 +487,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getState(), user.getRoles(), locale); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -483,7 +501,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -493,7 +511,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getRoles(), user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -507,20 +525,27 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } - // hash password - String passwordHash = encryptionHandler.convertToHash(password); + String passwordHash = null; + if (password != null) { + + // validate password meets basic requirements + validatePassword(password); + + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } // create new user User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user .getState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -534,7 +559,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -544,11 +569,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** - * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, java.lang.String) * * @throws AccessDeniedException * if the user credentials are not valid @@ -563,16 +588,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("The given password is shorter than 3 characters"); // we only work with hashed passwords - String passwordHash = encryptionHandler.convertToHash(password); + String passwordHash = this.encryptionHandler.convertToHash(password); // get user object - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); // no user means no authentication if (user == null) throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); // validate password - if (!user.isPassword(passwordHash)) + String pwHash = user.getPassword(); + if (pwHash == null) + throw new AccessDeniedException("User has no password and may not login!"); + if (!pwHash.equals(passwordHash)) throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); // validate if user is allowed to login @@ -585,8 +613,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get 2 auth tokens - String authToken = encryptionHandler.nextToken(); - String authPassword = encryptionHandler.nextToken(); + String authToken = this.encryptionHandler.nextToken(); + String authPassword = this.encryptionHandler.nextToken(); // get next session id String sessionId = nextSessionId(); @@ -597,7 +625,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create and save a new session Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System .currentTimeMillis()); - sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); // log logger.info("Authenticated: " + session); @@ -609,12 +637,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? * - * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.Restrictable) * * @throws AccessDeniedException * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not * perform the action defined by the {@link Restrictable} implementation + * + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.Restrictable) */ @Override public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { @@ -631,7 +660,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Restrictable may not be null!"); // get user object - User user = persistenceHandler.getUser(certificate.getUsername()); + User user = this.persistenceHandler.getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); @@ -644,7 +673,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // now iterate roles and validate on policies for (String roleName : user.getRoles()) { - Role role = persistenceHandler.getRole(roleName); + Role role = this.persistenceHandler.getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -661,7 +690,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, * ch.eitchnet.privilege.model.Restrictable) */ @Override @@ -687,7 +716,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get the privilege for this restrictable - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " + restrictable.getClass().getName()); @@ -705,7 +734,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.SessionHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) */ @Override public boolean isCertificateValid(Certificate certificate) { @@ -715,7 +744,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Certificate may not be null!"); // first see if a session exists for this certificate - CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); + CertificateSessionPair certificateSessionPair = this.sessionMap.get(certificate.getSessionId()); if (certificateSessionPair == null) throw new AccessDeniedException("There is no session information for " + certificate.toString()); @@ -732,15 +761,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { + certificate.getSessionId()); // get user object - User user = persistenceHandler.getUser(certificateSessionPair.session.getUsername()); + User user = this.persistenceHandler.getUser(certificateSessionPair.session.getUsername()); // if user exists, then certificate is valid if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); - } else { - return true; } + + // everything is ok, so return true as the certificate must be valid + return true; } /** @@ -755,7 +785,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get user object - User user = persistenceHandler.getUser(certificate.getUsername()); + User user = this.persistenceHandler.getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " @@ -789,7 +819,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this validateIsPrivilegeAdmin(certificate); - return persistenceHandler.persist(certificate); + return this.persistenceHandler.persist(certificate); } /** @@ -804,7 +834,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler = persistenceHandler; lastSessionId = 0l; - sessionMap = new HashMap(); + this.sessionMap = new HashMap(); } /** @@ -820,9 +850,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @author rvonburg */ private class CertificateSessionPair { - private Session session; - private Certificate certificate; + public final Session session; + public final Certificate certificate; + /** + * @param session + * @param certificate + */ public CertificateSessionPair(Session session, Certificate certificate) { this.session = session; this.certificate = certificate; diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index 9efa5f337..6a1d88271 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -13,24 +13,36 @@ package ch.eitchnet.privilege.handler; import java.util.Map; /** + * The {@link EncryptionHandler} exposes API which is used to handle encrypting of strings, or returning secure tokens + * for certificates and so forth + * * @author rvonburg * */ public interface EncryptionHandler { /** - * @return + * Calculates or generates a token which can be used to identify certificates and so forth + * + * @return the secure token */ public String nextToken(); /** + * Converts a given string, e.g. a password to a hash which is defined by the concrete implementation + * * @param string - * @return + * the string to convert + * @return the hash of the string after converting */ public String convertToHash(String string); /** + * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this map + * might need + * * @param parameterMap + * a map containing configuration properties */ public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 5fcd9c7a6..ee4459a20 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -13,80 +13,135 @@ package ch.eitchnet.privilege.handler; import java.util.Map; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** + *

    + * The {@link PersistenceHandler} takes care of retrieving and persisting model objects to the underlying database. This + * database can be simple XML files, or an LDAP and so forth + *

    + * + *

    + * The {@link PersistenceHandler} also serves the special {@link PrivilegePolicy} objects. These policies are special + * objects which implement an algorithm to define if an action is allowed on a {@link Restrictable} by a {@link Role} + * and {@link Privilege} + *

    + * * @author rvonburg * */ public interface PersistenceHandler { /** + * Returns a {@link User} object from the underlying database + * * @param username - * @return + * the name/id of the {@link User} object to return + * + * @return the {@link User} object, or null if it was not found */ public User getUser(String username); /** - * @param user - */ - public void addOrReplaceUser(User user); - - /** - * @param username - * @return - */ - public User removeUser(String username); - - /** + * Returns a {@link Role} object from the underlying database + * * @param roleName - * @return + * the name/id of the {@link Role} object to return + * + * @return the {@link Role} object, or null if it was not found */ public Role getRole(String roleName); /** - * @param role - */ - public void addOrReplaceRole(Role role); - - /** - * @param roleName - * @return - */ - public Role removeRole(String roleName); - - /** + * Returns a {@link Privilege} object from the underlying database + * * @param privilegeName - * @return + * the name/id of the {@link Privilege} object to return + * + * @return the {@link Privilege} object, or null if it was not found */ public Privilege getPrivilege(String privilegeName); /** - * @param privilege - */ - public void addOrReplacePrivilege(Privilege privilege); - - /** - * @param privilegeName - * @return - */ - public Privilege removePrivilege(String privilegeName); - - /** + *

    + * Thus this method instantiates a {@link PrivilegePolicy} object from the given policyName. The + * {@link PrivilegePolicy} is not stored in a database, but rather behind a privilege name a class name is stored + * which then is used to instantiate a new object + *

    + * * @param policyName - * @return + * the name/id of the {@link PrivilegePolicy} object to return + * + * @return the {@link PrivilegePolicy} object, or null if no class is defined for the given policy name */ public PrivilegePolicy getPolicy(String policyName); + /** + * Removes a {@link User} with the given name and returns the removed object if it existed + * + * @param username + * the name of the {@link User} to remove + * + * @return the {@link User} removed, or null if it did not exist + */ + public User removeUser(String username); + + /** + * Removes a {@link Role} with the given name and returns the removed object if it existed + * + * @param roleName + * the name of the {@link Role} to remove + * + * @return the {@link Role} removed, or null if it did not exist + */ + public Role removeRole(String roleName); + + /** + * Removes a {@link Privilege} with the given name and returns the removed object if it existed + * + * @param privilegeName + * the name of the {@link Privilege} to remove + * + * @return the {@link Privilege} removed, or null if it did not exist + */ + public Privilege removePrivilege(String privilegeName); + + /** + * Adds a {@link User} object to the underlying database. If the {@link User} already exists, it is replaced + * + * @param user + * the {@link User} object to add + */ + public void addOrReplaceUser(User user); + + /** + * Adds a {@link Role} object to the underlying database. If the {@link Role} already exists, it is replaced + * + * @param role + * the {@link User} object to add + */ + public void addOrReplaceRole(Role role); + + /** + * Adds a {@link Privilege} object to the underlying database. If the {@link Privilege} already exists, it is + * replaced + * + * @param privilege + * the {@link Privilege} object to add + */ + public void addOrReplacePrivilege(Privilege privilege); + /** * @param certificate + * * @return */ public boolean persist(Certificate certificate); - + /** * @param parameterMap */ diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 74f27bd70..0c8264076 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -14,6 +14,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -22,6 +23,7 @@ import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 6d6ecb46f..d926ae2b2 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -60,13 +60,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { private String basePath; /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.internal.Privilege) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.internal.Privilege) */ @Override public void addOrReplacePrivilege(Privilege privilege) { - privilegeMap.put(privilege.getName(), privilege); - privilegeMapDirty = true; + this.privilegeMap.put(privilege.getName(), privilege); + this.privilegeMapDirty = true; } /** @@ -74,19 +73,18 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public Privilege removePrivilege(String privilegeName) { - Privilege privilege = privilegeMap.remove(privilegeName); - privilegeMapDirty = privilege != null; + Privilege privilege = this.privilegeMap.remove(privilegeName); + this.privilegeMapDirty = privilege != null; return privilege; } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.internal.Role) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.internal.Role) */ @Override public void addOrReplaceRole(Role role) { - roleMap.put(role.getName(), role); - roleMapDirty = true; + this.roleMap.put(role.getName(), role); + this.roleMapDirty = true; } /** @@ -94,19 +92,18 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public Role removeRole(String roleName) { - Role role = roleMap.remove(roleName); - roleMapDirty = role != null; + Role role = this.roleMap.remove(roleName); + this.roleMapDirty = role != null; return role; } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.internal.User) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.internal.User) */ @Override public void addOrReplaceUser(User user) { - userMap.put(user.getUsername(), user); - userMapDirty = true; + this.userMap.put(user.getUsername(), user); + this.userMapDirty = true; } /** @@ -114,8 +111,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public User removeUser(String username) { - User user = userMap.remove(username); - userMapDirty = user != null; + User user = this.userMap.remove(username); + this.userMapDirty = user != null; return user; } @@ -124,7 +121,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public Privilege getPrivilege(String privilegeName) { - return privilegeMap.get(privilegeName); + return this.privilegeMap.get(privilegeName); } /** @@ -132,7 +129,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public Role getRole(String roleName) { - return roleMap.get(roleName); + return this.roleMap.get(roleName); } /** @@ -140,7 +137,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public User getUser(String username) { - return userMap.get(username); + return this.userMap.get(username); } /** @@ -150,7 +147,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { public PrivilegePolicy getPolicy(String policyName) { // get the policies class - Class policyClazz = policyMap.get(policyName); + Class policyClazz = this.policyMap.get(policyName); if (policyClazz == null) { return null; } @@ -169,21 +166,21 @@ public class XmlPersistenceHandler implements PersistenceHandler { // USERS // get users file name - String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + String usersFileName = this.parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); if (usersFileName == null || usersFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); } // get users file - File usersFile = new File(basePath + "/" + usersFileName); - boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == usersFileDate; - if (!userMapDirty && usersFileUnchanged) { + File usersFile = new File(this.basePath + "/" + usersFileName); + boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == this.usersFileDate; + if (!this.userMapDirty && usersFileUnchanged) { logger.warn("No users unpersisted and user file unchanged on file system"); } else { logger.info("Persisting users..."); // build XML DOM of users - List users = toDomUsers(certificate); + List users = toDomUsers(); Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_USERS); for (Element userElement : users) { rootElement.add(userElement); @@ -191,20 +188,20 @@ public class XmlPersistenceHandler implements PersistenceHandler { // write DOM to file XmlHelper.writeDocument(rootElement, usersFile); - userMapDirty = true; + this.userMapDirty = true; } // ROLES // get roles file name - String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + String rolesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); if (rolesFileName == null || rolesFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); } // get roles file - File rolesFile = new File(basePath + "/" + rolesFileName); - boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == rolesFileDate; - if (!roleMapDirty && rolesFileUnchanged) { + File rolesFile = new File(this.basePath + "/" + rolesFileName); + boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == this.rolesFileDate; + if (!this.roleMapDirty && rolesFileUnchanged) { logger.warn("No roles unpersisted and roles file unchanged on file system"); } else { logger.info("Persisting roles..."); @@ -218,21 +215,21 @@ public class XmlPersistenceHandler implements PersistenceHandler { // write DOM to file XmlHelper.writeDocument(rootElement, rolesFile); - roleMapDirty = true; + this.roleMapDirty = true; } // PRIVILEGES // get privileges file name - String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); + String privilegesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); if (privilegesFileName == null || privilegesFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); } // get privileges file - File privilegesFile = new File(basePath + "/" + privilegesFileName); + File privilegesFile = new File(this.basePath + "/" + privilegesFileName); boolean privilegesFileUnchanged = privilegesFile.exists() - && privilegesFile.lastModified() == privilegesFileDate; - if (!privilegeMapDirty && privilegesFileUnchanged) { + && privilegesFile.lastModified() == this.privilegesFileDate; + if (!this.privilegeMapDirty && privilegesFileUnchanged) { logger.warn("No privileges unpersisted and privileges file unchanged on file system"); } else { logger.info("Persisting privileges..."); @@ -246,24 +243,24 @@ public class XmlPersistenceHandler implements PersistenceHandler { // write DOM to file XmlHelper.writeDocument(rootElement, privilegesFile); - privilegeMapDirty = true; + this.privilegeMapDirty = true; } // reset dirty states and return if something was dirty, false otherwise - if (userMapDirty || roleMapDirty || privilegeMapDirty) { - userMapDirty = false; - roleMapDirty = false; - privilegeMapDirty = false; + if (this.userMapDirty || this.roleMapDirty || this.privilegeMapDirty) { + this.userMapDirty = false; + this.roleMapDirty = false; + this.privilegeMapDirty = false; return true; - } else { - userMapDirty = false; - roleMapDirty = false; - privilegeMapDirty = false; - - return false; } + + this.userMapDirty = false; + this.roleMapDirty = false; + this.privilegeMapDirty = false; + + return false; } /** @@ -272,14 +269,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void initialize(Map parameterMap) { - roleMap = new HashMap(); - userMap = new HashMap(); - privilegeMap = new HashMap(); - policyMap = new HashMap>(); + this.roleMap = new HashMap(); + this.userMap = new HashMap(); + this.privilegeMap = new HashMap(); + this.policyMap = new HashMap>(); // get and validate base bath - basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); - File basePathF = new File(basePath); + this.basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + File basePathF = new File(this.basePath); if (!basePathF.exists() && !basePathF.isDirectory()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); @@ -294,7 +291,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get roles file - File rolesFile = new File(basePath + "/" + rolesFileName); + File rolesFile = new File(this.basePath + "/" + rolesFileName); if (!rolesFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " @@ -306,7 +303,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read roles readRoles(rolesRootElement); - rolesFileDate = rolesFile.lastModified(); + this.rolesFileDate = rolesFile.lastModified(); // USERS // get users file name @@ -317,7 +314,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get users file - File usersFile = new File(basePath + "/" + usersFileName); + File usersFile = new File(this.basePath + "/" + usersFileName); if (!usersFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " @@ -329,7 +326,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read users readUsers(usersRootElement); - usersFileDate = usersFile.lastModified(); + this.usersFileDate = usersFile.lastModified(); // PRIVILEGES // get privileges file name @@ -340,7 +337,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get privileges file - File privilegesFile = new File(basePath + "/" + privilegesFileName); + File privilegesFile = new File(this.basePath + "/" + privilegesFileName); if (!privilegesFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " @@ -352,7 +349,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read privileges readPrivileges(privilegesRootElement); - privilegesFileDate = privilegesFile.lastModified(); + this.privilegesFileDate = privilegesFile.lastModified(); // POLICIES // get policy file name @@ -363,7 +360,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get policy file - File policyFile = new File(basePath + "/" + policyFileName); + File policyFile = new File(this.basePath + "/" + policyFileName); if (!policyFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " @@ -376,18 +373,18 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read policies readPolicies(policiesRootElement); - userMapDirty = false; - roleMapDirty = false; - privilegeMapDirty = false; + this.userMapDirty = false; + this.roleMapDirty = false; + this.privilegeMapDirty = false; - logger.info("Read " + userMap.size() + " Users"); - logger.info("Read " + roleMap.size() + " Roles"); - logger.info("Read " + privilegeMap.size() + " Privileges"); + logger.info("Read " + this.userMap.size() + " Users"); + logger.info("Read " + this.roleMap.size() + " Roles"); + logger.info("Read " + this.privilegeMap.size() + " Privileges"); // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; - for (String username : userMap.keySet()) { - User user = userMap.get(username); + for (String username : this.userMap.keySet()) { + User user = this.userMap.get(username); if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { privilegeAdminExists = true; break; @@ -437,7 +434,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { locale); // put user in map - userMap.put(username, user); + this.userMap.put(username, user); } } @@ -462,7 +459,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } Role role = new Role(roleName, privileges); - roleMap.put(roleName, role); + this.roleMap.put(roleName, role); } } @@ -479,7 +476,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); - boolean allAllowed = Boolean.valueOf(allAllowedS); + boolean allAllowed = Boolean.valueOf(allAllowedS).booleanValue(); @SuppressWarnings("unchecked") List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); @@ -506,7 +503,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); + this.privilegeMap.put(privilegeName, privilege); } } @@ -523,19 +520,19 @@ public class XmlPersistenceHandler implements PersistenceHandler { Class clazz = ClassHelper.loadClass(policyClass); - policyMap.put(policyName, clazz); + this.policyMap.put(policyName, clazz); } } private List toDomPrivileges() { - List privilegesAsElements = new ArrayList(privilegeMap.size()); + List privilegesAsElements = new ArrayList(this.privilegeMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String privilegeName : privilegeMap.keySet()) { + for (String privilegeName : this.privilegeMap.keySet()) { // get the privilege object - Privilege privilege = privilegeMap.get(privilegeName); + Privilege privilege = this.privilegeMap.get(privilegeName); // create the privilege element Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); @@ -570,13 +567,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { private List toDomRoles() { - List rolesAsElements = new ArrayList(roleMap.size()); + List rolesAsElements = new ArrayList(this.roleMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String roleName : roleMap.keySet()) { + for (String roleName : this.roleMap.keySet()) { // get the role object - Role role = roleMap.get(roleName); + Role role = this.roleMap.get(roleName); // create the role element Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); @@ -596,15 +593,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { return rolesAsElements; } - private List toDomUsers(Certificate certificate) { + private List toDomUsers() { - List usersAsElements = new ArrayList(userMap.size()); + List usersAsElements = new ArrayList(this.userMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String userName : userMap.keySet()) { + for (String userName : this.userMap.keySet()) { // get the user object - User user = userMap.get(userName); + User user = this.userMap.get(userName); // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 4c8a7f7dc..66ddad1d7 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -21,7 +21,6 @@ import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; - /** *

    * This class is a simple application which can be used to bootstrap a new configuration for the @@ -72,10 +71,10 @@ public class BootstrapConfigurationHelper { File pathF = new File(path); if (pathF.exists()) { throw new RuntimeException("Path already exists: " + pathF.getAbsolutePath()); - } else { - if (!pathF.mkdirs()) { - throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); - } + } + + if (!pathF.mkdirs()) { + throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); } // TODO ask other questions... diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index d61e36d04..549dfcd00 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -33,6 +33,7 @@ public class InitializationHelper { /** * @param privilegeContainerXmlFile + * @return */ public static PrivilegeHandler initializeFromXml(File privilegeContainerXmlFile) { diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index c7f3bbf85..e55af8a44 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -60,7 +60,7 @@ public final class Certificate implements Serializable { * @return the locale */ public Locale getLocale() { - return locale; + return this.locale; } /** @@ -75,14 +75,14 @@ public final class Certificate implements Serializable { * @return the sessionId */ public String getSessionId() { - return sessionId; + return this.sessionId; } /** * @return the username */ public String getUsername() { - return username; + return this.username; } /** @@ -94,11 +94,10 @@ public final class Certificate implements Serializable { * @return the authToken if the given authPassword is corret, null otherwise */ public String getAuthToken(String authPassword) { - if (this.authPassword.equals(authPassword)) { - return authToken; - } else { - return null; - } + if (this.authPassword.equals(authPassword)) + return this.authToken; + + return null; } /** @@ -108,11 +107,11 @@ public final class Certificate implements Serializable { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((authPassword == null) ? 0 : authPassword.hashCode()); - result = prime * result + ((authToken == null) ? 0 : authToken.hashCode()); - result = prime * result + ((locale == null) ? 0 : locale.hashCode()); - result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); - result = prime * result + ((username == null) ? 0 : username.hashCode()); + result = prime * result + ((this.authPassword == null) ? 0 : this.authPassword.hashCode()); + result = prime * result + ((this.authToken == null) ? 0 : this.authToken.hashCode()); + result = prime * result + ((this.locale == null) ? 0 : this.locale.hashCode()); + result = prime * result + ((this.sessionId == null) ? 0 : this.sessionId.hashCode()); + result = prime * result + ((this.username == null) ? 0 : this.username.hashCode()); return result; } @@ -128,30 +127,30 @@ public final class Certificate implements Serializable { if (!(obj instanceof Certificate)) return false; Certificate other = (Certificate) obj; - if (authPassword == null) { + if (this.authPassword == null) { if (other.authPassword != null) return false; - } else if (!authPassword.equals(other.authPassword)) + } else if (!this.authPassword.equals(other.authPassword)) return false; - if (authToken == null) { + if (this.authToken == null) { if (other.authToken != null) return false; - } else if (!authToken.equals(other.authToken)) + } else if (!this.authToken.equals(other.authToken)) return false; - if (locale == null) { + if (this.locale == null) { if (other.locale != null) return false; - } else if (!locale.equals(other.locale)) + } else if (!this.locale.equals(other.locale)) return false; - if (sessionId == null) { + if (this.sessionId == null) { if (other.sessionId != null) return false; - } else if (!sessionId.equals(other.sessionId)) + } else if (!this.sessionId.equals(other.sessionId)) return false; - if (username == null) { + if (this.username == null) { if (other.username != null) return false; - } else if (!username.equals(other.username)) + } else if (!this.username.equals(other.username)) return false; return true; } @@ -163,11 +162,11 @@ public final class Certificate implements Serializable { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Certificate [sessionId="); - builder.append(sessionId); + builder.append(this.sessionId); builder.append(", username="); - builder.append(username); + builder.append(this.username); builder.append(", locale="); - builder.append(locale); + builder.append(this.locale); builder.append("]"); return builder.toString(); } diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index bce742671..82ad3e070 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -46,7 +46,7 @@ public class PrivilegeRep implements Serializable { * @return the name */ public String getName() { - return name; + return this.name; } /** @@ -61,7 +61,7 @@ public class PrivilegeRep implements Serializable { * @return the policy */ public String getPolicy() { - return policy; + return this.policy; } /** @@ -76,7 +76,7 @@ public class PrivilegeRep implements Serializable { * @return the allAllowed */ public boolean isAllAllowed() { - return allAllowed; + return this.allAllowed; } /** @@ -91,7 +91,7 @@ public class PrivilegeRep implements Serializable { * @return the denyList */ public Set getDenyList() { - return denyList; + return this.denyList; } /** @@ -106,7 +106,7 @@ public class PrivilegeRep implements Serializable { * @return the allowList */ public Set getAllowList() { - return allowList; + return this.allowList; } /** diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index 155876e23..d144b1781 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -16,7 +16,13 @@ package ch.eitchnet.privilege.model; */ public interface Restrictable { + /** + * @return + */ public String getPrivilegeName(); + /** + * @return + */ public Object getPrivilegeValue(); } diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index 12c3d755d..8a7bb538f 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -37,7 +37,7 @@ public class RoleRep implements Serializable { * @return the name */ public String getName() { - return name; + return this.name; } /** @@ -52,7 +52,7 @@ public class RoleRep implements Serializable { * @return the privileges */ public Set getPrivileges() { - return privileges; + return this.privileges; } /** diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index c7310d750..be5133b10 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -51,7 +51,7 @@ public class UserRep implements Serializable { * @return the username */ public String getUsername() { - return username; + return this.username; } /** @@ -66,7 +66,7 @@ public class UserRep implements Serializable { * @return the firstname */ public String getFirstname() { - return firstname; + return this.firstname; } /** @@ -81,7 +81,7 @@ public class UserRep implements Serializable { * @return the surname */ public String getSurname() { - return surname; + return this.surname; } /** @@ -96,7 +96,7 @@ public class UserRep implements Serializable { * @return the userState */ public UserState getUserState() { - return userState; + return this.userState; } /** @@ -111,7 +111,7 @@ public class UserRep implements Serializable { * @return the roles */ public Set getRoles() { - return roles; + return this.roles; } /** @@ -126,7 +126,7 @@ public class UserRep implements Serializable { * @return the locale */ public Locale getLocale() { - return locale; + return this.locale; } /** diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java index 955bdefaf..391975b1f 100644 --- a/src/ch/eitchnet/privilege/model/UserState.java +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -12,7 +12,7 @@ package ch.eitchnet.privilege.model; /** * @author rvonburg - * + * */ public enum UserState { NEW, diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index da188f9d8..037a4f492 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -29,6 +29,9 @@ public final class Privilege { private final Set allowList; /** + * + * @param name + * @param policy * @param allAllowed * @param denyList * @param allowList @@ -45,42 +48,43 @@ public final class Privilege { * @return the name */ public String getName() { - return name; + return this.name; } /** * @return the policy */ public String getPolicy() { - return policy; + return this.policy; } /** * @return the allAllowed */ public boolean isAllAllowed() { - return allAllowed; + return this.allAllowed; } /** * @return the allowList */ public Set getAllowList() { - return allowList; + return this.allowList; } /** * @return the denyList */ public Set getDenyList() { - return denyList; + return this.denyList; } /** * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients */ public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(name, policy, allAllowed, new HashSet(denyList), new HashSet(allowList)); + return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet(this.denyList), + new HashSet(this.allowList)); } /** @@ -90,15 +94,15 @@ public final class Privilege { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Privilege [name="); - builder.append(name); + builder.append(this.name); builder.append(", policy="); - builder.append(policy); + builder.append(this.policy); builder.append(", allAllowed="); - builder.append(allAllowed); + builder.append(this.allAllowed); builder.append(", denyList="); - builder.append(denyList); + builder.append(this.denyList); builder.append(", allowList="); - builder.append(allowList); + builder.append(this.allowList); builder.append("]"); return builder.toString(); } @@ -110,11 +114,11 @@ public final class Privilege { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (allAllowed ? 1231 : 1237); - result = prime * result + ((allowList == null) ? 0 : allowList.hashCode()); - result = prime * result + ((denyList == null) ? 0 : denyList.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((policy == null) ? 0 : policy.hashCode()); + result = prime * result + (this.allAllowed ? 1231 : 1237); + result = prime * result + ((this.allowList == null) ? 0 : this.allowList.hashCode()); + result = prime * result + ((this.denyList == null) ? 0 : this.denyList.hashCode()); + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + result = prime * result + ((this.policy == null) ? 0 : this.policy.hashCode()); return result; } @@ -130,27 +134,27 @@ public final class Privilege { if (getClass() != obj.getClass()) return false; Privilege other = (Privilege) obj; - if (allAllowed != other.allAllowed) + if (this.allAllowed != other.allAllowed) return false; - if (allowList == null) { + if (this.allowList == null) { if (other.allowList != null) return false; - } else if (!allowList.equals(other.allowList)) + } else if (!this.allowList.equals(other.allowList)) return false; - if (denyList == null) { + if (this.denyList == null) { if (other.denyList != null) return false; - } else if (!denyList.equals(other.denyList)) + } else if (!this.denyList.equals(other.denyList)) return false; - if (name == null) { + if (this.name == null) { if (other.name != null) return false; - } else if (!name.equals(other.name)) + } else if (!this.name.equals(other.name)) return false; - if (policy == null) { + if (this.policy == null) { if (other.policy != null) return false; - } else if (!policy.equals(other.policy)) + } else if (!this.policy.equals(other.policy)) return false; return true; } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index a5390bea5..664216cb5 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -39,14 +39,14 @@ public final class Role { * @return the name */ public String getName() { - return name; + return this.name; } /** * @return */ public Set getPrivileges() { - return privileges; + return this.privileges; } /** @@ -54,14 +54,14 @@ public final class Role { * @return */ public boolean hasPrivilege(String key) { - return privileges.contains(key); + return this.privileges.contains(key); } /** * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - return new RoleRep(name, new HashSet(privileges)); + return new RoleRep(this.name, new HashSet(this.privileges)); } /** @@ -71,9 +71,9 @@ public final class Role { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Role [name="); - builder.append(name); + builder.append(this.name); builder.append(", privileges="); - builder.append(privileges); + builder.append(this.privileges); builder.append("]"); return builder.toString(); } @@ -85,8 +85,8 @@ public final class Role { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((privileges == null) ? 0 : privileges.hashCode()); + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + result = prime * result + ((this.privileges == null) ? 0 : this.privileges.hashCode()); return result; } @@ -102,15 +102,15 @@ public final class Role { if (getClass() != obj.getClass()) return false; Role other = (Role) obj; - if (name == null) { + if (this.name == null) { if (other.name != null) return false; - } else if (!name.equals(other.name)) + } else if (!this.name.equals(other.name)) return false; - if (privileges == null) { + if (this.privileges == null) { if (other.privileges != null) return false; - } else if (!privileges.equals(other.privileges)) + } else if (!this.privileges.equals(other.privileges)) return false; return true; } diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index 4d4ff4355..f5003d0ca 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -24,6 +24,12 @@ public final class Session { private final String authPassword; /** + * + * @param sessionId + * @param authToken + * @param authPassword + * @param username + * @param loginTime */ public Session(String sessionId, String authToken, String authPassword, String username, long loginTime) { this.sessionId = sessionId; @@ -37,35 +43,35 @@ public final class Session { * @return the sessionId */ public String getSessionId() { - return sessionId; + return this.sessionId; } /** * @return the authToken */ public String getAuthToken() { - return authToken; + return this.authToken; } /** * @return the authPassword */ public String getAuthPassword() { - return authPassword; + return this.authPassword; } /** * @return the username */ public String getUsername() { - return username; + return this.username; } /** * @return the loginTime */ public long getLoginTime() { - return loginTime; + return this.loginTime; } /** @@ -75,11 +81,11 @@ public final class Session { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((authPassword == null) ? 0 : authPassword.hashCode()); - result = prime * result + ((authToken == null) ? 0 : authToken.hashCode()); - result = prime * result + (int) (loginTime ^ (loginTime >>> 32)); - result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); - result = prime * result + ((username == null) ? 0 : username.hashCode()); + result = prime * result + ((this.authPassword == null) ? 0 : this.authPassword.hashCode()); + result = prime * result + ((this.authToken == null) ? 0 : this.authToken.hashCode()); + result = prime * result + (int) (this.loginTime ^ (this.loginTime >>> 32)); + result = prime * result + ((this.sessionId == null) ? 0 : this.sessionId.hashCode()); + result = prime * result + ((this.username == null) ? 0 : this.username.hashCode()); return result; } @@ -95,27 +101,27 @@ public final class Session { if (!(obj instanceof Session)) return false; Session other = (Session) obj; - if (authPassword == null) { + if (this.authPassword == null) { if (other.authPassword != null) return false; - } else if (!authPassword.equals(other.authPassword)) + } else if (!this.authPassword.equals(other.authPassword)) return false; - if (authToken == null) { + if (this.authToken == null) { if (other.authToken != null) return false; - } else if (!authToken.equals(other.authToken)) + } else if (!this.authToken.equals(other.authToken)) return false; - if (loginTime != other.loginTime) + if (this.loginTime != other.loginTime) return false; - if (sessionId == null) { + if (this.sessionId == null) { if (other.sessionId != null) return false; - } else if (!sessionId.equals(other.sessionId)) + } else if (!this.sessionId.equals(other.sessionId)) return false; - if (username == null) { + if (this.username == null) { if (other.username != null) return false; - } else if (!username.equals(other.username)) + } else if (!this.username.equals(other.username)) return false; return true; } @@ -127,11 +133,11 @@ public final class Session { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Session [sessionId="); - builder.append(sessionId); + builder.append(this.sessionId); builder.append(", username="); - builder.append(username); + builder.append(this.username); builder.append(", loginTime="); - builder.append(loginTime); + builder.append(this.loginTime); builder.append("]"); return builder.toString(); } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 787a6d8c6..6808b899d 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -65,7 +65,7 @@ public final class User { * @return the username */ public String getUsername() { - return username; + return this.username; } /** @@ -81,42 +81,35 @@ public final class User { // field even though? The User object should be private, but maybe I // forgot something? - return password; - } - - /** - * @return the password - */ - public boolean isPassword(String password) { - return this.password.equals(password); + return this.password; } /** * @return the firstname */ public String getFirstname() { - return firstname; + return this.firstname; } /** * @return the surname */ public String getSurname() { - return surname; + return this.surname; } /** * @return the userState */ public UserState getState() { - return userState; + return this.userState; } /** * @return the roles */ public Set getRoles() { - return roles; + return this.roles; } /** @@ -126,21 +119,22 @@ public final class User { * @return true if the this user has the specified role */ public boolean hasRole(String role) { - return roles.contains(role); + return this.roles.contains(role); } /** * @return the locale */ public Locale getLocale() { - return locale; + return this.locale; } /** * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients */ public UserRep asUserRep() { - return new UserRep(username, firstname, surname, userState, new HashSet(roles), locale); + return new UserRep(this.username, this.firstname, this.surname, this.userState, + new HashSet(this.roles), this.locale); } /** @@ -150,17 +144,17 @@ public final class User { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("User [username="); - builder.append(username); + builder.append(this.username); builder.append(", firstname="); - builder.append(firstname); + builder.append(this.firstname); builder.append(", surname="); - builder.append(surname); + builder.append(this.surname); builder.append(", locale="); - builder.append(locale); + builder.append(this.locale); builder.append(", userState="); - builder.append(userState); + builder.append(this.userState); builder.append(", roles="); - builder.append(roles); + builder.append(this.roles); builder.append("]"); return builder.toString(); } diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/test/ch/eitchnet/privilege/test/TestRestrictable.java index 1f65beef6..e9badc734 100644 --- a/test/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/test/ch/eitchnet/privilege/test/TestRestrictable.java @@ -14,18 +14,20 @@ import ch.eitchnet.privilege.model.Restrictable; /** * @author rvonburg - * + * */ public class TestRestrictable implements Restrictable { - /**@see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() */ @Override public String getPrivilegeName() { return "Service"; } - /**@see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() */ @Override public Object getPrivilegeValue() { From 6c5b266f677ae6a3b4ca9e295dea40e0584bf23c Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 19 Sep 2010 20:19:38 +0000 Subject: [PATCH 036/457] [Minor] wrote some java docs --- .../handler/DefaultEncryptionHandler.java | 22 +++ .../handler/DefaultPrivilegeHandler.java | 44 +++--- .../privilege/handler/EncryptionHandler.java | 4 +- .../privilege/handler/PersistenceHandler.java | 11 +- .../privilege/handler/PrivilegeHandler.java | 126 +++++++++++++++--- .../handler/XmlPersistenceHandler.java | 14 +- .../privilege/helper/XmlConstants.java | 1 + src/ch/eitchnet/privilege/model/UserRep.java | 14 +- .../privilege/model/internal/User.java | 23 +++- .../privilege/test/PrivilegeTest.java | 6 +- 10 files changed, 208 insertions(+), 57 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 1ee77872d..4b33f648a 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.handler; import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; @@ -22,13 +23,34 @@ import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.i18n.PrivilegeException; /** + *

    + * This default {@link EncryptionHandler} creates nokens by using a {@link SecureRandom} object. Hashing is done by + * using {@link MessageDigest} and the configured algorithm which is passed in the parameters + *

    + * + * Required parameters: + *
      + *
    • {@link XmlConstants#XML_PARAM_HASH_ALGORITHM}
    • + *
    + * * @author rvonburg * */ public class DefaultEncryptionHandler implements EncryptionHandler { + + /** + * The log4j logger used in this instance + */ private static final Logger logger = Logger.getLogger(DefaultEncryptionHandler.class); + /** + * The {@link SecureRandom} which is used to create new tokens + */ private SecureRandom secureRandom; + + /** + * The configured hash algorithm for this instance + */ private String hashAlgorithm; /** diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 28ee4c489..7e8b813a2 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -151,8 +151,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep - .getUserState(), userRep.getRoles(), userRep.getLocale()); + User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep + .getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale()); // delegate to persistence handler this.persistenceHandler.addOrReplaceUser(user); @@ -229,8 +229,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet(currentRoles); newRoles.add(roleName); - User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user - .getState(), newRoles, user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user + .getSurname(), user.getUserState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -334,8 +334,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); - User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user - .getState(), newRoles, user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user + .getSurname(), user.getUserState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -483,8 +483,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user - .getState(), user.getRoles(), locale); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user + .getSurname(), user.getUserState(), user.getRoles(), locale); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -507,8 +507,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUsername(), user.getPassword(), firstname, surname, user.getState(), user - .getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, user + .getUserState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -541,8 +541,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user - .getState(), user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), user + .getSurname(), user.getUserState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -565,8 +565,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), state, - user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user + .getSurname(), state, user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -604,8 +604,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); // validate if user is allowed to login - if (user.getState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + if (user.getUserState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getUserState()); // validate user has at least one role if (user.getRoles().isEmpty()) { @@ -819,7 +819,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this validateIsPrivilegeAdmin(certificate); - return this.persistenceHandler.persist(certificate); + return this.persistenceHandler.persist(); } /** @@ -850,12 +850,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @author rvonburg */ private class CertificateSessionPair { + /** + * The {@link Session} + */ public final Session session; + /** + * The {@link Certificate} + */ public final Certificate certificate; /** + * Creates a new {@link CertificateSessionPair} with the given session and certificate + * * @param session + * the session * @param certificate + * the certificate */ public CertificateSessionPair(Session session, Certificate certificate) { this.session = session; diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index 6a1d88271..c4f6b34b9 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -38,8 +38,8 @@ public interface EncryptionHandler { public String convertToHash(String string); /** - * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this map - * might need + * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration the + * concrete {@link EncryptionHandler} might need * * @param parameterMap * a map containing configuration properties diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index ee4459a20..2e34f1703 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -12,7 +12,6 @@ package ch.eitchnet.privilege.handler; import java.util.Map; -import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -136,14 +135,18 @@ public interface PersistenceHandler { public void addOrReplacePrivilege(Privilege privilege); /** - * @param certificate + * Informs this {@link PersistenceHandler} to persist any changes which need to be saved * - * @return + * @return true if changes were persisted successfully, false if something went wrong */ - public boolean persist(Certificate certificate); + public boolean persist(); /** + * Initialize the concrete {@link PersistenceHandler}. The passed parameter map contains any configuration the + * concrete {@link PersistenceHandler} might need + * * @param parameterMap + * a map containing configuration properties */ public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 0c8264076..749d425ff 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -22,6 +22,7 @@ import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -33,118 +34,195 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; public interface PrivilegeHandler { /** - * This is the role users must have, if they are allowed to modify objects + * value = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects */ public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; /** - * @param username + * Returns a {@link UserRep} for the given username * - * @return + * @param username + * the name of the {@link UserRep} to return + * + * @return the {@link UserRep} for the given username, or null if it was not found */ public UserRep getUser(String username); /** - * @param roleName + * Returns a {@link RoleRep} for the given roleName * - * @return + * @param roleName + * the name of the {@link RoleRep} to return + * + * @return the {@link RoleRep} for the given roleName, or null if it was not found */ public RoleRep getRole(String roleName); /** - * @param privilegeName + * Returns a {@link PrivilegeRep} for the given privilegeName * - * @return + * @param privilegeName + * the name of the {@link PrivilegeRep} to return + * + * @return the {@link PrivilegeRep} for the given privilegeName, or null if it was not found */ public PrivilegeRep getPrivilege(String privilegeName); /** - * @param policyName + * Returns a {@link PrivilegePolicy} for the given policyName * - * @return + * @param policyName + * the name of the {@link PrivilegePolicy} to return + * + * @return the {@link PrivilegePolicy} for the given policyName, or null if it was not found */ public PrivilegePolicy getPolicy(String policyName); /** - * @param certificate - * @param username + * Removes the user with the given username * - * @return + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the user to remove + * + * @return the {@link UserRep} of the user removed, or null if the user did not exist */ public UserRep removeUser(Certificate certificate, String username); /** + * Removes the role with the given roleName from the user with the given username + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username + * the username of the user from which the role is to be removed * @param roleName + * the rolename of the role to remove from the user */ public void removeRoleFromUser(Certificate certificate, String username, String roleName); /** - * @param certificate - * @param roleName + * Removes the role with the given roleName * - * @return + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param roleName + * the roleName of the role to remove + * + * @return the {@link RoleRep} of the role removed, or null if the role did not exist */ public RoleRep removeRole(Certificate certificate, String roleName); /** + * Removes the privilege with the given privilegeName from the role with the given roleName + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param roleName + * the roleName of the role from which the privilege is to be removed * @param privilegeName + * the privilegeName of the privilege to remove from the role */ public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); /** - * @param certificate - * @param privilegeName + * Removes the privilege with the given privilegeName * - * @return + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the privilegeName of the privilege to remove + * + * @return the {@link PrivilegeRep} of the privilege removed, or null if the privilege did not exist */ public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); /** + *

    + * Adds a new user, or replaces the user with the information from this {@link UserRep} if the user already exists + *

    + * + *

    + * If the password given is null, then the user is created, but can not not login! Otherwise the password must meet + * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(String)} + *

    + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param userRep + * the {@link UserRep} containing the information to create the new {@link User} * @param password + * the password of the new user. If the password is null, then this is accepted but the user can not + * login, otherwise the password must be validated against + * {@link PrivilegeHandler#validatePassword(String)} */ public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); /** + * Adds a new role, or replaces the role with the information from this {@link RoleRep} if the role already exists + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param roleRep + * the {@link RoleRep} containing the information to create the new {@link Role} */ public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); /** + * Adds a new privilege, or replaces the privilege with the information from this {@link PrivilegeRep} if the + * privilege already exists + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeRep + * the {@link PrivilegeRep} containing the information to create the new {@link Privilege} */ public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); /** + * Adds the role with the given roleName to the {@link User} with the given username + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username + * the username of the {@link User} to which the role should be added * @param roleName + * the roleName of the {@link Role} which should be added to the {@link User} */ public void addRoleToUser(Certificate certificate, String username, String roleName); /** + * Adds the privilege with the given privilegeName to the {@link Role} with the given roleName + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param roleName + * the roleName of the {@link Role} to which the privilege should be added * @param privilegeName + * the privilegeName of the {@link Privilege} which should be added to the {@link Role} */ public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); /** + * Changes the password for the {@link User} with the given username. If the password is null, then the {@link User} + * can not login anymore. Otherwise the password must meet the requirements of the implementation under + * {@link PrivilegeHandler#validatePassword(String)} + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username + * the username of the {@link User} for which the password is to be changed * @param password + * the new password for this user. If the password is null, then the {@link User} can not login anymore. + * Otherwise the password must meet the requirements of the implementation under + * {@link PrivilegeHandler#validatePassword(String)} */ public void setUserPassword(Certificate certificate, String username, String password); /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username * @param firstname * @param surname @@ -153,6 +231,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username * @param state */ @@ -160,6 +239,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username * @param locale */ @@ -167,6 +247,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeName * @param policyName */ @@ -174,6 +255,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeName * @param allAllowed */ @@ -181,6 +263,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeName * @param denyList */ @@ -188,6 +271,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeName * @param allowList */ @@ -206,6 +290,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param restrictable * * @return @@ -276,14 +361,19 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * * @return */ public boolean persist(Certificate certificate); /** + * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this map + * might need * * @param parameterMap + * a map containing configuration properties + * * @param encryptionHandler * @param persistenceHandler */ diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index d926ae2b2..d1fa6a43c 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -28,7 +28,6 @@ import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -159,10 +158,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist(ch.eitchnet.privilege.model.Certificate) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() */ @Override - public boolean persist(Certificate certificate) { + public boolean persist() { // USERS // get users file name @@ -404,6 +403,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { List userElements = usersRootElement.elements(XmlConstants.XML_USER); for (Element userElement : userElements) { + String userId = userElement.attributeValue(XmlConstants.XML_ATTR_USER_ID); + String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); @@ -430,8 +431,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // create user - User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), - locale); + User user = new User(userId, username, password, firstname, surname, userState, Collections + .unmodifiableSet(roles), locale); // put user in map this.userMap.put(username, user); @@ -605,6 +606,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); + userElement.addAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); @@ -620,7 +622,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // add state element Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); - stateElement.setText(user.getState().toString()); + stateElement.setText(user.getUserState().toString()); userElement.add(stateElement); // add locale element diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 5a42dbe34..b78ce40fa 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -46,6 +46,7 @@ public class XmlConstants { public static final String XML_ATTR_NAME = "name"; public static final String XML_ATTR_VALUE = "value"; public static final String XML_ATTR_POLICY = "policy"; + public static final String XML_ATTR_USER_ID = "userId"; public static final String XML_ATTR_USERNAME = "username"; public static final String XML_ATTR_PASSWORD = "password"; diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index be5133b10..a91ab8e2c 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -22,6 +22,7 @@ public class UserRep implements Serializable { private static final long serialVersionUID = 1L; + private final String userId; private String username; private String firstname; private String surname; @@ -30,6 +31,7 @@ public class UserRep implements Serializable { private Locale locale; /** + * @param userId * @param username * @param firstname * @param surname @@ -37,8 +39,9 @@ public class UserRep implements Serializable { * @param roles * @param locale */ - public UserRep(String username, String firstname, String surname, UserState userState, Set roles, - Locale locale) { + public UserRep(String userId, String username, String firstname, String surname, UserState userState, + Set roles, Locale locale) { + this.userId = userId; this.username = username; this.firstname = firstname; this.surname = surname; @@ -47,6 +50,13 @@ public class UserRep implements Serializable { this.locale = locale; } + /** + * @return the userId + */ + public String getUserId() { + return this.userId; + } + /** * @return the username */ diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 6808b899d..30f51901c 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -24,6 +24,8 @@ import ch.eitchnet.privilege.model.UserState; */ public final class User { + private final String userId; + private final String username; private final String password; @@ -37,7 +39,7 @@ public final class User { private final Locale locale; /** - * + * @param userId * @param username * @param password * @param firstname @@ -46,9 +48,11 @@ public final class User { * @param roles * @param locale */ - public User(String username, String password, String firstname, String surname, UserState userState, + public User(String userId, String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale) { + this.userId = userId; + this.username = username; this.password = password; this.userState = userState; @@ -61,6 +65,13 @@ public final class User { this.locale = locale; } + /** + * @return the userId + */ + public String getUserId() { + return userId; + } + /** * @return the username */ @@ -101,7 +112,7 @@ public final class User { /** * @return the userState */ - public UserState getState() { + public UserState getUserState() { return this.userState; } @@ -133,7 +144,7 @@ public final class User { * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients */ public UserRep asUserRep() { - return new UserRep(this.username, this.firstname, this.surname, this.userState, + return new UserRep(this.userId, this.username, this.firstname, this.surname, this.userState, new HashSet(this.roles), this.locale); } @@ -143,7 +154,9 @@ public final class User { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("User [username="); + builder.append("User [userId="); + builder.append(this.userId); + builder.append(", username="); builder.append(this.username); builder.append(", firstname="); builder.append(this.firstname); diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 90960ab95..2416508a9 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -90,7 +90,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); // let's add a new user bob - UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", "bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); @@ -154,7 +154,7 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user bob - UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", "bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } @@ -173,7 +173,7 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted - UserRep userRep = new UserRep("ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("2", "ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } From 3238c024bf92726ce35572f4a86810086bdd31fa Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 1 Nov 2010 22:01:26 +0000 Subject: [PATCH 037/457] --- docs/PrivilegeHandlers.dia | Bin 2034 -> 2890 bytes docs/PrivilegeModelPrivilege.dia | Bin 2230 -> 2108 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/PrivilegeHandlers.dia b/docs/PrivilegeHandlers.dia index 4f8da83b765831d300996f1c1f64ca25a1fdaabb..ca0e91507482e2b4d006b28ed34007a4b0c137dc 100644 GIT binary patch literal 2890 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 b12cJx9BV|Aw85FYGg=ypsl!w$TAKd=Ic%m7 literal 2034 zcmaKqc{~#iAIA&1N^_>)k`0mHOha=H&3zPdOHq_%axcP6IdT_~99whW{2bG$jSRUm z=ah_=4o9vW)11FwJ%2o}=XpJ^*Yo|nUf)07f4u*CgHrep{XQ=4Wj8m>q?R~tweCi* zlB!028MB16ykrfU^egw<$`T=~O!B`~N-3c(1zi6_Dn@9wbD~5&AvYb_rI!Jn#)PD$ zv#9&3-(K)Rj1>0pue@^DK=$bwH5R3kh5LJB@~_veICi(!*B1wCXL8?dp50T42(iys zl%V=+fIuK2eL#}v=qF!PT+=pah5!#KMD1@m){_0(<&%w9->iz3o;gJquid_78Sq{1 z^Ay%@AhNj)Dv^!hk!;*Ta;)}!s9_9zLoTXg@Hq4E+Pt}4aK7IpBaH@5TX@jl^Z^Yv zEK8{fueUwZX}w8xQaC5^-LUN*mi+``@@>4)bqznXMRwE>Gq+0G%z2$ux|;w)6noue z#?FtBmi(E%Y^KeAusLYNOS_0WX#xNMx_y0lJQWeT6_N3hayFpII6lj4wyN)knZ@*rCVg1n4)@UyiDP~* zZnr@O_NXT}7OF`jBKak<22DSR^NyoJk&)xIx zfwC+`wzy(4wX+(w?+rP)rN9E0f_%Dcmu42mUGo!1E--9^Zm#O%YTVv`#E8bT=fK7Z z;DOf4`x=>gS^ju($@5R9&PP1eiLTG%v;23;zVtTg0ZXkex?w(J3Ei{I?gFL?dn{m? zJOYBhGA+jg(t>qP>lp6f*9>HquS{I$q|r6>wjW22tmZ7Hy%TT|>!&95^DdsWfRZu+ zPzhyu&(aH#W`o?NJq*6j6kR!A9bRQdgQUdB1igdtKDl2tXui)S;<0sQUE>35vW@8u zDdJh#;n0RvTb%k)E{o2PhFq(3frCp*M;%Mx3P!xbb&&F6(}@TzqL} z5wm`kgp|3dCy+20=c_fArL>&0z0}hRXV|Kq4*rhCOS82J3;FSdiF5L?tA?=NA_1%x z4PH}%*Qgb1&+u^Uo@5S$dv&L7?DO|^(SrDs4=#Tr(5vcrbr{HW^KXctyBGqpdGV?#(U=OsU{>R=;i(?fED2XsU(H*L;dCRP9w zJg(?gmC2;iK(4K`UOhZ*ssOeq$fiLw=h40 z;pcrV%K<3+HV8Er{Q-Z+T3IcU9>#j3$38DQh-ifO)D#@QI`uW0$+zjZMp0=ofzq|? zH~b11`Bq;(ML{~JLQgtnTMUvb+h&w-#d+nW*wylAbl#H%DUJ46QH452;q_Z)^YrJB=;yaYv8C)Ag| zsGO(X&qj^(l{+BFl=-Z`Ozz4l7maD?I5m5)k5PQpi=2r-524i1I;?_vhuWhkwIZ1N zBaW!hpH{Au@&=C|NFCPZ3g-FB-z$nqY&1tq zUbwlyZ%v{aP|^=tfoYhsF4_=5+we#7 z7J9Z~yaYWdZQ(y+WPw4`L+eUHE$(&>xjY6Bz)0a6G#-xZ*L%*qDiy2i6*i-lVFfPR z(HvwXSifcud0*1xzO>_Y_EHVxr^KX2AgBc}Rc?fuE_vOK`_v}rrB{6^=9dJ_Uw?xu zQe=JC^|!{3MEp-h_A~aL><8{IjB4oN6D(vs*hLr`hLzBzLodrz)LU8%_hV!$gdO-H z(AZxs^1gl=e_VmeASk|j2sTQW=@niyte=r5dDQFL)>J`H?t%irTJ_0NVQXeSLIT@z@^Ehs1?4pf9+_AVGk7DgL#Kji&iU?mV9m|*#) zJ8yjaZ3&H)otOsI+62z4pRBwvr@7(MGP`@AK#&PFT8f_#YB=Sosq)#2=%HaRHoZ&X zW55OEd)*kLe<;5J#5aFD|msue6SJHKsb% zd4wFJEY+6D6OVX)Fnz1@{tCFYi8Bq0s$MudWB2$`*@SoU|9RMe=1@sg4ITofi%)8J zs}0{O=tE{23{upZfp7fCG~6fsK#)I4XrHqN7#@Qd zPSuI9AEM#IDyda=;9uQ2o2*n%v>IpEY3JI9@A_;T{vGz}WpmvDUq8B5uVMbsp??7W CZ2*h_ diff --git a/docs/PrivilegeModelPrivilege.dia b/docs/PrivilegeModelPrivilege.dia index 85fc14ffb44e591e416dc0963e1dfbbad13aad08..8842f4ea446c450e57c1310fee9e10914e695b60 100644 GIT binary patch literal 2108 zcmZvbXEYlM1BMf$R;d`D4vE;67PXbwgiw^Cg0!KPCd4S=O6|~L)TTzGY6Nv#rHX6S z9!1qEQXzIj(HOOF?|09+=X^iD`~G^)^XvWdNF;Cr{vI3qIvO2F(-sNeti*h~mM;{; z&w;dsOtF7 zj%0;Oz3Y-F#_b883RI=tEI(pk@6Fomhm+vpipYA>c0^~d?#Tj=>T9OozUmbYESFZW>B;0HU<>6(%f3E@yB@1xM?lGx(hvS=KDq#C6eDL{G zX-p|Eh_5KJO86wgNlr;NNOHE36l#@OIKN))!fJcpjLx(UDyEu-49xy4IeM(D+fk+S zI%21xCY0hd%B;;ll>N{+cc0E$Xb|oR>RE)Ijunb6_j%RY=5zHZfDndRC5bz z<8u-ULd$+Drlg&71C;lxKL3?Bizf+Gig4?KCLrDOKb1UXJmZbr_0QSy4Bom$-@ zU`*t0M@;Mv4c@i&skL?;f!2>Zy1#|k4sOq2N9&@c2Y>q4<6XYx3ePSO@hyJ4x-00D zwU2mL>(W5qy33_E1|*k=I{xf)eq7v{>NxjEAoS}~muU0;)!+KZB8_vj?3e;NS)QB0Hxp^Z>JF?m(EvncTr+0tbHLGW0W*2iS60q(U z%Q9ldBP|x*uz~f3OH|skD@swTtq9qe3aKOkf<4Kc6{7YfFqs^??E3D9arfbtc)f5n zXXd2>f>y#*awxta=9Ug)Y_FCTz~4;|&=+Af-6f!zJScTZc2-^%g=3M5ZbzwlS)yFD z`0{g97T{k^RFzYPVhRm1rZo4XZ^>=7X-{^ad~^1>Mok>5TpMjYbJxNRj6iB6d%(a# ziq>JgShAhnz^~Eg*Naaz&mcsV^iPHDkb0*+m?@eqb84AvJGC673Ld>DZ$HMDBm&ysq*Jk3=kFn+1E(%ecNc{*vJ?6Ida!i zuI@(31!VJrd#_j<aGyu{D zgbWV(8hx%pnUG;1u2qY8@xjk5eYgxR7C5tnu$wrX9#9Rxxe>JI={vKjw{4EMxuJ4- z6t(JBYm5Cs-j+xOJtdiU+@Ku4@`cD5T#GPpJWEN=&VBU!qF1~^ShaF`JhjBWG@IC? zbcy~4?n35Ipx3 z7WsM0u%BJ=Obz_sAtp?6>tO+%&zr>dY5MAQj*Dy}z<3d3kX+=P+b=!{lY1Tv5vS%8xe{}R~ z1mX66cj|}#HmP9+re&p1$1TaPfYho4Wuz=jQ=ULUMp9%`jyP^1(Eo&-L?@YoF_b1F zIu)%mqxxp;gbo~r7q_LFdDMZqj0mc5u?$W0yNTcJ^RrK8HcX36d%V{6l8Tbg&cwCg z(goaQ51lGT%Q5Q#lTJJ1>4LY*6T1a}Qn=OuG++%oj_b2P0oYJCDj4vOc>MaBdy~iE z9gdGcq_GT-(N&R|??F_#S#szqy9Gc7AOt|3gB}z%&Z!8>U&sKMMM)Wg(l2hI?dMF8 zoZ`SFfVm~3*N36<>w+#CItRpXTw&uGM_QKYl+?v;W5a7WV{E=g1T zFZt?L3i2AH4{mZ24vn@H=(mwsg^tbnu=qkrDmk0i1NNjG`Y%93>KGy)x=98+ED$$D zvzbRpx2B3sbIf;|IkbWp-2R>Lq~q>b2kw7Pmxm5wohJYYfOJ&q$U$mld|5hY+Uf=F z%fKvvRWJs*BMv8319=?pxuF#o(*7>dHcGx#_)f@wOWX|9UYgSO0Qd`_8UtJa literal 2230 zcmZ{Yc{mde1II~BIWjBEeN9oxHQS2hEHTOzLMFr9@Pf8i`)(E_)19w*f6%q>f(rJZt9N zX(B1~1C(sV+zKueFJvRy5?Oa4xSOO92#CZWFjRi8u9Ha;Z6Ze>e9!X?n#4TOUfT3y zbW-jr%)l%ODiQ4Wz4n7|f+^rE1Ja)U)P2iir{o&@(%yL`M_QzkpMm%$5kS3_4pZ?` z3z%f(H&FM_kFQ;l*&Z!9Ev+om^GpmG6YT<;-=VIAHOQ%1Q{9yyRov;QMm8z}3C>uC zHDsq=H%78Z(*uCwgOduG1AKSoVX~={D}7u6HD*+ejv{(va!lY& zLN>qX$IVDPFVTt6p7=m5lDhRQ5BjigHY!?U(&-VxD*1b#L;8~xUdy8oK@t2zyv$~~ zcbM>B3)z3xTnEx61QJe4s#JUsQ$Ee()nzl~`uiLK?gKh+@gi0#Ddc$T}ZvhiF9hG}l5KO2b`gyPO;T<}`k0fo&t$J4FN?CSm zXg)r^?xzCM>S?rta)GbS(>e<4VGzbzE|F6weo zrFS9xp?1E!3~Q`+$xXdyeY7s^coG+l&5hqJ#R}=(9hbMaHGeszq~8i7J=Bn;IVOR| zFxc9^|LvpRy4fG)@OD2pJR#L{_lw@Khsh{=u5*1s7N$|G1OR9d7}ofmZ+-IAPBhG( zBM>}}cFYL>-vL*4%(y&(9w;RPMp^#_ru2GdUB1x#HRoW}9Ygx*1GWQy=0Uye?1|<} z*+{5%;;qE%Rk}y)=g*u!v&lv}IAW>rcoqg#(aen=N?J@Xh9MGMq=TF%++IU9U*Yg8 zg@8=)Y=p4tDLJ>VT+eMT>;HVbV@00PV}-)?Dr-$8AG4@%{9h@kaP$T=?R;BqFrO7) zf@)Qltlu$RR=g|07O>S}hU}#-QFNx0x?3Tw23=5~#oU*RTjd2exQ13ZipPZO$gTsm zo0u4#V@s?+T0xr7%VD0D%^jZGsNL9d0CQx?0)(z+b{8mipjSh2FFfLHt7!**T0_1z z?6#QB>Ih4NIByNrCP7o)<-9zJhJr59W9&ky*DWBIqx*kV^ zm*aC_PRY9UzR2z}{N~fJX#3wRR+sEH)Q)C-my2wnBIOShbc;^C|5w19H@4L! z7q6t@clSdn*H%{zW(G%|zY+2MYo3aDue|mr34-jUCBpsoJR0OA_)|cYXRHZ6!`Yz_ z%ug+S;NEg4Bex4=C7hSZ%Y2{oH6>2B7Hxg;)p(4d{Z>XWdI!gI_HW3IYj1{b+}4~Q zj_|KUZsv#){xZU$6QHk&SDyiCSEO3I_#R8m&AjJ;gJ1L@4GQ>sR{ezpu1BK^V!3L7 z(oi)m>VT2BOGfwpDuAlrYuvt*6w+s?V3V9Cm}!4SNq3>pAq!6Q92fRCb6?2aZfH}Z z7%snC@E8;tO4|E+scb$oW0sb3y;2;cs(7fS`Ul^^WMu-5@ Date: Mon, 1 Nov 2010 22:01:44 +0000 Subject: [PATCH 038/457] --- src/ch/eitchnet/privilege/policy/PrivilegePolicy.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 44c679e5b..3f4b64853 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -20,5 +20,11 @@ import ch.eitchnet.privilege.model.internal.Role; */ public interface PrivilegePolicy { + /** + * @param role + * @param privilege + * @param restrictable + * @return + */ public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable); } From 43f3bb33073256c55b49fe2548a658881b36917c Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 6 Nov 2010 22:26:43 +0000 Subject: [PATCH 039/457] --- .settings/org.eclipse.jdt.core.prefs | 16 ++++++++-------- docs/PrivilegeAuthentication.dia | Bin 0 -> 1544 bytes docs/PrivilegeHandlers.dia | Bin 2890 -> 2880 bytes 3 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 docs/PrivilegeAuthentication.dia diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index e83bae06a..c436f312a 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,4 @@ -#Sun Sep 19 16:32:10 CEST 2010 +#Sat Nov 06 15:16:05 CET 2010 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 @@ -31,20 +31,20 @@ org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=enabled -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=enabled -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning diff --git a/docs/PrivilegeAuthentication.dia b/docs/PrivilegeAuthentication.dia new file mode 100644 index 0000000000000000000000000000000000000000..0d4cb6b4ffc8510b0bb062d8966b8bd113c0cffa GIT binary patch literal 1544 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 Pct$m+xaN>7L{0S{J)fU0 delta 2870 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 IO;1nvFC!YIQ~&?~ From 5d871206752d4f87f585b7b8fe95a6c9c60ea8fb Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 11 Nov 2010 21:30:23 +0000 Subject: [PATCH 040/457] --- docs/PrivilegeAuthentication.dia | Bin 1544 -> 1819 bytes docs/PrivilegeModelUser.dia | Bin 1752 -> 2198 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/PrivilegeAuthentication.dia b/docs/PrivilegeAuthentication.dia index 0d4cb6b4ffc8510b0bb062d8966b8bd113c0cffa..fe9ed43c90f0f54a4a5da0775c29515cee275fd5 100644 GIT binary patch literal 1819 zcmV+$2juu4iwFP!000021MOT(Z`(K!zUNm6>ZJ>$v`FgNG%30T7JF#XJ#5bgZPPZl zvSdhfoLu&|FDWUG^^7f<-Z-Fu1duYC5r>>_X7n-o^!Z^G8uv6wSsdM(5Ll)`qiH;2 z(c;$p`S%|meDm|&)u$OFAH~liAuB`N5vB6%)?9MF{x}+Kw_6ZCri8}{2-yatbo4I? zLoyOVBlGUcF!mM92q*Gdag}hMu*rr~BO)t$Yfi}Y*CL5G(ag-H3b|<<#))xH!dvs* zJUh%$p=MOl)1iA!7IczO@~d67C5|BIqk2t~s@l~$PMHwlkL#L9iyrxVPo|Jcg;2D( zd-siUPLp@}V-**l^))?gt0BH3$$~|7Tf#qK?hFJR9LU3kzJo2~;U~WwZ*Fp4bK!Z- zCFeEA((yV@ctRMjo1DaPNJ*4i<;jNjx06muD3(~)uwZ}fbH;i6!t>8bm>x2K{PHg1 zj*Z?$!e-sUTaswevSl;A96u;>SFZh0k@kC*vPnpro}NYgWa=MJqrN&j8G=9GK5#YU z+FDb@%%=`@j!zMb4V%&Qg=JQjY0fg z*75U_!T@6D+6R8s2XzE#voM)d4J2X+BwB>DFoJ-W8Sy;lMp$&j33PykoPI`EloRuX z1wUg`-cS29j-oM*W|^0c^^W9B@nacWo1{MEa#dzo{Y=bR7#?7vTvHxrBH{76 zzDRbc3}8??a8E0fn73PTPGn`4knMOLPdBx-(NS0&ZD5+jX}5t&=09qQufuU9>Z9eB zrZtZ>rJG}wr}0Le^6beeQTXJiP%dFaz@UKZv=&rPe=(LMn#r6X@SMj2&!K5N|Mk{% z>xrqNG{2lDjqWV+BfHd)L*U>XVqJR*vqt~|=h_v&ZlDV=l6dDbIHwO>aXx(;DO)<~ zrC_PZv^B}uYnYN;<*m(M3A<+@UC=)>JZyV^)6w*_`6-TglVN$?n3EL?AI0;LC^e0g zKNd)w-})yF?6In%<8mdbT8`#K2x`)s`gzh19521Sq-9rrMAv!p2JhY2= zPE;yMZW~yI70Ghjz|YR!0L|U#zIHK9bZeqp6WyBV?k2jCI->hKO;fUH$|UyiOyYSN z#zCct#LI!y0_X-J;LF`Z5ZD)O31O=c6a0JCuCj$`$S_#28%0n4q@u8t#R~y;k{N?=1M_4Bm-T#1B3PJfS^! z!WRxt4UX%;Fs$LJ8J-X@63RD8WPGIwXY&lCE*7E!faEGy2~ic8@sEM13cNw<9GeB* zp%8_TT22q{NIm2N7kQV?>h<^6A+(+i9M7}&T18J&e465W^%UQi^*WTfML?6L160!i z$Og#wea-V}o=@|9n&;CzpXT|L@O<9j)?$~aRPuZX17XA~cs^m%l{Q7+fdlMc^h=ON z|Fw(BA&aqVwy&Lsb1+2Ct^o-g6>J}bWiv>VeVXihU1XoDj_m8mC;qASjX?Vh+eRP; z!QQ?R39j~q=aVM?wM)qnL!C~#UTHc0KF;^vSM<{Ek%}3;dqgLcSC&xl|FL@{IN9zI z2l)6r9IXukAL1L)vjnndfi6J3Ex)#%BvmgqbSh%lSPpxL!{0t@i-EDG$vjS0-zi_l zv-i_y^7e8!n25D+%b6G13@d~pf50XaPu(Vyj=C70>Z4&w>*C^@VMSS7d^8MMAh`6Z zxWRd?LUO#iChn+-F>v>4RSWuxO5)R4(Iv4iiO;l?=sYDc9Q^IDtVHt!9Qpvlz76eR z2?YaN;;sjfFqD_XxnT!h_ z`J<5Nsj7umYe}pUiqHw@Ll^tLj19%@Ig;D%9GuOC>-#s@0=DaAX^#9As|LVAKpo<=$L10=dc%NKCg^WGVaL>F!kvd4qRG=jYj{yzj0`-}=70`VU5K Jg_ILy003O`j|l(( literal 1544 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 From 7d231ebb3aefc9fb107e23a09eda8397b349aa7b Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 27 Nov 2010 21:00:34 +0000 Subject: [PATCH 041/457] [Minor] added some java docs --- .../handler/DefaultEncryptionHandler.java | 2 +- .../handler/DefaultPrivilegeHandler.java | 108 +++-- .../privilege/handler/PersistenceHandler.java | 16 +- .../privilege/handler/PrivilegeHandler.java | 380 +++++++++++++----- .../handler/XmlPersistenceHandler.java | 12 +- 5 files changed, 377 insertions(+), 141 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 4b33f648a..8fb5dfc28 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -24,7 +24,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; /** *

    - * This default {@link EncryptionHandler} creates nokens by using a {@link SecureRandom} object. Hashing is done by + * This default {@link EncryptionHandler} creates tokens using a {@link SecureRandom} object. Hashing is done by * using {@link MessageDigest} and the configured algorithm which is passed in the parameters *

    * diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 7e8b813a2..2365c0001 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -33,6 +33,21 @@ import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** + *

    + * This is default implementation of the {@link PrivilegeHandler} + *

    + * + * The following list describes implementation details: + *
      + *
    • any methods which change the model are first validated by checking if the certificate is for an admin user by + * calling {@link #validateIsPrivilegeAdmin(Certificate)}
    • + *
    • all model requests are delegated to the configured {@link PrivilegeHandler}, except for the session id to + * {@link Certificate} map, no model data is kept in this implementation. This also means that to return the + * representation objects, for every new model query, a new representation object is created
    • + *
    • when creating new users, or editing users then a null password is understood as no password set
    • + *
    • Password requirements are simple: Non null and non empty/length 0
    • + *
    + * * @author rvonburg * */ @@ -106,8 +121,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // create a new privilege - Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep - .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); + Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), + privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); // delegate to persistence handler this.persistenceHandler.addOrReplacePrivilege(privilege); @@ -151,8 +166,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep - .getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale()); + User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), + userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale()); // delegate to persistence handler this.persistenceHandler.addOrReplaceUser(user); @@ -229,8 +244,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet(currentRoles); newRoles.add(roleName); - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user - .getSurname(), user.getUserState(), newRoles, user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), + user.getSurname(), user.getUserState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -334,8 +349,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user - .getSurname(), user.getUserState(), newRoles, user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), + user.getSurname(), user.getUserState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -387,8 +402,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, privilege - .getDenyList(), privilege.getAllowList()); + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, + privilege.getDenyList(), privilege.getAllowList()); // delegate privilege replacement to persistence handler this.persistenceHandler.addOrReplacePrivilege(newPrivilege); @@ -458,20 +473,26 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } + // validate that the policy exists + PrivilegePolicy policy = this.persistenceHandler.getPolicy(policyName); + if (policy == null) { + throw new PrivilegeException("No privilege policy exists for the name " + policyName); + } + // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), privilege - .getDenyList(), privilege.getAllowList()); + Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), + privilege.getDenyList(), privilege.getAllowList()); // delegate privilege replacement to persistence handler this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocale(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Locale) */ @Override - public void setUserLocaleState(Certificate certificate, String username, Locale locale) { + public void setUserLocale(Certificate certificate, String username, Locale locale) { // validate who is doing this validateIsPrivilegeAdmin(certificate); @@ -483,8 +504,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user - .getSurname(), user.getUserState(), user.getRoles(), locale); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), + user.getSurname(), user.getUserState(), user.getRoles(), locale); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -507,8 +528,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, user - .getUserState(), user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, + user.getUserState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -541,8 +562,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), user - .getSurname(), user.getUserState(), user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), + user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -565,8 +586,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user - .getSurname(), state, user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), + user.getSurname(), state, user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -623,8 +644,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); // create and save a new session - Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System - .currentTimeMillis()); + Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), + System.currentTimeMillis()); this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); // log @@ -635,8 +656,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? - * + * Checks if the action is allowed by iterating the roles of the certificates user and then delegating to + * {@link #actionAllowed(Role, Restrictable)} * * @throws AccessDeniedException * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not @@ -648,6 +669,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + // TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? + // first validate certificate if (!isCertificateValid(certificate)) { logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " @@ -690,18 +713,43 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, + * Checks if the {@link RoleRep} has access to the {@link Restrictable} by delegating to + * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} + * + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.RoleRep, * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(Role role, Restrictable restrictable) { + public boolean actionAllowed(RoleRep roleRep, Restrictable restrictable) { // user and restrictable must not be null - if (role == null) + if (roleRep == null) throw new PrivilegeException("Role may not be null!"); else if (restrictable == null) throw new PrivilegeException("Restrictable may not be null!"); + // get role for the roleRep + Role role = this.persistenceHandler.getRole(roleRep.getName()); + + // validate that the role exists + if (role == null) { + throw new PrivilegeException("No Role exists with the name " + roleRep.getName()); + } + + return actionAllowed(role, restrictable); + } + + /** + * Checks if the {@link Role} has access to the {@link Restrictable} by delegating to + * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} + * + * @param role + * @param restrictable + * + * @return true if the privilege is granted, false otherwise + */ + protected boolean actionAllowed(Role role, Restrictable restrictable) { + // validate PrivilegeName for this restrictable String privilegeName = restrictable.getPrivilegeName(); if (privilegeName == null || privilegeName.length() < 3) { @@ -800,6 +848,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** + * This simple implementation validates that the password is not null, and that the password string is not empty + * * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(java.lang.String) */ @Override diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 2e34f1703..7a6db6b33 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -12,6 +12,7 @@ package ch.eitchnet.privilege.handler; import java.util.Map; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -67,17 +68,20 @@ public interface PersistenceHandler { /** *

    - * Thus this method instantiates a {@link PrivilegePolicy} object from the given policyName. The - * {@link PrivilegePolicy} is not stored in a database, but rather behind a privilege name a class name is stored - * which then is used to instantiate a new object + * This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy} + * is not stored in a database. The privilege name is a class name and is then used to instantiate a new + * {@link PrivilegePolicy} object *

    * * @param policyName - * the name/id of the {@link PrivilegePolicy} object to return + * the class name of the {@link PrivilegePolicy} object to return * - * @return the {@link PrivilegePolicy} object, or null if no class is defined for the given policy name + * @return the {@link PrivilegePolicy} object + * + * @throws PrivilegeException + * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated */ - public PrivilegePolicy getPolicy(String policyName); + public PrivilegePolicy getPolicy(String policyName) throws PrivilegeException; /** * Removes a {@link User} with the given name and returns the removed object if it existed diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 749d425ff..294cde3f0 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -28,6 +29,10 @@ import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** + * The {@link PrivilegeHandler} is the centrally exposed API for accessing the privilege library. It exposes all needed + * methods to access Privilege data model objects, modify them and validate if users or roles have privileges to perform + * an action + * * @author rvonburg * */ @@ -87,8 +92,14 @@ public interface PrivilegeHandler { * the username of the user to remove * * @return the {@link UserRep} of the user removed, or null if the user did not exist + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public UserRep removeUser(Certificate certificate, String username); + public UserRep removeUser(Certificate certificate, String username) throws AccessDeniedException, + PrivilegeException; /** * Removes the role with the given roleName from the user with the given username @@ -98,9 +109,15 @@ public interface PrivilegeHandler { * @param username * the username of the user from which the role is to be removed * @param roleName - * the rolename of the role to remove from the user + * the roleName of the role to remove from the user + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public void removeRoleFromUser(Certificate certificate, String username, String roleName); + public void removeRoleFromUser(Certificate certificate, String username, String roleName) + throws AccessDeniedException, PrivilegeException; /** * Removes the role with the given roleName @@ -111,8 +128,14 @@ public interface PrivilegeHandler { * the roleName of the role to remove * * @return the {@link RoleRep} of the role removed, or null if the role did not exist + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public RoleRep removeRole(Certificate certificate, String roleName); + public RoleRep removeRole(Certificate certificate, String roleName) throws AccessDeniedException, + PrivilegeException; /** * Removes the privilege with the given privilegeName from the role with the given roleName @@ -123,8 +146,14 @@ public interface PrivilegeHandler { * the roleName of the role from which the privilege is to be removed * @param privilegeName * the privilegeName of the privilege to remove from the role + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) + throws AccessDeniedException, PrivilegeException; /** * Removes the privilege with the given privilegeName @@ -135,8 +164,14 @@ public interface PrivilegeHandler { * the privilegeName of the privilege to remove * * @return the {@link PrivilegeRep} of the privilege removed, or null if the privilege did not exist + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) throws AccessDeniedException, + PrivilegeException; /** *

    @@ -156,8 +191,14 @@ public interface PrivilegeHandler { * the password of the new user. If the password is null, then this is accepted but the user can not * login, otherwise the password must be validated against * {@link PrivilegeHandler#validatePassword(String)} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) + throws AccessDeniedException, PrivilegeException; /** * Adds a new role, or replaces the role with the information from this {@link RoleRep} if the role already exists @@ -166,8 +207,14 @@ public interface PrivilegeHandler { * the {@link Certificate} of the user which has the privilege to perform this action * @param roleRep * the {@link RoleRep} containing the information to create the new {@link Role} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, + PrivilegeException; /** * Adds a new privilege, or replaces the privilege with the information from this {@link PrivilegeRep} if the @@ -177,8 +224,14 @@ public interface PrivilegeHandler { * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeRep * the {@link PrivilegeRep} containing the information to create the new {@link Privilege} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) throws AccessDeniedException, + PrivilegeException; /** * Adds the role with the given roleName to the {@link User} with the given username @@ -189,8 +242,14 @@ public interface PrivilegeHandler { * the username of the {@link User} to which the role should be added * @param roleName * the roleName of the {@link Role} which should be added to the {@link User} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public void addRoleToUser(Certificate certificate, String username, String roleName); + public void addRoleToUser(Certificate certificate, String username, String roleName) throws AccessDeniedException, + PrivilegeException; /** * Adds the privilege with the given privilegeName to the {@link Role} with the given roleName @@ -201,8 +260,14 @@ public interface PrivilegeHandler { * the roleName of the {@link Role} to which the privilege should be added * @param privilegeName * the privilegeName of the {@link Privilege} which should be added to the {@link Role} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate */ - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) + throws AccessDeniedException, PrivilegeException; /** * Changes the password for the {@link User} with the given username. If the password is null, then the {@link User} @@ -217,115 +282,213 @@ public interface PrivilegeHandler { * the new password for this user. If the password is null, then the {@link User} can not login anymore. * Otherwise the password must meet the requirements of the implementation under * {@link PrivilegeHandler#validatePassword(String)} - */ - public void setUserPassword(Certificate certificate, String username, String password); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * @param firstname - * @param surname - */ - public void setUserName(Certificate certificate, String username, String firstname, String surname); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * @param state - */ - public void setUserState(Certificate certificate, String username, UserState state); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * @param locale - */ - public void setUserLocaleState(Certificate certificate, String username, Locale locale); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * @param policyName - */ - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * @param allAllowed - */ - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * @param denyList - */ - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * @param allowList - */ - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); - - /** - * @param username - * @param password * - * @return + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void setUserPassword(Certificate certificate, String username, String password) + throws AccessDeniedException, PrivilegeException; + + /** + * Changes the name of the user. This changes the first name and the surname. If either value is null, then that + * value is not changed + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the {@link User} for which the name is to be changed + * @param firstname + * the new first name + * @param surname + * the new surname + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void setUserName(Certificate certificate, String username, String firstname, String surname) + throws AccessDeniedException, PrivilegeException; + + /** + * Changes the {@link UserState} of the user + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the {@link User} for which the {@link UserState} is to be changed + * @param state + * the new state for the user + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void setUserState(Certificate certificate, String username, UserState state) throws AccessDeniedException, + PrivilegeException; + + /** + * Changes the {@link Locale} of the user + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the {@link User} for which the {@link Locale} is to be changed + * @param locale + * the new {@link Locale} for the user + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, + PrivilegeException; + + /** + * Sets the class name of the {@link PrivilegePolicy} object for the {@link Privilege} registered for the given + * privilegeName + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the name of the {@link Privilege} for which the policy is to be changed + * @param policyName + * class name of the {@link PrivilegePolicy} to be registered for this {@link Privilege} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the class name of the policy is invalid + */ + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) + throws AccessDeniedException, PrivilegeException; + + /** + * Sets the special flag on the given {@link Privilege} defined by the privilegeName argument so that all is allowed + * and thus the allow and deny list of the privilege are ignored + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the name of the {@link Privilege} for which the allAllowed value is to be changed + * @param allAllowed + * the boolean defining if all privileges are granted for the value true, or not for the value false + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) + throws AccessDeniedException, PrivilegeException; + + /** + * Sets the {@link List} of strings which represent values which denies finer grained privilege rights for the + * {@link Privilege} defined by the privilegeName argument + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the name of the {@link Privilege} for which the deny list is to be changed + * @param denyList + * the {@link List} of strings which represent values which denies finer grained privilege rights + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) + throws AccessDeniedException, PrivilegeException; + + /** + * Sets the {@link List} of strings which represent values which grants finer grained privilege rights for the + * {@link Privilege} defined by the privilegeName argument + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the name of the {@link Privilege} for which the allow list is to be changed + * @param allowList + * the {@link List} of strings which represent values which grants finer grained privilege rights + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) + throws AccessDeniedException, PrivilegeException; + + /** + * Authenticates a user by validating that a {@link User} for the given username and password exist and then returns + * a {@link Certificate} with which this user may then perform actions + * + * @param username + * the username of the {@link User} which is registered in the {@link PersistenceHandler} + * @param password + * the password with which this user is to be authenticated. Null passwords are not accepted and they + * must meet the requirements of the {@link #validatePassword(String)}-method + * + * @return a {@link Certificate} with which this user may then perform actions * * @throws AccessDeniedException * if the user credentials are not valid */ - public Certificate authenticate(String username, String password); + public Certificate authenticate(String username, String password) throws AccessDeniedException; /** + * Checks if the {@link User} registered to the given {@link Certificate} is allowed to access the + * {@link Restrictable} + * * @param certificate * the {@link Certificate} of the user which has the privilege to perform this action * @param restrictable + * the {@link Restrictable} to which the user wants access * - * @return + * @return true if the access is allowed, false otherwise * * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation + * if the user for this certificate may not perform the action defined by the {@link Restrictable} + * implementation * @throws PrivilegeException * if there is anything wrong with this certificate */ - public boolean actionAllowed(Certificate certificate, Restrictable restrictable); + public boolean actionAllowed(Certificate certificate, Restrictable restrictable) throws AccessDeniedException, + PrivilegeException; /** - * @param role + * Checks if the {@link RoleRep} is allowed to access the {@link Restrictable} + * + * @param roleRep + * the {@link RoleRep} for which access to the {@link Restrictable} is to be checked * @param restrictable - * @return + * the {@link Restrictable} to which access is to be checked + * + * @return true if the {@link RoleRep} has access, false otherwise * * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation - * @throws PrivilegeException - * if there is anything wrong with this certificate + * if the role may not perform the action defined by the {@link Restrictable} implementation */ - public boolean actionAllowed(Role role, Restrictable restrictable); + public boolean actionAllowed(RoleRep roleRep, Restrictable restrictable) throws AccessDeniedException; /** - * @param certificate - * @return + * Checks if the given {@link Certificate} is valid. This means that the certificate is for a valid session and that + * the user exists for the certificate. This method checks if the {@link Certificate} has been tampered with + * + * @param certificate + * the {@link Certificate} to check + * + * @return true if the {@link Certificate} is valid, false otherwise * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} * @throws PrivilegeException * if there is anything wrong with this certificate */ - public boolean isCertificateValid(Certificate certificate); + public boolean isCertificateValid(Certificate certificate) throws PrivilegeException; /** *

    @@ -344,39 +507,52 @@ public interface PrivilegeHandler { * @param certificate * the {@link Certificate} for which the role should be validated against * - * @throws ch.eitchnet.privilege.i18n.PrivilegeException + * @throws AccessDeniedException * if the user does not not have admin privileges */ - public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException; + public void validateIsPrivilegeAdmin(Certificate certificate) throws AccessDeniedException; /** - * Validate that the given password meets any requirements. What these requirements are is a decision made by the - * concrete implementation + * Validate that the given password meets certain requirements. What these requirements are is a decision made by + * the concrete implementation * * @param password + * the password to be validated to meet certain requirements * * @throws PrivilegeException + * if the password does not implement the requirement of the concrete implementation */ public void validatePassword(String password) throws PrivilegeException; /** + * Persists any changes to the privilege data model. Changes are thus not persisted immediately, but must be + * actively performed + * * @param certificate * the {@link Certificate} of the user which has the privilege to perform this action * - * @return + * @return true if changes were persisted, false if no changes were persisted + * + * @throws AccessDeniedException + * if the users of the given certificate does not have the privilege to perform this action */ - public boolean persist(Certificate certificate); + public boolean persist(Certificate certificate) throws AccessDeniedException; /** - * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this map - * might need + * Initializes the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this + * {@link PrivilegeHandler} might need. This method may only be called once and this must be enforced by the + * concrete implementation * * @param parameterMap * a map containing configuration properties - * * @param encryptionHandler + * the {@link EncryptionHandler} instance for this {@link PrivilegeHandler} * @param persistenceHandler + * the {@link PersistenceHandler} instance for this {@link PrivilegeHandler} + * + * @throws PrivilegeException + * if the this method is called multiple times or an initialization exception occurs */ public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler); + PersistenceHandler persistenceHandler) throws PrivilegeException; } diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index d1fa6a43c..720715f57 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -152,7 +152,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // instantiate the policy - PrivilegePolicy policy = ClassHelper.instantiateClass(policyClazz); + PrivilegePolicy policy; + try { + policy = ClassHelper.instantiateClass(policyClazz); + } catch (Exception e) { + throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" + + policyName, e); + } return policy; } @@ -431,8 +437,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // create user - User user = new User(userId, username, password, firstname, surname, userState, Collections - .unmodifiableSet(roles), locale); + User user = new User(userId, username, password, firstname, surname, userState, + Collections.unmodifiableSet(roles), locale); // put user in map this.userMap.put(username, user); From 66128804cd64801101e063a97b81808c4a44d9d9 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 27 Nov 2010 21:54:46 +0000 Subject: [PATCH 042/457] [Minor] added some java docs --- .../handler/DefaultEncryptionHandler.java | 4 +- .../handler/XmlPersistenceHandler.java | 6 +- .../helper/BootstrapConfigurationHelper.java | 6 +- .../privilege/helper/ClassHelper.java | 47 +++++++- ...{EncryptionHelper.java => HashHelper.java} | 21 +++- .../helper/InitializationHelper.java | 6 +- .../privilege/helper/PasswordCreator.java | 3 +- .../privilege/helper/XmlConstants.java | 111 ++++++++++++++++++ .../eitchnet/privilege/helper/XmlHelper.java | 29 ++++- .../privilege/model/Restrictable.java | 12 +- .../eitchnet/privilege/model/UserState.java | 24 ++++ .../privilege/model/internal/Role.java | 16 ++- .../privilege/model/internal/User.java | 8 +- .../privilege/policy/PrivilegePolicy.java | 3 +- 14 files changed, 266 insertions(+), 30 deletions(-) rename src/ch/eitchnet/privilege/helper/{EncryptionHelper.java => HashHelper.java} (58%) diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 8fb5dfc28..976f62463 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -18,7 +18,7 @@ import java.util.Map; import org.apache.log4j.Logger; -import ch.eitchnet.privilege.helper.EncryptionHelper; +import ch.eitchnet.privilege.helper.HashHelper; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -60,7 +60,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String convertToHash(String string) { try { - return EncryptionHelper.encryptString(this.hashAlgorithm, string); + return HashHelper.stringToHash(this.hashAlgorithm, string); } catch (NoSuchAlgorithmException e) { throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 720715f57..dab8bd719 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -192,7 +192,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // write DOM to file - XmlHelper.writeDocument(rootElement, usersFile); + XmlHelper.writeElement(rootElement, usersFile); this.userMapDirty = true; } @@ -219,7 +219,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // write DOM to file - XmlHelper.writeDocument(rootElement, rolesFile); + XmlHelper.writeElement(rootElement, rolesFile); this.roleMapDirty = true; } @@ -247,7 +247,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // write DOM to file - XmlHelper.writeDocument(rootElement, privilegesFile); + XmlHelper.writeElement(rootElement, privilegesFile); this.privilegeMapDirty = true; } diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 66ddad1d7..4966659e9 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -21,15 +21,17 @@ import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; +import ch.eitchnet.privilege.handler.PrivilegeHandler; + /** *

    * This class is a simple application which can be used to bootstrap a new configuration for the - * {@link PrivilegeContainer} + * {@link PrivilegeHandler} *

    * *

    * Simple execute the application and it will ask a few questions and then write a new set of configuration files which - * can be used to run the {@link PrivilegeContainer} + * can be used to run the {@link PrivilegeHandler} *

    * * @author rvonburg diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java index 34c9a1139..1ecdae89f 100644 --- a/src/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -13,13 +13,28 @@ package ch.eitchnet.privilege.helper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** + * The {@link ClassHelper} class is a helper to instantiate classes using reflection + * * @author rvonburg * */ public class ClassHelper { + /** + * Returns an instance of the class' name given by instantiating the class through an empty arguments constructor + * + * @param + * the type of the class to return + * @param className + * the name of a class to instantiate through an empty arguments constructor + * + * @return the newly instantiated object from the given class name + * + * @throws PrivilegeException + * if the class could not be instantiated + */ @SuppressWarnings("unchecked") - public static T instantiateClass(String className) { + public static T instantiateClass(String className) throws PrivilegeException { try { Class clazz = (Class) Class.forName(className); @@ -31,7 +46,20 @@ public class ClassHelper { } } - public static T instantiateClass(Class clazz) { + /** + * Instantiates an object for the given {@link Class} using an empty arguments constructor + * + * @param + * the type of the class to return + * @param clazz + * the {@link Class} from which a new object is to be instantiated using an empty arguments constructor + * + * @return the newly instantiated object from the given {@link Class} + * + * @throws PrivilegeException + * if the {@link Class} could not be instantiated + */ + public static T instantiateClass(Class clazz) throws PrivilegeException { try { return clazz.getConstructor().newInstance(); @@ -41,8 +69,21 @@ public class ClassHelper { } } + /** + * Loads the {@link Class} object for the given class name + * + * @param + * the type of {@link Class} to return + * @param className + * the name of the {@link Class} to load and return + * + * @return the {@link Class} object for the given class name + * + * @throws PrivilegeException + * if the class could not be instantiated + */ @SuppressWarnings("unchecked") - public static Class loadClass(String className) { + public static Class loadClass(String className) throws PrivilegeException { try { Class clazz = (Class) Class.forName(className); diff --git a/src/ch/eitchnet/privilege/helper/EncryptionHelper.java b/src/ch/eitchnet/privilege/helper/HashHelper.java similarity index 58% rename from src/ch/eitchnet/privilege/helper/EncryptionHelper.java rename to src/ch/eitchnet/privilege/helper/HashHelper.java index afc52c1f2..fa095ffca 100644 --- a/src/ch/eitchnet/privilege/helper/EncryptionHelper.java +++ b/src/ch/eitchnet/privilege/helper/HashHelper.java @@ -18,16 +18,31 @@ import java.security.NoSuchAlgorithmException; * @author rvonburg * */ -public class EncryptionHelper { +public class HashHelper { /** - * Hex char table for fast calculating of hex value + * 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' }; - public static String encryptString(String hashAlgorithm, String string) throws NoSuchAlgorithmException, + /** + * Creates the hash of the given string using {@link MessageDigest} and the defined hash algorithm + * + * @param hashAlgorithm + * the algorithm to use for hashing + * @param string + * the string to hash + * + * @return a new string encrypted by the defined algorithm + * + * @throws NoSuchAlgorithmException + * if the algorithm is not found + * @throws UnsupportedEncodingException + * if something is wrong with the given string to hash + */ + public static String stringToHash(String hashAlgorithm, String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index 549dfcd00..46a723212 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -33,7 +33,8 @@ public class InitializationHelper { /** * @param privilegeContainerXmlFile - * @return + * + * @return the {@link PrivilegeHandler} instance loaded from the configuration file */ public static PrivilegeHandler initializeFromXml(File privilegeContainerXmlFile) { @@ -110,7 +111,8 @@ public class InitializationHelper { /** * @param element - * @return + * + * @return the {@link Map} of the parameter name/value combinations from the given {@link Element} */ @SuppressWarnings("unchecked") public static Map convertToParameterMap(Element element) { diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java index 701e58a94..adf243d26 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -22,6 +22,7 @@ public class PasswordCreator { /** * @param args + * @throws Exception */ public static void main(String[] args) throws Exception { @@ -48,7 +49,7 @@ public class PasswordCreator { System.out.print("Password: "); String password = r.readLine().trim(); - System.out.print("Hash is: " + EncryptionHelper.encryptString(hashAlgorithm, password)); + System.out.print("Hash is: " + HashHelper.stringToHash(hashAlgorithm, password)); } } diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index b78ce40fa..06f838c9f 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -15,45 +15,156 @@ package ch.eitchnet.privilege.helper; * */ public class XmlConstants { + /** + * XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer" : + */ public static final String XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer"; + /** + * XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles" : + */ public static final String XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles"; + /** + * XML_ROOT_PRIVILEGES = "Privileges" : + */ public static final String XML_ROOT_PRIVILEGES = "Privileges"; + /** + * XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers" : + */ public static final String XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers"; + /** + * XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies" : + */ public static final String XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies"; + /** + * XML_HANDLER_PERSISTENCE = "PersistenceHandler" : + */ public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; + /** + * XML_HANDLER_ENCRYPTION = "EncryptionHandler" : + */ public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; + /** + * XML_HANDLER_PRIVILEGE = "PrivilegeHandler" : + */ public static final String XML_HANDLER_PRIVILEGE = "PrivilegeHandler"; + /** + * XML_ROLES = "Roles" : + */ public static final String XML_ROLES = "Roles"; + /** + * XML_ROLE = "Role" : + */ public static final String XML_ROLE = "Role"; + /** + * XML_USERS = "Users" : + */ public static final String XML_USERS = "Users"; + /** + * XML_USER = "User" + */ public static final String XML_USER = "User"; + /** + * XML_PRIVILEGES = "Privileges" : + */ public static final String XML_PRIVILEGES = "Privileges"; + /** + * XML_PRIVILEGE = "Privilege" : + */ public static final String XML_PRIVILEGE = "Privilege"; + /** + * XML_POLICY = "Policy" : + */ public static final String XML_POLICY = "Policy"; + /** + * XML_PARAMETERS = "Parameters" : + */ public static final String XML_PARAMETERS = "Parameters"; + /** + * XML_PARAMETER = "Parameter" : + */ public static final String XML_PARAMETER = "Parameter"; + /** + * XML_ALL_ALLOWED = "AllAllowed" : + */ public static final String XML_ALL_ALLOWED = "AllAllowed"; + /** + * XML_DENY = "Deny" : + */ public static final String XML_DENY = "Deny"; + /** + * XML_ALLOW = "Allow" : + */ public static final String XML_ALLOW = "Allow"; + /** + * XML_FIRSTNAME = "Firstname" : + */ public static final String XML_FIRSTNAME = "Firstname"; + /** + * XML_SURNAME = "Surname" : + */ public static final String XML_SURNAME = "Surname"; + /** + * XML_STATE = "State" : + */ public static final String XML_STATE = "State"; + /** + * XML_LOCALE = "Locale" : + */ public static final String XML_LOCALE = "Locale"; + /** + * XML_ATTR_CLASS = "class" : + */ public static final String XML_ATTR_CLASS = "class"; + /** + * XML_ATTR_NAME = "name" : + */ public static final String XML_ATTR_NAME = "name"; + /** + * XML_ATTR_VALUE = "value" : + */ public static final String XML_ATTR_VALUE = "value"; + /** + * XML_ATTR_POLICY = "policy" : + */ public static final String XML_ATTR_POLICY = "policy"; + /** + * XML_ATTR_USER_ID = "userId" : + */ public static final String XML_ATTR_USER_ID = "userId"; + /** + * XML_ATTR_USERNAME = "username" : + */ public static final String XML_ATTR_USERNAME = "username"; + /** + * XML_ATTR_PASSWORD = "password" : + */ public static final String XML_ATTR_PASSWORD = "password"; + /** + * XML_PARAM_HASH_ALGORITHM = "hashAlgorithm" : + */ public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; + /** + * XML_PARAM_POLICY_FILE = "policyXmlFile" : + */ public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; + /** + * XML_PARAM_ROLES_FILE = "rolesXmlFile" : + */ public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; + /** + * XML_PARAM_USERS_FILE = "usersXmlFile" : + */ public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; + /** + * XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile" : + */ public static final String XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile"; + /** + * XML_PARAM_BASE_PATH = "basePath" : + */ public static final String XML_PARAM_BASE_PATH = "basePath"; } diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index f4f57a87a..53febb9ab 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -35,10 +35,21 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; */ public class XmlHelper { + /** + * DEFAULT_ENCODING = "UTF-8" : defines the default UTF-8 encoding expected of XML files + */ public static final String DEFAULT_ENCODING = "UTF-8"; private static final Logger logger = Logger.getLogger(XmlHelper.class); + /** + * Parses an XML file on the file system using dom4j and returns the resulting {@link Document} object + * + * @param xmlFile + * the {@link File} which has the path to the XML file to read + * + * @return a {@link Document} object containing the dom4j {@link Element}s of the XML file + */ public static Document parseDocument(File xmlFile) { try { @@ -58,6 +69,14 @@ public class XmlHelper { } } + /** + * Writes a dom4j {@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 + */ public static void writeDocument(Document document, File file) { logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); @@ -94,7 +113,15 @@ public class XmlHelper { } } - public static void writeDocument(Element rootElement, File file) { + /** + * Writes a dom4j {@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 + */ + public static void writeElement(Element rootElement, File file) { Document document = DocumentFactory.getInstance().createDocument(DEFAULT_ENCODING); document.setRootElement(rootElement); diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index d144b1781..e027fa846 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -10,19 +10,27 @@ package ch.eitchnet.privilege.model; +import ch.eitchnet.privilege.model.internal.Privilege; + /** + * TODO javadoc + * * @author rvonburg * */ public interface Restrictable { /** - * @return + * Returns the name of the {@link Privilege} which is to be used to validate privileges against + * + * @return the name of the {@link Privilege} which is to be used to validate privileges against */ public String getPrivilegeName(); /** - * @return + * Returns the value which defines or describes what privilege is to be granted + * + * @return the value which defines or describes what privilege is to be granted */ public Object getPrivilegeValue(); } diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java index 391975b1f..ab1fbde5e 100644 --- a/src/ch/eitchnet/privilege/model/UserState.java +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -10,13 +10,37 @@ package ch.eitchnet.privilege.model; +import ch.eitchnet.privilege.model.internal.User; + /** + * The {@link UserState} enum defines the different states a {@link User} can have: + *
      + *
    • NEW - the user is new, and cannot login
    • + *
    • ENABLED - the user has been enabled, meaning a password has been set and the user has at least one role assigned + * and may thus login
    • + *
    • DISABLED - the user been disabled by an administrator
    • + *
    • EXPIRED - the user has automatically expired through a predefined time
    • + *
    + * * @author rvonburg * */ public enum UserState { + /** + * the user is new, and cannot login + */ NEW, + /** + * the user has been enabled, meaning a password has been set and the user has at least one role assigned and may + * thus login + */ ENABLED, + /** + * the user been disabled by an administrator + */ DISABLED, + /** + * the user has automatically expired through a predefined time + */ EXPIRED; } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 664216cb5..d41e73c17 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -43,18 +43,24 @@ public final class Role { } /** - * @return + * Returns the {@link Set} of {@link Privilege} names which is granted to this {@link Role} + * + * @return the {@link Set} of {@link Privilege} names which is granted to this */ public Set getPrivileges() { return this.privileges; } /** - * @param key - * @return + * Determines if this {@link Role} has the {@link Privilege} with the given name + * + * @param name + * the name of the {@link Privilege} + * + * @return true if this {@link Role} has the {@link Privilege} with the given name */ - public boolean hasPrivilege(String key) { - return this.privileges.contains(key); + public boolean hasPrivilege(String name) { + return this.privileges.contains(name); } /** diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 30f51901c..176481429 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -69,7 +69,7 @@ public final class User { * @return the userId */ public String getUserId() { - return userId; + return this.userId; } /** @@ -80,11 +80,9 @@ public final class User { } /** + * Returns the hashed password for this {@link User} * - * @param privilegeHandler - * @param certificate - * - * @return + * @return the hashed password for this {@link User} */ public String getPassword() { diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 3f4b64853..461ea6951 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -24,7 +24,8 @@ public interface PrivilegePolicy { * @param role * @param privilege * @param restrictable - * @return + * + * @return return true if the action is allowed, false if not */ public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable); } From d6ff3c86cc603827350f58062ba52c015670a9c7 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 27 Nov 2010 21:55:05 +0000 Subject: [PATCH 043/457] [Minor] added some java docs --- .../privilege/test/PrivilegeTest.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 2416508a9..ff800f009 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -41,7 +41,8 @@ public class PrivilegeTest { private static PrivilegeHandler privilegeHandler; /** - * @throws java.lang.Exception + * @throws Exception + * if something goes wrong */ @BeforeClass public static void init() throws Exception { @@ -63,6 +64,10 @@ public class PrivilegeTest { } } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAuthenticationOk() throws Exception { @@ -70,6 +75,10 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } + /** + * @throws Exception + * if something goes wrong + */ @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { @@ -77,6 +86,10 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } + /** + * @throws Exception + * if something goes wrong + */ @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { @@ -84,6 +97,10 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAddUserBobWithPW() throws Exception { @@ -103,6 +120,7 @@ public class PrivilegeTest { * Will fail because user bob is not yet enabled * * @throws Exception + * if something goes wrong */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { @@ -110,6 +128,10 @@ public class PrivilegeTest { privilegeHandler.authenticate("bob", "12345678901"); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testEnableUserBob() throws Exception { @@ -121,6 +143,7 @@ public class PrivilegeTest { * Will fail as user bob has no role * * @throws Exception + * if something goes wrong */ @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { @@ -129,6 +152,10 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAddUserRoleToBob() throws Exception { @@ -136,6 +163,10 @@ public class PrivilegeTest { privilegeHandler.addRoleToUser(certificate, "bob", "user"); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAuthAsBob() throws Exception { @@ -146,6 +177,7 @@ public class PrivilegeTest { * Will fail because user bob does not have admin rights * * @throws Exception + * if something goes wrong */ @Test(expected = AccessDeniedException.class) public void testFailAddUserTedAsBob() throws Exception { @@ -159,6 +191,10 @@ public class PrivilegeTest { logger.info("Added user bob"); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAddAdminRoleToBob() throws Exception { @@ -166,6 +202,10 @@ public class PrivilegeTest { privilegeHandler.addRoleToUser(certificate, "bob", PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAddUserTedAsBob() throws Exception { @@ -178,6 +218,10 @@ public class PrivilegeTest { logger.info("Added user bob"); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testPerformRestrictable() throws Exception { From 47927ac99b6152cd505282607a406e0e996d7ee2 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 25 Jul 2011 22:00:41 +0000 Subject: [PATCH 044/457] [Major] updated a lot of JavaDoc and made sure that every class has at least a header JavaDoc with a description. Added a TODO under the doc folder with some TODOs =) --- MANIFEST.MF | 2 + docs/TODO | 14 +++++++ privilege.jardesc | 18 +++++++++ .../helper/BootstrapConfigurationHelper.java | 9 ++++- .../privilege/helper/ClassHelper.java | 1 - .../eitchnet/privilege/helper/HashHelper.java | 3 +- .../helper/InitializationHelper.java | 12 +++++- .../privilege/helper/PasswordCreator.java | 13 ++++++- .../privilege/helper/XmlConstants.java | 3 +- .../eitchnet/privilege/helper/XmlHelper.java | 3 +- .../privilege/i18n/AccessDeniedException.java | 10 +++-- .../privilege/i18n/PrivilegeException.java | 9 ++++- .../eitchnet/privilege/model/Certificate.java | 25 ++++++++++--- .../privilege/model/PrivilegeRep.java | 18 ++++++++- .../privilege/model/Restrictable.java | 8 +++- src/ch/eitchnet/privilege/model/RoleRep.java | 11 +++++- src/ch/eitchnet/privilege/model/UserRep.java | 17 ++++++++- .../privilege/model/internal/Privilege.java | 23 +++++++++++- .../privilege/model/internal/Role.java | 14 ++++++- .../privilege/model/internal/Session.java | 37 ++++++++++++++++--- .../privilege/model/internal/User.java | 20 ++++++++++ .../privilege/policy/DefaultPrivilege.java | 3 +- .../privilege/policy/PrivilegePolicy.java | 17 ++++++++- 23 files changed, 258 insertions(+), 32 deletions(-) create mode 100644 MANIFEST.MF create mode 100644 docs/TODO create mode 100644 privilege.jardesc diff --git a/MANIFEST.MF b/MANIFEST.MF new file mode 100644 index 000000000..58630c02e --- /dev/null +++ b/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/docs/TODO b/docs/TODO new file mode 100644 index 000000000..296271aac --- /dev/null +++ b/docs/TODO @@ -0,0 +1,14 @@ +A list of TODOs for Privilege +============================================ + +- Write up a proper explanation on the idea on how Privilege, PrivilegePolicy, + Restrictable and Roles fit together to grant privileges to Users + +- Re-think the PrivilegePolicy and its + actionAllowed(Role, Privilege, Restrictable)-method. Maybe the Privilege + argument is not needed, as this should be on the Role anyhow... + +- Finish the JavaDoc + +- Set up a website =) + diff --git a/privilege.jardesc b/privilege.jardesc new file mode 100644 index 000000000..d58ef8d02 --- /dev/null +++ b/privilege.jardesc @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 4966659e9..114d9840b 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -34,11 +34,15 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; * can be used to run the {@link PrivilegeHandler} *

    * - * @author rvonburg + *

    + * Note:This class is not yet implemented! + *

    * + * @author rvonburg */ public class BootstrapConfigurationHelper { -// private static final Logger logger = Logger.getLogger(BootstrapConfigurationHelper.class); + + // private static final Logger logger = Logger.getLogger(BootstrapConfigurationHelper.class); private static String path; @@ -58,6 +62,7 @@ public class BootstrapConfigurationHelper { /** * @param args + * the args from the command line */ public static void main(String[] args) { BasicConfigurator.resetConfiguration(); diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java index 1ecdae89f..eda6f8eea 100644 --- a/src/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -16,7 +16,6 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * The {@link ClassHelper} class is a helper to instantiate classes using reflection * * @author rvonburg - * */ public class ClassHelper { diff --git a/src/ch/eitchnet/privilege/helper/HashHelper.java b/src/ch/eitchnet/privilege/helper/HashHelper.java index fa095ffca..cbf6ae8f3 100644 --- a/src/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/ch/eitchnet/privilege/helper/HashHelper.java @@ -15,8 +15,9 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** - * @author rvonburg + * Helper class to hash a String for a certain hash algorithm, using the Java {@link MessageDigest} classes * + * @author rvonburg */ public class HashHelper { diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index 46a723212..e0c2aadb6 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -24,15 +24,20 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; /** - * @author rvonburg + * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the + * configuration * + * @author rvonburg */ public class InitializationHelper { private static final Logger logger = Logger.getLogger(InitializationHelper.class); /** + * Initializes the {@link PrivilegeHandler} from the configuration file + * * @param privilegeContainerXmlFile + * a {@link File} reference to the XML file containing the configuration for Privilege * * @return the {@link PrivilegeHandler} instance loaded from the configuration file */ @@ -110,7 +115,12 @@ public class InitializationHelper { } /** + * Converts an {@link XmlConstants#XML_PARAMETERS} element containing {@link XmlConstants#XML_PARAMETER} elements to + * a {@link Map} of String key/value pairs + * * @param element + * the XML {@link Element} with name {@link XmlConstants#XML_PARAMETERS} containing + * {@link XmlConstants#XML_PARAMETER} elements * * @return the {@link Map} of the parameter name/value combinations from the given {@link Element} */ diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java index adf243d26..a70dda885 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -15,14 +15,23 @@ import java.io.InputStreamReader; import java.security.MessageDigest; /** - * @author rvonburg + *

    + * Simple main class which can be used to create a hash from a password which the user must type in at the command line + *

    * + *

    + * TODO: Note: currently the password input is echoed which is a security risk. This is a TODO + *

    + * + * @author rvonburg */ public class PasswordCreator { /** * @param args - * @throws Exception + * the args from the command line, NOT USED + * @throws Exception + * thrown if anything goes wrong */ public static void main(String[] args) throws Exception { diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 06f838c9f..465e1cf53 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -11,8 +11,9 @@ package ch.eitchnet.privilege.helper; /** - * @author rvonburg + * The constants used in parsing XML documents which contain the configuration for Privilege * + * @author rvonburg */ public class XmlConstants { /** diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index 53febb9ab..c77dcec08 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -30,8 +30,9 @@ import org.dom4j.io.XMLWriter; import ch.eitchnet.privilege.i18n.PrivilegeException; /** - * @author rvonburg + * Helper class for performing XML based tasks using Dom4J * + * @author rvonburg */ public class XmlHelper { diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java index c84978e3f..339bd6fed 100644 --- a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -11,17 +11,19 @@ package ch.eitchnet.privilege.i18n; /** - * @author rvonburg + * Exception thrown if access is denied during login, or if a certain privilege is not granted * + * @author rvonburg */ public class AccessDeniedException extends PrivilegeException { private static final long serialVersionUID = 1L; /** - * @param string + * @param msg + * detail on why and where access was denied */ - public AccessDeniedException(String string) { - super(string); + public AccessDeniedException(String msg) { + super(msg); } } diff --git a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java index 4960a42f9..decd696a9 100644 --- a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -11,24 +11,31 @@ package ch.eitchnet.privilege.i18n; /** - * @author rvonburg + * Main {@link RuntimeException} thrown if something goes wrong in Privilege * + * @author rvonburg */ public class PrivilegeException extends RuntimeException { private static final long serialVersionUID = 1L; /** + * Default constructor + * * @param string + * message to go with the exception */ public PrivilegeException(String string) { super(string); } /** + * Constructor with underlying exception * * @param string + * message to go with the exception * @param t + * throwable to wrap with this exception which is the underlying exception of this exception */ public PrivilegeException(String string, Throwable t) { super(string, t); diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index e55af8a44..7dfe3b6a9 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -13,11 +13,16 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Locale; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.internal.Session; /** - * @author rvonburg + * The {@link Certificate} is the object a client keeps when accessing a Privilege enabled system. This object is the + * instance which is always used when performing an access and is returned when a user performs a login through + * {@link PrivilegeHandler#authenticate(String, String)} * + * @author rvonburg */ public final class Certificate implements Serializable { @@ -31,11 +36,22 @@ public final class Certificate implements Serializable { private Locale locale; /** + * Default constructor initializing with all information needed for this certificate + * + *

    Note, both the authentication token and password are private fields which are generated on login and only known by the {@link PrivilegeHandler}

    + * * @param sessionId + * the users session id * @param username + * the users login name * @param authToken + * the authentication token defining the users unique session and is a private field of this certificate. + * It corresponds with the authentication token on the {@link Session} * @param authPassword + * the password to access the authentication token, this is not known to the client but set by the + * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the {@link Session} * @param locale + * the users {@link Locale} */ public Certificate(String sessionId, String username, String authToken, String authPassword, Locale locale) { @@ -86,12 +102,12 @@ public final class Certificate implements Serializable { } /** - * Returns the authToken if the given authPassword is corret, null otherwise + * Returns the authToken if the given authPassword is correct, null otherwise * * @param authPassword - * the auth password with which this certificate was created + * the authentication password with which this certificate was created * - * @return the authToken if the given authPassword is corret, null otherwise + * @return the authToken if the given authPassword is correct, null otherwise */ public String getAuthToken(String authPassword) { if (this.authPassword.equals(authPassword)) @@ -170,5 +186,4 @@ public final class Certificate implements Serializable { builder.append("]"); return builder.toString(); } - } diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index 82ad3e070..5b92e4613 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -13,9 +13,17 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Set; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + /** - * @author rvonburg + * To keep certain details of the {@link Privilege} itself hidden from remote clients and make sure instances are only + * edited by users with the correct privilege, this representational version is allowed to be viewed by remote clients + * and simply wraps all public data from the {@link Privilege} * + * @author rvonburg */ public class PrivilegeRep implements Serializable { @@ -28,11 +36,19 @@ public class PrivilegeRep implements Serializable { private Set allowList; /** + * Default constructor + * * @param name + * the name of this privilege, which is unique to all privileges known in the {@link PrivilegeHandler} * @param policy + * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted * @param allAllowed + * a boolean defining if a {@link Role} with this {@link Privilege} has unrestricted access to a + * {@link Restrictable} * @param denyList + * a list of deny rules for this {@link Privilege} * @param allowList + * a list of allow rules for this {@link Privilege} */ public PrivilegeRep(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = name; diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index e027fa846..9122d52c5 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -11,9 +11,15 @@ package ch.eitchnet.privilege.model; import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** - * TODO javadoc + *

    + * Objects implementing this interface are used to grant/restrict privileges to them. A {@link PrivilegePolicy} + * implements the logic on granting/restricting privileges for a {@link Restrictable} and the + * {@link #getPrivilegeName()} is used to find the {@link Privilege} which has the associated {@link PrivilegePolicy} + * for evaluating access + *

    * * @author rvonburg * diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index 8a7bb538f..019f1a408 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -13,9 +13,14 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Set; +import ch.eitchnet.privilege.model.internal.Role; + /** - * @author rvonburg + * To keep certain details of the {@link Role} itself hidden from remote clients and make sure instances are only edited + * by users with the correct privilege, this representational version is allowed to be viewed by remote clients and + * simply wraps all public data from the {@link Role} * + * @author rvonburg */ public class RoleRep implements Serializable { @@ -25,8 +30,12 @@ public class RoleRep implements Serializable { private Set privileges; /** + * Default constructor + * * @param name + * the name of this role * @param privileges + * the set of privileges granted to this role */ public RoleRep(String name, Set privileges) { this.name = name; diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index a91ab8e2c..7efbb8ab9 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -14,9 +14,15 @@ import java.io.Serializable; import java.util.Locale; import java.util.Set; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + /** - * @author rvonburg + * To keep certain details of the {@link User} itself hidden from remote clients and make sure instances are only edited + * by users with the correct privilege, this representational version is allowed to be viewed by remote clients and + * simply wraps all public data from the {@link User} * + * @author rvonburg */ public class UserRep implements Serializable { @@ -31,13 +37,22 @@ public class UserRep implements Serializable { private Locale locale; /** + * Default constructor + * * @param userId + * the user's id * @param username + * the user's login name * @param firstname + * the user's first name * @param surname + * the user's surname * @param userState + * the user's {@link UserState} * @param roles + * the set of {@link Role}s assigned to this user * @param locale + * the user's {@link Locale} */ public UserRep(String userId, String username, String firstname, String surname, UserState userState, Set roles, Locale locale) { diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 037a4f492..7d6ca9b1c 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -14,11 +14,25 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** - * @author rvonburg + *

    + * {@link Privilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it which + * defines the privileges a logged in user with that role has. For every privilege a {@link PrivilegePolicy} is + * configured which is used to evaluate if privilege is granted to a {@link Restrictable} + *

    * + *

    + * {@link Privilege}s have allow and deny rules which the configured {@link PrivilegeHandler} uses to + *

    + * + *

    Note: This is an internal object which is not to be serialized or passed to clients, {@link PrivilegeRep}s are used for that

    + * + * @author rvonburg */ public final class Privilege { @@ -29,12 +43,19 @@ public final class Privilege { private final Set allowList; /** + * Default constructor * * @param name + * the name of this privilege, which is unique to all privileges known in the {@link PrivilegeHandler} * @param policy + * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted * @param allAllowed + * a boolean defining if a {@link Role} with this {@link Privilege} has unrestricted access to a + * {@link Restrictable} * @param denyList + * a list of deny rules for this {@link Privilege} * @param allowList + * a list of allow rules for this {@link Privilege} */ public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = name; diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index d41e73c17..a4788ea43 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -17,8 +17,17 @@ import java.util.Set; import ch.eitchnet.privilege.model.RoleRep; /** - * @author rvonburg + *

    + * A {@link User} is assigned a set of roles. These roles have a set of privileges assigned to them by name and they + * define the privileges granted to a user with this role + *

    * + *

    + * Note: This is an internal object which is not to be serialized or passed to clients, {@link RoleRep}s are used for + * that + *

    + * + * @author rvonburg */ public final class Role { @@ -26,9 +35,12 @@ public final class Role { private final Set privileges; /** + * Default constructor * * @param name + * the name of the role * @param privileges + * a set of names of privileges granted to this role */ public Role(String name, Set privileges) { this.name = name; diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index f5003d0ca..14e1129be 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -10,9 +10,22 @@ package ch.eitchnet.privilege.model.internal; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.Certificate; + /** - * @author rvonburg + *

    + * A {@link Session} is linked to currently logged in user. The {@link PrivilegeHandler} creates an instance of this + * class once a {@link User} has successfully logged in and keeps this object private but hands out a + * {@link Certificate} which the user must use every time a privilege needs to be granted + *

    * + *

    + * Note: This is an internal object which is not to be serialized or passed to clients, the client must keep his + * {@link Certificate} which is used for accessing privileges + *

    + * + * @author rvonburg */ public final class Session { @@ -24,18 +37,32 @@ public final class Session { private final String authPassword; /** + * Default constructor + * + *

    + * Note, both the authentication token and password are private fields which are generated on login and only known + * by the {@link PrivilegeHandler} + *

    * * @param sessionId - * @param authToken - * @param authPassword + * the users session id * @param username + * the users login name + * @param authToken + * the authentication token defining the users unique session and is a private field of this session. It + * corresponds with the authentication token on the {@link Certificate} + * @param authPassword + * the password to access the authentication token, this is not known to the client but set by the + * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the + * {@link Certificate} * @param loginTime + * the time the user logged in */ - public Session(String sessionId, String authToken, String authPassword, String username, long loginTime) { + public Session(String sessionId, String username, String authToken, String authPassword, long loginTime) { this.sessionId = sessionId; + this.username = username; this.authToken = authToken; this.authPassword = authPassword; - this.username = username; this.loginTime = loginTime; } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 176481429..2118763e3 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -19,6 +19,14 @@ import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; /** + * This class defines the actual login information for a given user which can be granted privileges. Every user is + * granted a set of {@link Role}s and has a {@link UserState} including detail information like first name and surname + * + *

    + * Note: This is an internal object which is not to be serialized or passed to clients, {@link UserRep}s are used for + * that + *

    + * * @author rvonburg * */ @@ -39,14 +47,24 @@ public final class User { private final Locale locale; /** + * Default constructor + * * @param userId + * the user's id * @param username + * the user's login name * @param password + * the user's password (hashed) * @param firstname + * the user's first name * @param surname + * the user's surname * @param userState + * the user's {@link UserState} * @param roles + * the set of {@link Role}s assigned to this user * @param locale + * the user's {@link Locale} */ public User(String userId, String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale) { @@ -125,6 +143,8 @@ public final class User { * Returns true if this user has the specified role * * @param role + * the name of the {@link Role} to check for + * * @return true if the this user has the specified role */ public boolean hasRole(String role) { diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 097214169..1eecb1e26 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -16,8 +16,9 @@ import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; /** - * @author rvonburg + * XXX re-think this implementation... * + * @author rvonburg */ public class DefaultPrivilege implements PrivilegePolicy { diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 461ea6951..865d373cd 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -13,17 +13,32 @@ package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; /** - * @author rvonburg + *

    + * {@link PrivilegePolicy} implements logic to determine if a {@link User} which has the given {@link Role} and the + * given {@link Privilege} has access to the given {@link Restrictable} + *

    * + *

    + * Re-think this interface and especially the {@link #actionAllowed(Role, Privilege, Restrictable)}-method... maybe we + * need one with out the {@link Privilege} in its signature? + *

    + * + * @author rvonburg */ public interface PrivilegePolicy { /** + * Checks if the given {@link Role} and the given {@link Privilege} has access to the given {@link Restrictable} + * * @param role + * the {@link Role} trying to access the {@link Restrictable} * @param privilege + * the {@link Privilege} to check with * @param restrictable + * the {@link Restrictable} to which the user wants access * * @return return true if the action is allowed, false if not */ From 4a66c0d2a4ddd1d2415a8ddb44271caef1c319f9 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 25 Jul 2011 22:35:47 +0000 Subject: [PATCH 045/457] [New] added an ant build script --- MANIFEST.MF | 7 ++++++- build_package.xml | 34 ++++++++++++++++++++++++++++++++++ docs/TODO | 2 ++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 build_package.xml diff --git a/MANIFEST.MF b/MANIFEST.MF index 58630c02e..c30795c80 100644 --- a/MANIFEST.MF +++ b/MANIFEST.MF @@ -1,2 +1,7 @@ Manifest-Version: 1.0 - +Implementation-Vendor: eitchnet.ch +Implementation-Title: eitchnet Java Privilege implementation +Implementation-Version: 0.0.1 +Specification-Vendor: eitchnet.ch +Specification-Title: eitchnet Java Privilege implementation +Specification-Version: 1.6 diff --git a/build_package.xml b/build_package.xml new file mode 100644 index 000000000..8a576f51d --- /dev/null +++ b/build_package.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/TODO b/docs/TODO index 296271aac..319d89b1f 100644 --- a/docs/TODO +++ b/docs/TODO @@ -8,6 +8,8 @@ A list of TODOs for Privilege actionAllowed(Role, Privilege, Restrictable)-method. Maybe the Privilege argument is not needed, as this should be on the Role anyhow... +- i18n for any messages and exceptions! + - Finish the JavaDoc - Set up a website =) From a5d52733e9b7fa4ab5d06979ccd16512d4646d41 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 25 Jul 2011 22:39:19 +0000 Subject: [PATCH 046/457] --- build_package.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/build_package.xml b/build_package.xml index 8a576f51d..d57e5b6d3 100644 --- a/build_package.xml +++ b/build_package.xml @@ -7,7 +7,8 @@ - + + @@ -16,6 +17,14 @@ + + + + + + From bf3ff008b63c866fbc5c60ab233ba0f9088ab1ce Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 25 Jul 2011 22:42:09 +0000 Subject: [PATCH 047/457] [New] added an ant build script --- build_package.xml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/build_package.xml b/build_package.xml index d57e5b6d3..aaa219219 100644 --- a/build_package.xml +++ b/build_package.xml @@ -9,7 +9,7 @@ - + @@ -17,14 +17,9 @@ - - - - + - + @@ -40,4 +35,14 @@ + + + + + + + + + + \ No newline at end of file From 42383f940066225aeefcee16ebc71481af9127a0 Mon Sep 17 00:00:00 2001 From: eitch Date: Wed, 27 Jul 2011 20:15:47 +0000 Subject: [PATCH 049/457] [New] added a new PrivilegeHandler.invalidate(Certificate)-method with which users can log out of Privilege --- .../handler/DefaultPrivilegeHandler.java | 22 ++- .../privilege/handler/PrivilegeHandler.java | 11 ++ .../handler/XmlPersistenceHandler.java | 166 +++++++++--------- 3 files changed, 119 insertions(+), 80 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2365c0001..1b88b8221 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; @@ -655,6 +656,25 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return certificate; } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#invalidateSession(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public boolean invalidateSession(Certificate certificate) { + + // first validate certificate + if (!isCertificateValid(certificate)) { + logger.info("Certificate is not valid, so no session to invalidate: " + certificate.toString()); + return false; + } + + // remove registration + CertificateSessionPair certificateSessionPair = this.sessionMap.remove(certificate.getSessionId()); + + // return true if object was really removed + return certificateSessionPair != null; + } + /** * Checks if the action is allowed by iterating the roles of the certificates user and then delegating to * {@link #actionAllowed(Role, Restrictable)} @@ -884,7 +904,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler = persistenceHandler; lastSessionId = 0l; - this.sessionMap = new HashMap(); + this.sessionMap = Collections.synchronizedMap(new HashMap()); } /** diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 294cde3f0..84de5495e 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -25,6 +25,7 @@ import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -441,6 +442,16 @@ public interface PrivilegeHandler { */ public Certificate authenticate(String username, String password) throws AccessDeniedException; + /** + * Invalidates the {@link Session} for the given {@link Certificate}, effectively logging out the user who was + * authenticated with the credentials associated to the given {@link Certificate} + * + * @param certificate + * the {@link Certificate} for which the {@link Session} is to be invalidated + * @return true if the {@link Session} was still valid and is now invalidated, false otherwise + */ + public boolean invalidateSession(Certificate certificate); + /** * Checks if the {@link User} registered to the given {@link Certificate} is allowed to access the * {@link Restrictable} diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index dab8bd719..da7e6c8ec 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -35,8 +35,10 @@ import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** - * @author rvonburg + * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed + * in {@link #initialize(Map)} * + * @author rvonburg */ public class XmlPersistenceHandler implements PersistenceHandler { @@ -274,10 +276,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void initialize(Map parameterMap) { - this.roleMap = new HashMap(); - this.userMap = new HashMap(); - this.privilegeMap = new HashMap(); - this.policyMap = new HashMap>(); + this.roleMap = Collections.synchronizedMap(new HashMap()); + this.userMap = Collections.synchronizedMap(new HashMap()); + this.privilegeMap = Collections.synchronizedMap(new HashMap()); + this.policyMap = Collections.synchronizedMap(new HashMap>()); // get and validate base bath this.basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); @@ -536,37 +538,39 @@ public class XmlPersistenceHandler implements PersistenceHandler { List privilegesAsElements = new ArrayList(this.privilegeMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String privilegeName : this.privilegeMap.keySet()) { + synchronized (this.privilegeMap) { + for (String privilegeName : this.privilegeMap.keySet()) { - // get the privilege object - Privilege privilege = this.privilegeMap.get(privilegeName); + // get the privilege object + Privilege privilege = this.privilegeMap.get(privilegeName); - // create the privilege element - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + // create the privilege element + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); - // add the all allowed element - Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.add(allAllowedElement); + // add the all allowed element + Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.add(allAllowedElement); - // add all the deny values - for (String denyValue : privilege.getDenyList()) { - Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); - denyValueElement.setText(denyValue); - privilegeElement.add(denyValueElement); + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); + denyValueElement.setText(denyValue); + privilegeElement.add(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setText(allowValue); + privilegeElement.add(allowValueElement); + } + + // add element to return list + privilegesAsElements.add(privilegeElement); } - - // add all the allow values - for (String allowValue : privilege.getAllowList()) { - Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); - allowValueElement.setText(allowValue); - privilegeElement.add(allowValueElement); - } - - // add element to return list - privilegesAsElements.add(privilegeElement); } return privilegesAsElements; @@ -577,24 +581,26 @@ public class XmlPersistenceHandler implements PersistenceHandler { List rolesAsElements = new ArrayList(this.roleMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String roleName : this.roleMap.keySet()) { + synchronized (this.roleMap) { + for (String roleName : this.roleMap.keySet()) { - // get the role object - Role role = this.roleMap.get(roleName); + // get the role object + Role role = this.roleMap.get(roleName); - // create the role element - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + // create the role element + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - // add all the privileges - for (String privilegeName : role.getPrivileges()) { - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilegeName); - roleElement.add(privilegeElement); + // add all the privileges + for (String privilegeName : role.getPrivileges()) { + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilegeName); + roleElement.add(privilegeElement); + } + + // add element to return list + rolesAsElements.add(roleElement); } - - // add element to return list - rolesAsElements.add(roleElement); } return rolesAsElements; @@ -605,48 +611,50 @@ public class XmlPersistenceHandler implements PersistenceHandler { List usersAsElements = new ArrayList(this.userMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String userName : this.userMap.keySet()) { + synchronized (this.userMap) { + for (String userName : this.userMap.keySet()) { - // get the user object - User user = this.userMap.get(userName); + // get the user object + User user = this.userMap.get(userName); - // create the user element - Element userElement = documentFactory.createElement(XmlConstants.XML_USER); - userElement.addAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); - userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + // create the user element + Element userElement = documentFactory.createElement(XmlConstants.XML_USER); + userElement.addAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); + userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); + userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); - // add first name element - Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); - firstnameElement.setText(user.getFirstname()); - userElement.add(firstnameElement); + // add first name element + Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setText(user.getFirstname()); + userElement.add(firstnameElement); - // add surname element - Element surnameElement = documentFactory.createElement(XmlConstants.XML_SURNAME); - surnameElement.setText(user.getSurname()); - userElement.add(surnameElement); + // add surname element + Element surnameElement = documentFactory.createElement(XmlConstants.XML_SURNAME); + surnameElement.setText(user.getSurname()); + userElement.add(surnameElement); - // add state element - Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); - stateElement.setText(user.getUserState().toString()); - userElement.add(stateElement); + // add state element + Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); + stateElement.setText(user.getUserState().toString()); + userElement.add(stateElement); - // add locale element - Element localeElement = documentFactory.createElement(XmlConstants.XML_LOCALE); - localeElement.setText(user.getLocale().toString()); - userElement.add(localeElement); + // add locale element + Element localeElement = documentFactory.createElement(XmlConstants.XML_LOCALE); + localeElement.setText(user.getLocale().toString()); + userElement.add(localeElement); - // add all the role elements - Element rolesElement = documentFactory.createElement(XmlConstants.XML_ROLES); - userElement.add(rolesElement); - for (String roleName : user.getRoles()) { - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.setText(roleName); - rolesElement.add(roleElement); + // add all the role elements + Element rolesElement = documentFactory.createElement(XmlConstants.XML_ROLES); + userElement.add(rolesElement); + for (String roleName : user.getRoles()) { + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.setText(roleName); + rolesElement.add(roleElement); + } + + // add element to return list + usersAsElements.add(userElement); } - - // add element to return list - usersAsElements.add(userElement); } return usersAsElements; From 2758aa61d1bf5b27bbe66ad0f4822fcc91972fbd Mon Sep 17 00:00:00 2001 From: eitch Date: Wed, 27 Jul 2011 20:16:05 +0000 Subject: [PATCH 050/457] --- build_package.xml => build.xml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename build_package.xml => build.xml (100%) diff --git a/build_package.xml b/build.xml similarity index 100% rename from build_package.xml rename to build.xml From e7b8de2765bff7dcd9c2ce7a83ddc60fdc7b344f Mon Sep 17 00:00:00 2001 From: eitch Date: Wed, 27 Jul 2011 20:21:17 +0000 Subject: [PATCH 051/457] --- build.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.xml b/build.xml index aaa219219..8063f0c42 100644 --- a/build.xml +++ b/build.xml @@ -31,7 +31,10 @@ - + + + + From f26013583de12f0bcf289f90d8df2b4054a35e11 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 28 Jul 2011 20:50:35 +0000 Subject: [PATCH 052/457] [Interface] modified privilege to have only two configuration files: Privilege.xml for the configuration for the handlers and Policies. and PrivilegeModel.xml for the configuration of the run time modifiable data (roles, users, privileges) --- config/Privilege.xml | 29 ++ config/PrivilegeContainer.xml | 22 -- config/PrivilegeModel.xml | 49 ++++ config/PrivilegePolicies.xml | 6 - config/PrivilegeRoles.xml | 13 - config/PrivilegeUsers.xml | 16 -- config/Privileges.xml | 16 -- .../handler/DefaultPrivilegeHandler.java | 3 +- .../privilege/handler/PersistenceHandler.java | 4 +- .../handler/XmlPersistenceHandler.java | 250 +++++------------- .../helper/BootstrapConfigurationHelper.java | 47 ++-- .../helper/InitializationHelper.java | 66 ++++- .../privilege/helper/XmlConstants.java | 66 +++-- .../privilege/model/internal/Privilege.java | 20 +- .../privilege/model/internal/Role.java | 9 + .../privilege/model/internal/Session.java | 15 ++ .../privilege/model/internal/User.java | 24 ++ .../privilege/test/PrivilegeTest.java | 2 +- 18 files changed, 328 insertions(+), 329 deletions(-) create mode 100644 config/Privilege.xml delete mode 100644 config/PrivilegeContainer.xml create mode 100644 config/PrivilegeModel.xml delete mode 100644 config/PrivilegePolicies.xml delete mode 100644 config/PrivilegeRoles.xml delete mode 100644 config/PrivilegeUsers.xml delete mode 100644 config/Privileges.xml diff --git a/config/Privilege.xml b/config/Privilege.xml new file mode 100644 index 000000000..0913623cb --- /dev/null +++ b/config/Privilege.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml deleted file mode 100644 index 20b2be359..000000000 --- a/config/PrivilegeContainer.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml new file mode 100644 index 000000000..429e3a2f8 --- /dev/null +++ b/config/PrivilegeModel.xml @@ -0,0 +1,49 @@ + + + + + + true + + + + + + false + + ch.eitchnet.privilege.test.TestRestrictable + + + + + + + Robert + von Burg + ENABLED + en_GB + + PrivilegeAdmin + admin + serviceExecutor + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/PrivilegePolicies.xml b/config/PrivilegePolicies.xml deleted file mode 100644 index 2b2c6f486..000000000 --- a/config/PrivilegePolicies.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml deleted file mode 100644 index 89724934b..000000000 --- a/config/PrivilegeRoles.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml deleted file mode 100644 index f03340020..000000000 --- a/config/PrivilegeUsers.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Robert - von Burg - ENABLED - en_GB - - PrivilegeAdmin - admin - serviceExecutor - - - - \ No newline at end of file diff --git a/config/Privileges.xml b/config/Privileges.xml deleted file mode 100644 index baec28f6c..000000000 --- a/config/Privileges.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - true - - - - - - false - - ch.eitchnet.privilege.test.TestRestrictable - - - \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 1b88b8221..a6e7de1b0 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -645,8 +645,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); // create and save a new session - Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), - System.currentTimeMillis()); + Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); // log diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 7a6db6b33..13531f834 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -151,6 +151,8 @@ public interface PersistenceHandler { * * @param parameterMap * a map containing configuration properties + * @param policyMap + * map of policy key/policy class pairs */ - public void initialize(Map parameterMap); + public void initialize(Map parameterMap, Map> policyMap); } diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index da7e6c8ec..86c9dd6ae 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -36,7 +36,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed - * in {@link #initialize(Map)} + * in {@link #initialize(Map, Map)} * * @author rvonburg */ @@ -49,11 +49,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { private Map privilegeMap; private Map> policyMap; - private long usersFileDate; + private long modelsFileDate; private boolean userMapDirty; - private long rolesFileDate; private boolean roleMapDirty; - private long privilegesFileDate; private boolean privilegeMapDirty; private Map parameterMap; @@ -171,87 +169,51 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public boolean persist() { - // USERS - // get users file name - String usersFileName = this.parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { + // get models file name + String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + if (modelFileName == null || modelFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); } - // get users file - File usersFile = new File(this.basePath + "/" + usersFileName); - boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == this.usersFileDate; - if (!this.userMapDirty && usersFileUnchanged) { - logger.warn("No users unpersisted and user file unchanged on file system"); - } else { - logger.info("Persisting users..."); - - // build XML DOM of users - List users = toDomUsers(); - Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_USERS); - for (Element userElement : users) { - rootElement.add(userElement); - } - - // write DOM to file - XmlHelper.writeElement(rootElement, usersFile); - this.userMapDirty = true; + // get model file + File modelFile = new File(this.basePath + "/" + modelFileName); + boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; + if (!(modelFileUnchanged && this.privilegeMapDirty && this.roleMapDirty && this.userMapDirty)) { + logger.warn("Not persisting as current file is unchanged and model data is not dirty"); + return false; } + DocumentFactory docFactory = DocumentFactory.getInstance(); + + // create root element + Element rootElement = docFactory.createElement(XmlConstants.XML_ROOT_PRIVILEGE_USERS_AND_ROLES); + + // USERS + // build XML DOM of users + List users = toDomUsers(); + Element usersElement = docFactory.createElement(XmlConstants.XML_USERS); + for (Element userElement : users) { + usersElement.add(userElement); + } + rootElement.add(usersElement); + // ROLES - // get roles file name - String rolesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); - } - // get roles file - File rolesFile = new File(this.basePath + "/" + rolesFileName); - boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == this.rolesFileDate; - if (!this.roleMapDirty && rolesFileUnchanged) { - logger.warn("No roles unpersisted and roles file unchanged on file system"); - } else { - logger.info("Persisting roles..."); - - // build XML DOM of roles - List roles = toDomRoles(); - Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_ROLES); - for (Element roleElement : roles) { - rootElement.add(roleElement); - } - - // write DOM to file - XmlHelper.writeElement(rootElement, rolesFile); - this.roleMapDirty = true; + // build XML DOM of roles + List roles = toDomRoles(); + Element rolesElement = docFactory.createElement(XmlConstants.XML_ROLES); + for (Element roleElement : roles) { + rolesElement.add(roleElement); } + rootElement.add(rolesElement); // PRIVILEGES - // get privileges file name - String privilegesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); - if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); - } - // get privileges file - File privilegesFile = new File(this.basePath + "/" + privilegesFileName); - boolean privilegesFileUnchanged = privilegesFile.exists() - && privilegesFile.lastModified() == this.privilegesFileDate; - if (!this.privilegeMapDirty && privilegesFileUnchanged) { - logger.warn("No privileges unpersisted and privileges file unchanged on file system"); - } else { - logger.info("Persisting privileges..."); - - // build XML DOM of privileges - List privileges = toDomPrivileges(); - Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_PRIVILEGES); - for (Element privilegeElement : privileges) { - rootElement.add(privilegeElement); - } - - // write DOM to file - XmlHelper.writeElement(rootElement, privilegesFile); - this.privilegeMapDirty = true; + // build XML DOM of privileges + List privileges = toDomPrivileges(); + Element privilegesElement = docFactory.createElement(XmlConstants.XML_PRIVILEGES); + for (Element privilegeElement : privileges) { + privilegesElement.add(privilegeElement); } + rootElement.add(privilegesElement); // reset dirty states and return if something was dirty, false otherwise if (this.userMapDirty || this.roleMapDirty || this.privilegeMapDirty) { @@ -274,12 +236,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) */ @Override - public void initialize(Map parameterMap) { + public void initialize(Map parameterMap, Map> policyMap) { this.roleMap = Collections.synchronizedMap(new HashMap()); this.userMap = Collections.synchronizedMap(new HashMap()); this.privilegeMap = Collections.synchronizedMap(new HashMap()); - this.policyMap = Collections.synchronizedMap(new HashMap>()); + this.policyMap = policyMap; // get and validate base bath this.basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); @@ -289,96 +251,42 @@ public class XmlPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); } + // get model file name + String modelFileName = parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + if (modelFileName == null || modelFileName.isEmpty()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); + } + + // validate file exists + File modelsFile = new File(this.basePath + "/" + modelFileName); + if (!modelsFile.exists()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " + + modelsFile.getAbsolutePath()); + } + + // parse models xml file to XML document + Element modelsRootElement = XmlHelper.parseDocument(modelsFile).getRootElement(); + this.modelsFileDate = modelsFile.lastModified(); + // ROLES - // get roles file name - String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); - } - - // get roles file - File rolesFile = new File(this.basePath + "/" + rolesFileName); - if (!rolesFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " - + rolesFile.getAbsolutePath()); - } - - // parse roles xml file to XML document - Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); - + // get roles element + Element rolesElement = modelsRootElement.element(XmlConstants.XML_ROLES); // read roles - readRoles(rolesRootElement); - this.rolesFileDate = rolesFile.lastModified(); + readRoles(rolesElement); // USERS - // get users file name - String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); - } - - // get users file - File usersFile = new File(this.basePath + "/" + usersFileName); - if (!usersFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " - + usersFile.getAbsolutePath()); - } - - // parse users xml file to XML document - Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); - + // get users element + Element usersElement = modelsRootElement.element(XmlConstants.XML_USERS); // read users - readUsers(usersRootElement); - this.usersFileDate = usersFile.lastModified(); + readUsers(usersElement); // PRIVILEGES - // get privileges file name - String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); - if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); - } - - // get privileges file - File privilegesFile = new File(this.basePath + "/" + privilegesFileName); - if (!privilegesFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " - + privilegesFile.getAbsolutePath()); - } - - // parse privileges xml file to XML document - Element privilegesRootElement = XmlHelper.parseDocument(privilegesFile).getRootElement(); - + // get privileges element + Element privilegesElement = modelsRootElement.element(XmlConstants.XML_PRIVILEGES); // read privileges - readPrivileges(privilegesRootElement); - this.privilegesFileDate = privilegesFile.lastModified(); - - // POLICIES - // get policy file name - String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); - if (policyFileName == null || policyFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid"); - } - - // get policy file - File policyFile = new File(this.basePath + "/" + policyFileName); - if (!policyFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " - + policyFile.getAbsolutePath()); - } - - // parse policy xml file to XML document - Element policiesRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); - - // read policies - readPolicies(policiesRootElement); + readPrivileges(privilegesElement); this.userMapDirty = false; this.roleMapDirty = false; @@ -397,8 +305,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { break; } } + if (!privilegeAdminExists) { - logger.warn("No User with PrivilegeAdmin role exists. Privilege modifications will not be possible!"); + logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + + "' exists. Privilege modifications will not be possible!"); } } @@ -441,6 +351,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // create user User user = new User(userId, username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), locale); + logger.info("Added user " + user); // put user in map this.userMap.put(username, user); @@ -516,23 +427,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } - /** - * @param policiesRootElement - */ - private void readPolicies(Element policiesRootElement) { - - @SuppressWarnings("unchecked") - List policyElements = policiesRootElement.elements(XmlConstants.XML_POLICY); - for (Element policyElement : policyElements) { - String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - - Class clazz = ClassHelper.loadClass(policyClass); - - this.policyMap.put(policyName, clazz); - } - } - private List toDomPrivileges() { List privilegesAsElements = new ArrayList(this.privilegeMap.size()); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 114d9840b..b25485d79 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -46,17 +46,13 @@ public class BootstrapConfigurationHelper { private static String path; - private static String defaultPrivilegeContainerXmlFile = "PrivilegeContainer.xml"; + private static String defaultPrivilegeContainerXmlFile = "Privilege.xml"; - private static String usersFileName = "PrivilegeUsers.xml"; - private static String rolesFileName = "PrivilegeRoles.xml"; - private static String privilegesFileName = "Privileges.xml"; + private static String basePath = ""; + private static String modelFileName = "PrivilegeUsers.xml"; private static String hashAlgorithm = "SHA-256"; - private static String policyXmlFile = "PrivilegePolicies.xml"; - - private static String defaultPrivilegeHandler = "ch.eitchnet.privilege.handler.DefaultPrivilegeHandler"; private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; @@ -116,48 +112,35 @@ public class BootstrapConfigurationHelper { // create document root DocumentFactory factory = DocumentFactory.getInstance(); Document doc = factory.createDocument(XmlHelper.DEFAULT_ENCODING); - doc.setName(XmlConstants.XML_ROOT_PRIVILEGE_CONTAINER); - Element rootElement = factory.createElement(XmlConstants.XML_ROOT_PRIVILEGE_CONTAINER); + doc.setName(XmlConstants.XML_ROOT_PRIVILEGE); + Element rootElement = factory.createElement(XmlConstants.XML_ROOT_PRIVILEGE); doc.setRootElement(rootElement); + Element containerElement = factory.createElement(XmlConstants.XML_CONTAINER); + Element parameterElement; Element parametersElement; // create PersistenceHandler Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); - rootElement.add(persistenceHandlerElem); + containerElement.add(persistenceHandlerElem); persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPersistenceHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); persistenceHandlerElem.add(parametersElement); - // Parameter usersXmlFile + // Parameter basePath parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_USERS_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, usersFileName); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_BASE_PATH); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, basePath); parametersElement.add(parameterElement); - // Parameter rolesXmlFile + // Parameter modelXmlFile parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_ROLES_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, rolesFileName); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_MODEL_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, modelFileName); parametersElement.add(parameterElement); - // Parameter privilegesXmlFile - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_PRIVILEGES_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, privilegesFileName); - parametersElement.add(parameterElement); - // Parameter policyXmlFile - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_POLICY_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, policyXmlFile); - parametersElement.add(parameterElement); - - // create PrivilegeHandler - Element privilegeHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PRIVILEGE); - rootElement.add(privilegeHandlerElem); - privilegeHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPrivilegeHandler); // create EncryptionHandler Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); - rootElement.add(encryptionHandlerElem); + containerElement.add(encryptionHandlerElem); encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultEncryptionHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); encryptionHandlerElem.add(parametersElement); diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index e0c2aadb6..5f7accc50 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -18,10 +18,12 @@ import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Element; +import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; import ch.eitchnet.privilege.handler.EncryptionHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the @@ -36,36 +38,38 @@ public class InitializationHelper { /** * Initializes the {@link PrivilegeHandler} from the configuration file * - * @param privilegeContainerXmlFile + * @param privilegeXmlFile * a {@link File} reference to the XML file containing the configuration for Privilege * * @return the {@link PrivilegeHandler} instance loaded from the configuration file */ - public static PrivilegeHandler initializeFromXml(File privilegeContainerXmlFile) { + public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { // make sure file exists - if (!privilegeContainerXmlFile.exists()) { - throw new PrivilegeException("Privilige file does not exist at path " - + privilegeContainerXmlFile.getAbsolutePath()); + if (!privilegeXmlFile.exists()) { + throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); } // parse container xml file to XML document - Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXmlFile).getRootElement(); + Element rootElement = XmlHelper.parseDocument(privilegeXmlFile).getRootElement(); + Element containerElement = rootElement.element(XmlConstants.XML_CONTAINER); // instantiate encryption handler - Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); + Element encryptionHandlerElement = containerElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); // instantiate persistence handler - Element persistenceHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); + Element persistenceHandlerElement = containerElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); // instantiate privilege handler - Element privilegeHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PRIVILEGE); - String privilegeHandlerClassName = privilegeHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PrivilegeHandler privilegeHandler = ClassHelper.instantiateClass(privilegeHandlerClassName); + PrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); + + // get policies + Element policiesElement = rootElement.element(XmlConstants.XML_POLICIES); + Map> policyMap = convertToPolicyMap(policiesElement); try { @@ -89,7 +93,7 @@ public class InitializationHelper { Map parameterMap = convertToParameterMap(parameterElement); // initialize persistence handler - persistenceHandler.initialize(parameterMap); + persistenceHandler.initialize(parameterMap, policyMap); } catch (Exception e) { logger.error(e, e); @@ -100,7 +104,7 @@ public class InitializationHelper { try { // get parameters - Element parameterElement = privilegeHandlerElement.element(XmlConstants.XML_PARAMETERS); + Element parameterElement = containerElement.element(XmlConstants.XML_PARAMETERS); Map parameterMap = convertToParameterMap(parameterElement); // initialize privilege handler @@ -108,7 +112,8 @@ public class InitializationHelper { } catch (Exception e) { logger.error(e, e); - throw new PrivilegeException("PrivilegeHandler " + privilegeHandlerClassName + " could not be initialized"); + throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + + " could not be initialized"); } return privilegeHandler; @@ -147,4 +152,37 @@ public class InitializationHelper { return parameterMap; } + + /** + * Converts an {@link XmlConstants#XML_POLICIES} element containing {@link XmlConstants#XML_POLICY} elements to a + * {@link Map} of String/Class pairs + * + * @param element + * the XML {@link Element} with name {@link XmlConstants#XML_POLICIES} containing + * {@link XmlConstants#XML_POLICY} elements + * + * @return the {@link Map} of the policy name/class combinations from the given {@link Element} + */ + @SuppressWarnings("unchecked") + public static Map> convertToPolicyMap(Element element) { + + Map> policyMap = new HashMap>(); + + List policyElements = element.elements(XmlConstants.XML_POLICY); + for (Element policyElement : policyElements) { + String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + + Class clazz; + try { + clazz = ClassHelper.loadClass(policyClass); + } catch (PrivilegeException e) { + throw new PrivilegeException("The Policy with name " + policyName + " does not exist", e); + } + + policyMap.put(policyName, clazz); + } + + return policyMap; + } } diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 465e1cf53..6bfe82148 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -19,32 +19,38 @@ public class XmlConstants { /** * XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer" : */ - public static final String XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer"; + public static final String XML_ROOT_PRIVILEGE = "Privilege"; + /** - * XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles" : + * XML_CONTAINER = "Container" : */ - public static final String XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles"; + public static final String XML_CONTAINER = "Container"; + /** - * XML_ROOT_PRIVILEGES = "Privileges" : + * XML_POLICIES = "Policies" : */ - public static final String XML_ROOT_PRIVILEGES = "Privileges"; + public static final String XML_POLICIES = "Policies"; + /** - * XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers" : + * XML_PRIVILEGES = "Privileges" : */ - public static final String XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers"; + public static final String XML_PRIVILEGES = "Privileges"; + /** - * XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies" : + * XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles" : */ - public static final String XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies"; + public static final String XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles"; /** * XML_HANDLER_PERSISTENCE = "PersistenceHandler" : */ public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; + /** * XML_HANDLER_ENCRYPTION = "EncryptionHandler" : */ public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; + /** * XML_HANDLER_PRIVILEGE = "PrivilegeHandler" : */ @@ -54,62 +60,72 @@ public class XmlConstants { * XML_ROLES = "Roles" : */ public static final String XML_ROLES = "Roles"; + /** * XML_ROLE = "Role" : */ public static final String XML_ROLE = "Role"; + /** * XML_USERS = "Users" : */ public static final String XML_USERS = "Users"; + /** * XML_USER = "User" */ public static final String XML_USER = "User"; - /** - * XML_PRIVILEGES = "Privileges" : - */ - public static final String XML_PRIVILEGES = "Privileges"; + /** * XML_PRIVILEGE = "Privilege" : */ public static final String XML_PRIVILEGE = "Privilege"; + /** * XML_POLICY = "Policy" : */ public static final String XML_POLICY = "Policy"; + /** * XML_PARAMETERS = "Parameters" : */ public static final String XML_PARAMETERS = "Parameters"; + /** * XML_PARAMETER = "Parameter" : */ public static final String XML_PARAMETER = "Parameter"; + /** * XML_ALL_ALLOWED = "AllAllowed" : */ public static final String XML_ALL_ALLOWED = "AllAllowed"; + /** * XML_DENY = "Deny" : */ public static final String XML_DENY = "Deny"; + /** * XML_ALLOW = "Allow" : */ public static final String XML_ALLOW = "Allow"; + /** * XML_FIRSTNAME = "Firstname" : */ public static final String XML_FIRSTNAME = "Firstname"; + /** * XML_SURNAME = "Surname" : */ public static final String XML_SURNAME = "Surname"; + /** * XML_STATE = "State" : */ public static final String XML_STATE = "State"; + /** * XML_LOCALE = "Locale" : */ @@ -119,26 +135,32 @@ public class XmlConstants { * XML_ATTR_CLASS = "class" : */ public static final String XML_ATTR_CLASS = "class"; + /** * XML_ATTR_NAME = "name" : */ public static final String XML_ATTR_NAME = "name"; + /** * XML_ATTR_VALUE = "value" : */ public static final String XML_ATTR_VALUE = "value"; + /** * XML_ATTR_POLICY = "policy" : */ public static final String XML_ATTR_POLICY = "policy"; + /** * XML_ATTR_USER_ID = "userId" : */ public static final String XML_ATTR_USER_ID = "userId"; + /** * XML_ATTR_USERNAME = "username" : */ public static final String XML_ATTR_USERNAME = "username"; + /** * XML_ATTR_PASSWORD = "password" : */ @@ -148,22 +170,12 @@ public class XmlConstants { * XML_PARAM_HASH_ALGORITHM = "hashAlgorithm" : */ public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; + /** - * XML_PARAM_POLICY_FILE = "policyXmlFile" : + * XML_PARAM_MODEL_FILE = "modelXmlFile" : */ - public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; - /** - * XML_PARAM_ROLES_FILE = "rolesXmlFile" : - */ - public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; - /** - * XML_PARAM_USERS_FILE = "usersXmlFile" : - */ - public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; - /** - * XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile" : - */ - public static final String XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile"; + public static final String XML_PARAM_MODEL_FILE = "modelXmlFile"; + /** * XML_PARAM_BASE_PATH = "basePath" : */ diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 7d6ca9b1c..40056ebab 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.Set; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -30,7 +31,10 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * {@link Privilege}s have allow and deny rules which the configured {@link PrivilegeHandler} uses to *

    * - *

    Note: This is an internal object which is not to be serialized or passed to clients, {@link PrivilegeRep}s are used for that

    + *

    + * Note: This is an internal object which is not to be serialized or passed to clients, {@link PrivilegeRep}s are used + * for that + *

    * * @author rvonburg */ @@ -58,6 +62,20 @@ public final class Privilege { * a list of allow rules for this {@link Privilege} */ public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + + if (name == null || name.isEmpty()) { + throw new PrivilegeException("No name defined!"); + } + if (policy == null || policy.isEmpty()) { + throw new PrivilegeException("No policy defined!"); + } + if (denyList == null) { + throw new PrivilegeException("No denyList defined!"); + } + if (allowList == null) { + throw new PrivilegeException("No allowList defined!"); + } + this.name = name; this.policy = policy; this.allAllowed = allAllowed; diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index a4788ea43..6d9205f34 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.RoleRep; /** @@ -43,6 +44,14 @@ public final class Role { * a set of names of privileges granted to this role */ public Role(String name, Set privileges) { + + if (name == null || name.isEmpty()) { + throw new PrivilegeException("No name defined!"); + } + if (privileges == null) { + throw new PrivilegeException("No privileges defined!"); + } + this.name = name; this.privileges = Collections.unmodifiableSet(privileges); } diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index 14e1129be..7e0df5272 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.model.internal; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; /** @@ -59,6 +60,20 @@ public final class Session { * the time the user logged in */ public Session(String sessionId, String username, String authToken, String authPassword, long loginTime) { + + if (sessionId == null || sessionId.isEmpty()) { + throw new PrivilegeException("No sessionId defined!"); + } + if (username == null || username.isEmpty()) { + throw new PrivilegeException("No username defined!"); + } + if (authToken == null || authToken.isEmpty()) { + throw new PrivilegeException("No authToken defined!"); + } + if (authPassword == null || authPassword.isEmpty()) { + throw new PrivilegeException("No authPassword defined!"); + } + this.sessionId = sessionId; this.username = username; this.authToken = authToken; diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 2118763e3..975a7e7ee 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.Locale; import java.util.Set; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -69,6 +70,29 @@ public final class User { public User(String userId, String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale) { + if (userId == null || userId.isEmpty()) { + throw new PrivilegeException("No UserId defined!"); + } + if (username == null || username.isEmpty()) { + throw new PrivilegeException("No username defined!"); + } + + // password may be null, meaning not able to login + + if (firstname == null || firstname.isEmpty()) { + throw new PrivilegeException("No firstname defined!"); + } + if (surname == null || surname.isEmpty()) { + throw new PrivilegeException("No surname defined!"); + } + if (userState == null) { + throw new PrivilegeException("No userState defined!"); + } + + // roles may be null, meaning not able to login and must be added later + + // local may be null, meaning use system default + this.userId = userId; this.username = username; diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index ff800f009..f48579942 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -55,7 +55,7 @@ public class PrivilegeTest { // initialize container String pwd = System.getProperty("user.dir"); - File privilegeContainerXmlFile = new File(pwd + "/config/PrivilegeContainer.xml"); + File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); } catch (Exception e) { logger.error(e, e); From d2dff36b4b4c7c8eddf373c99d0117c8dc450fc6 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 28 Jul 2011 22:31:18 +0000 Subject: [PATCH 053/457] [Minor] exposed some fields of the DefaultPrivilegeHandler by setting them to protected --- .../handler/DefaultPrivilegeHandler.java | 23 +++++++++++++------ .../privilege/helper/XmlConstants.java | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index a6e7de1b0..f84c10d15 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -57,27 +57,32 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * log4j logger */ - private static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); + protected static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); /** * last assigned id for the {@link Session}s */ - private static long lastSessionId; + protected long lastSessionId; /** * Map keeping a reference to all active sessions with their certificates */ - private Map sessionMap; + protected Map sessionMap; /** * The persistence handler is used for getting objects and saving changes */ - private PersistenceHandler persistenceHandler; + protected PersistenceHandler persistenceHandler; /** * The encryption handler is used for generating hashes and tokens */ - private EncryptionHandler encryptionHandler; + protected EncryptionHandler encryptionHandler; + + /** + * flag to define if already initialized + */ + protected boolean initialized; /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) @@ -899,18 +904,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, PersistenceHandler persistenceHandler) { + if (this.initialized) + throw new PrivilegeException("Already initialized!"); + this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; - lastSessionId = 0l; + this.lastSessionId = 0l; this.sessionMap = Collections.synchronizedMap(new HashMap()); + this.initialized = true; } /** * @return a new session id */ private synchronized String nextSessionId() { - return Long.toString(++lastSessionId % Long.MAX_VALUE); + return Long.toString(++this.lastSessionId % Long.MAX_VALUE); } /** diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 6bfe82148..26bfb757b 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -16,6 +16,7 @@ package ch.eitchnet.privilege.helper; * @author rvonburg */ public class XmlConstants { + /** * XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer" : */ From 7e8228530642136b5080c5e5d9ff77d55901a662 Mon Sep 17 00:00:00 2001 From: eitch Date: Fri, 29 Jul 2011 00:30:08 +0000 Subject: [PATCH 054/457] [Minor] logging of authentication attempts --- .../handler/DefaultPrivilegeHandler.java | 97 ++++++++++--------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index f84c10d15..2aaa5ecab 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -608,53 +608,62 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public Certificate authenticate(String username, String password) { - // both username and password must at least have 3 characters in length - if (username == null || username.length() < 3) - throw new PrivilegeException("The given username is shorter than 3 characters"); - else if (password == null || password.length() < 3) - throw new PrivilegeException("The given password is shorter than 3 characters"); - - // we only work with hashed passwords - String passwordHash = this.encryptionHandler.convertToHash(password); - - // get user object - User user = this.persistenceHandler.getUser(username); - // no user means no authentication - if (user == null) - throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); - - // validate password - String pwHash = user.getPassword(); - if (pwHash == null) - throw new AccessDeniedException("User has no password and may not login!"); - if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); - - // validate if user is allowed to login - if (user.getUserState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getUserState()); - - // validate user has at least one role - if (user.getRoles().isEmpty()) { - throw new PrivilegeException("User " + username + " does not have any roles defined!"); - } - - // get 2 auth tokens - String authToken = this.encryptionHandler.nextToken(); - String authPassword = this.encryptionHandler.nextToken(); - - // get next session id - String sessionId = nextSessionId(); - // create certificate - Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + Certificate certificate; + try { + // both username and password must at least have 3 characters in length + if (username == null || username.length() < 3) + throw new PrivilegeException("The given username is shorter than 3 characters"); + else if (password == null || password.length() < 3) + throw new PrivilegeException("The given password is shorter than 3 characters"); - // create and save a new session - Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); - this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + // we only work with hashed passwords + String passwordHash = this.encryptionHandler.convertToHash(password); - // log - logger.info("Authenticated: " + session); + // get user object + User user = this.persistenceHandler.getUser(username); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("There is no user defined with the credentials: " + username + + " / ***..."); + + // validate password + String pwHash = user.getPassword(); + if (pwHash == null) + throw new AccessDeniedException("User has no password and may not login!"); + if (!pwHash.equals(passwordHash)) + throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + + // validate if user is allowed to login + if (user.getUserState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + + user.getUserState()); + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + throw new PrivilegeException("User " + username + " does not have any roles defined!"); + } + + // get 2 auth tokens + String authToken = this.encryptionHandler.nextToken(); + String authPassword = this.encryptionHandler.nextToken(); + + // get next session id + String sessionId = nextSessionId(); + + certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + + // create and save a new session + Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); + this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + + // log + logger.info("User " + username + " authenticated: " + session); + + } catch (RuntimeException e) { + logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); + throw e; + } // return the certificate return certificate; From 20a696918bab35e86468b0b63940baac2b4b5643 Mon Sep 17 00:00:00 2001 From: eitch Date: Fri, 29 Jul 2011 19:51:00 +0000 Subject: [PATCH 055/457] --- .../eitchnet/privilege/helper/HashHelper.java | 22 +++- .../privilege/helper/PasswordCreaterUI.java | 119 ++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java diff --git a/src/ch/eitchnet/privilege/helper/HashHelper.java b/src/ch/eitchnet/privilege/helper/HashHelper.java index cbf6ae8f3..47ed49dc1 100644 --- a/src/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/ch/eitchnet/privilege/helper/HashHelper.java @@ -45,9 +45,29 @@ public class HashHelper { */ public static String stringToHash(String hashAlgorithm, String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { + return stringToHash(hashAlgorithm, string.getBytes()); + } + + /** + * Creates the hash of the given string using {@link MessageDigest} and the defined hash algorithm + * + * @param hashAlgorithm + * the algorithm to use for hashing + * @param bytes + * the bytes to hash + * + * @return a new string encrypted by the defined algorithm + * + * @throws NoSuchAlgorithmException + * if the algorithm is not found + * @throws UnsupportedEncodingException + * if something is wrong with the given string to hash + */ + public static String stringToHash(String hashAlgorithm, byte[] bytes) throws NoSuchAlgorithmException, + UnsupportedEncodingException { MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); - byte[] hashArray = digest.digest(string.getBytes()); + byte[] hashArray = digest.digest(bytes); byte[] hex = new byte[2 * hashArray.length]; int index = 0; diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java new file mode 100644 index 000000000..7ea816207 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2010 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ + +/** + * + */ +package ch.eitchnet.privilege.helper; + +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.SwingConstants; + +/** + * Simple Swing UI to create passwords + * + * @author rvonburg + */ +public class PasswordCreaterUI { + + /** + * Launches the UI + * + * @param args + * not used + */ + public static void main(String[] args) { + + JFrame.setDefaultLookAndFeelDecorated(true); + + JFrame frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setTitle("Password creator"); + frame.setLayout(new GridLayout(4, 2)); + + JLabel digest = new JLabel("Digest:", SwingConstants.RIGHT); + JLabel password = new JLabel("Password:", SwingConstants.RIGHT); + JLabel hash = new JLabel("Hash:", SwingConstants.RIGHT); + + String[] digests = new String[] { "MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512" }; + final JComboBox digestCombo = new JComboBox(digests); + digestCombo.setSelectedIndex(3); + final JPasswordField passwordField = new JPasswordField(); + final JTextField hashField = new JTextField(150); + + JButton digestBtn = new JButton("Digest"); + + passwordField.addKeyListener(new KeyListener() { + + @Override + public void keyTyped(KeyEvent e) { + // + } + + @Override + public void keyReleased(KeyEvent e) { + // + } + + @Override + public void keyPressed(KeyEvent e) { + hashField.setText(""); + } + }); + digestBtn.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + + try { + String digest = (String) digestCombo.getSelectedItem(); + char[] passwordChar = passwordField.getPassword(); + String password = new String(passwordChar); + String hash = HashHelper.stringToHash(digest, password); + hashField.setText(hash); + } catch (Exception e1) { + e1.printStackTrace(); + hashField.setText("Failed: " + e1.getLocalizedMessage()); + } + } + }); + + frame.add(digest); + frame.add(digestCombo); + frame.add(password); + frame.add(passwordField); + frame.add(hash); + frame.add(hashField); + frame.add(new JLabel()); + frame.add(digestBtn); + + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int width = 500; + int height = 160; + frame.setSize(width, height); + frame.setLocation(screenSize.width / 2 - width, screenSize.height / 2 - height); + + frame.setVisible(true); + } +} From 65c17ab4a31123680f198ca7218b97bc208bced7 Mon Sep 17 00:00:00 2001 From: eitch Date: Fri, 29 Jul 2011 19:52:44 +0000 Subject: [PATCH 056/457] [New] added a PasswordCreaterUI with which hashes of passwords can be created --- src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index 7ea816207..81690984a 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -116,4 +116,4 @@ public class PasswordCreaterUI { frame.setVisible(true); } -} +} \ No newline at end of file From 8b9c8ea1e55721eabb706478b110918eb66797c3 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 30 Jul 2011 13:20:08 +0000 Subject: [PATCH 057/457] [Major] refactored the use of the Privilege class, now Roles have Privileges in a composition relationship --- config/PrivilegeModel.xml | 41 +- .../handler/DefaultPrivilegeHandler.java | 312 ++++++--------- .../privilege/handler/PersistenceHandler.java | 73 ++-- .../privilege/handler/PrivilegeHandler.java | 161 +------- .../handler/XmlPersistenceHandler.java | 363 ++++++++---------- .../helper/InitializationHelper.java | 8 +- .../privilege/model/PrivilegeRep.java | 43 +++ src/ch/eitchnet/privilege/model/RoleRep.java | 26 +- .../privilege/model/internal/Privilege.java | 98 ++--- .../privilege/model/internal/Role.java | 69 ++-- .../privilege/model/internal/User.java | 31 ++ .../privilege/test/PrivilegeTest.java | 88 +++-- .../privilege/test/TestRestrictable.java | 2 +- 13 files changed, 556 insertions(+), 759 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 429e3a2f8..493476d57 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -1,31 +1,16 @@ - - - true - - - - - - false - - ch.eitchnet.privilege.test.TestRestrictable - - - - - Robert - von Burg + + Featherlite + Administrator ENABLED en_GB PrivilegeAdmin - admin - serviceExecutor + FeatherliteUser @@ -35,14 +20,16 @@ - - - - - - - - + + + true + + + true + + + true + diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2aaa5ecab..f92df13c7 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -19,6 +19,7 @@ import java.util.Set; import org.apache.log4j.Logger; +import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -64,6 +65,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ protected long lastSessionId; + /** + * Map of {@link PrivilegePolicy} classes + */ + private Map> policyMap; + /** * Map keeping a reference to all active sessions with their certificates */ @@ -84,20 +90,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ protected boolean initialized; - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) - */ - @Override - public PrivilegeRep getPrivilege(String privilegeName) { - return this.persistenceHandler.getPrivilege(privilegeName).asPrivilegeRep(); - } - /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) */ @Override public RoleRep getRole(String roleName) { - return this.persistenceHandler.getRole(roleName).asRoleRep(); + Role role = this.persistenceHandler.getRole(roleName); + if (role == null) + throw new PrivilegeException("Role " + roleName + " does not exist!"); + return role.asRoleRep(); } /** @@ -105,33 +106,45 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public UserRep getUser(String username) { - return this.persistenceHandler.getUser(username).asUserRep(); + User user = this.persistenceHandler.getUser(username); + if (user == null) + throw new PrivilegeException("User " + username + " does not exist!"); + return user.asUserRep(); } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPolicy(java.lang.String) + *

    + * This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy} + * is not stored in a database. The privilege name is a class name and is then used to instantiate a new + * {@link PrivilegePolicy} object + *

    + * + * @param policyName + * the class name of the {@link PrivilegePolicy} object to return + * + * @return the {@link PrivilegePolicy} object + * + * @throws PrivilegeException + * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated */ - @Override - public PrivilegePolicy getPolicy(String policyName) { - return this.persistenceHandler.getPolicy(policyName); - } + protected PrivilegePolicy getPolicy(String policyName) { - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.PrivilegeRep) - */ - @Override - public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { + // get the policies class + Class policyClazz = this.policyMap.get(policyName); + if (policyClazz == null) { + return null; + } - // validate who is doing this - validateIsPrivilegeAdmin(certificate); + // instantiate the policy + PrivilegePolicy policy; + try { + policy = ClassHelper.instantiateClass(policyClazz); + } catch (Exception e) { + throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" + + policyName, e); + } - // create a new privilege - Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), - privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); - - // delegate to persistence handler - this.persistenceHandler.addOrReplacePrivilege(privilege); + return policy; } /** @@ -144,8 +157,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this validateIsPrivilegeAdmin(certificate); - // create new role - Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); + // create new role from RoleRep + Role role = new Role(roleRep); + + // validate policy if not null + validatePolicies(role); // delegate to persistence handler this.persistenceHandler.addOrReplaceRole(role); @@ -180,39 +196,37 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilegeOnRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, ch.eitchnet.privilege.model.PrivilegeRep) */ @Override - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { + public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { // validate who is doing this validateIsPrivilegeAdmin(certificate); + // validate PrivilegeRep + privilegeRep.validate(); + // get role Role role = this.persistenceHandler.getRole(roleName); if (role == null) { throw new PrivilegeException("Role " + roleName + " does not exist!"); } - // ignore if role already has this privilege - Set currentPrivileges = role.getPrivileges(); - if (currentPrivileges.contains(roleName)) { - logger.error("Role " + roleName + " already has privilege " + privilegeName); - return; - } - - // validate that privilege exists - if (getPrivilege(privilegeName) == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist and can not be added to role " - + roleName); + // validate that policy exists if needed + String policy = privilegeRep.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + throw new PrivilegeException("Policy " + policy + " for Privilege " + privilegeRep.getName() + + " does not exist"); } // create new role with the additional privilege - Set newPrivileges = new HashSet(currentPrivileges); - newPrivileges.add(roleName); + Privilege newPrivilege = new Privilege(privilegeRep); + Map privilegeMap = new HashMap(role.getPrivilegeMap()); + privilegeMap.put(newPrivilege.getName(), newPrivilege); - Role newRole = new Role(role.getName(), newPrivileges); + Role newRole = new Role(role.getName(), privilegeMap); // delegate role replacement to persistence handler this.persistenceHandler.addOrReplaceRole(newRole); @@ -257,26 +271,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ - @Override - public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // delegate privilege removal to persistence handler - Privilege removedPrivilege = this.persistenceHandler.removePrivilege(privilegeName); - - if (removedPrivilege == null) - return null; - - // return privilege rep if it was removed - return removedPrivilege.asPrivilegeRep(); - } - /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) @@ -294,15 +288,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // ignore if role does not have privilege - Set currentPrivileges = role.getPrivileges(); - if (!currentPrivileges.contains(privilegeName)) { - logger.error("Role " + roleName + " doest not have privilege " + privilegeName); - return; + if (!role.hasPrivilege(privilegeName)) + throw new PrivilegeException("Role " + roleName + " does not have Privilege " + privilegeName); + + // create new set of privileges with out the to remove privilege + Map newPrivileges = new HashMap(role.getPrivilegeMap().size() - 1); + for (Privilege privilege : role.getPrivilegeMap().values()) { + if (!privilege.getName().equals(privilegeName)) + newPrivileges.put(privilege.getName(), privilege); } // create new role - Set newPrivileges = new HashSet(currentPrivileges); - newPrivileges.remove(privilegeName); Role newRole = new Role(role.getName(), newPrivileges); // delegate user replacement to persistence handler @@ -384,115 +380,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, boolean) - */ - @Override - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // get Privilege - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // ignore if privilege is already set to argument - if (privilege.isAllAllowed() == allAllowed) { - logger.error("Privilege " + privilegeName + " is already set to " - + (allAllowed ? "all allowed" : "not all allowed")); - return; - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, - privilege.getDenyList(), privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - this.persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Set) - */ - @Override - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // get Privilege - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), - privilege.getDenyList(), allowList); - - // delegate privilege replacement to persistence handler - this.persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Set) - */ - @Override - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // get Privilege - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), - denyList, privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - this.persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // get Privilege - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // validate that the policy exists - PrivilegePolicy policy = this.persistenceHandler.getPolicy(policyName); - if (policy == null) { - throw new PrivilegeException("No privilege policy exists for the name " + policyName); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), - privilege.getDenyList(), privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - this.persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocale(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Locale) @@ -706,9 +593,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // first validate certificate if (!isCertificateValid(certificate)) { - logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " - + restrictable); - return false; + throw new PrivilegeException("Certificate is not valid, so action is not allowed: " + certificate + + " for restrictable: " + restrictable); } // restrictable must not be null @@ -797,13 +683,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get the privilege for this restrictable - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " - + restrictable.getClass().getName()); - } + Privilege privilege = role.getPrivilegeMap().get(privilegeName); - // get the policy configured for this privilege + // check if all is allowed + if (privilege.isAllAllowed()) + return true; + + // otherwise delegate checking to the policy configured for this privilege PrivilegePolicy policy = this.getPolicy(privilege.getPolicy()); if (policy == null) { throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " @@ -906,24 +792,58 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#initialize(java.util.Map, - * ch.eitchnet.privilege.handler.EncryptionHandler, ch.eitchnet.privilege.handler.PersistenceHandler) + * Initializes the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this + * {@link PrivilegeHandler} might need. This method may only be called once and this must be enforced by the + * concrete implementation + * + * @param parameterMap + * a map containing configuration properties + * @param encryptionHandler + * the {@link EncryptionHandler} instance for this {@link PrivilegeHandler} + * @param persistenceHandler + * the {@link PersistenceHandler} instance for this {@link PrivilegeHandler} + * @param policyMap + * map of {@link PrivilegePolicy} classes + * + * @throws PrivilegeException + * if the this method is called multiple times or an initialization exception occurs */ - @Override public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler) { + PersistenceHandler persistenceHandler, Map> policyMap) { if (this.initialized) throw new PrivilegeException("Already initialized!"); + this.policyMap = policyMap; this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; + // validate policies on privileges of Roles + for (Role role : persistenceHandler.getAllRoles()) { + validatePolicies(role); + } + this.lastSessionId = 0l; this.sessionMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } + /** + * Validates that the policies which are not null on the privileges of the role exist + * + * @param role + * the role for which the policies are to be checked + */ + private void validatePolicies(Role role) { + for (Privilege privilege : role.getPrivilegeMap().values()) { + String policy = privilege.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + throw new PrivilegeException("Policy " + policy + " for Privilege " + privilege.getName() + + " does not exist on role " + role); + } + } + } + /** * @return a new session id */ diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 13531f834..65026f2f0 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,9 +10,9 @@ package ch.eitchnet.privilege.handler; +import java.util.List; import java.util.Map; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -36,6 +36,20 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public interface PersistenceHandler { + /** + * Returns all currently known {@link User}s + * + * @return all currently known {@link User}s + */ + public List getAllUsers(); + + /** + * Returns all currently known {@link Role}s + * + * @return all currently known {@link Role}s + */ + public List getAllRoles(); + /** * Returns a {@link User} object from the underlying database * @@ -56,33 +70,6 @@ public interface PersistenceHandler { */ public Role getRole(String roleName); - /** - * Returns a {@link Privilege} object from the underlying database - * - * @param privilegeName - * the name/id of the {@link Privilege} object to return - * - * @return the {@link Privilege} object, or null if it was not found - */ - public Privilege getPrivilege(String privilegeName); - - /** - *

    - * This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy} - * is not stored in a database. The privilege name is a class name and is then used to instantiate a new - * {@link PrivilegePolicy} object - *

    - * - * @param policyName - * the class name of the {@link PrivilegePolicy} object to return - * - * @return the {@link PrivilegePolicy} object - * - * @throws PrivilegeException - * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated - */ - public PrivilegePolicy getPolicy(String policyName) throws PrivilegeException; - /** * Removes a {@link User} with the given name and returns the removed object if it existed * @@ -103,16 +90,6 @@ public interface PersistenceHandler { */ public Role removeRole(String roleName); - /** - * Removes a {@link Privilege} with the given name and returns the removed object if it existed - * - * @param privilegeName - * the name of the {@link Privilege} to remove - * - * @return the {@link Privilege} removed, or null if it did not exist - */ - public Privilege removePrivilege(String privilegeName); - /** * Adds a {@link User} object to the underlying database. If the {@link User} already exists, it is replaced * @@ -129,15 +106,6 @@ public interface PersistenceHandler { */ public void addOrReplaceRole(Role role); - /** - * Adds a {@link Privilege} object to the underlying database. If the {@link Privilege} already exists, it is - * replaced - * - * @param privilege - * the {@link Privilege} object to add - */ - public void addOrReplacePrivilege(Privilege privilege); - /** * Informs this {@link PersistenceHandler} to persist any changes which need to be saved * @@ -145,14 +113,19 @@ public interface PersistenceHandler { */ public boolean persist(); + /** + * Informs this {@link PersistenceHandler} to reload the data from the backend + * + * @return true if the reload was successful, false if something went wrong + */ + public boolean reload(); + /** * Initialize the concrete {@link PersistenceHandler}. The passed parameter map contains any configuration the * concrete {@link PersistenceHandler} might need * * @param parameterMap * a map containing configuration properties - * @param policyMap - * map of policy key/policy class pairs */ - public void initialize(Map parameterMap, Map> policyMap); + public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 84de5495e..49608c42d 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -10,10 +10,7 @@ package ch.eitchnet.privilege.handler; -import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.Set; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -27,7 +24,6 @@ import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * The {@link PrivilegeHandler} is the centrally exposed API for accessing the privilege library. It exposes all needed @@ -64,26 +60,6 @@ public interface PrivilegeHandler { */ public RoleRep getRole(String roleName); - /** - * Returns a {@link PrivilegeRep} for the given privilegeName - * - * @param privilegeName - * the name of the {@link PrivilegeRep} to return - * - * @return the {@link PrivilegeRep} for the given privilegeName, or null if it was not found - */ - public PrivilegeRep getPrivilege(String privilegeName); - - /** - * Returns a {@link PrivilegePolicy} for the given policyName - * - * @param policyName - * the name of the {@link PrivilegePolicy} to return - * - * @return the {@link PrivilegePolicy} for the given policyName, or null if it was not found - */ - public PrivilegePolicy getPolicy(String policyName); - /** * Removes the user with the given username * @@ -156,24 +132,6 @@ public interface PrivilegeHandler { public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) throws AccessDeniedException, PrivilegeException; - /** - * Removes the privilege with the given privilegeName - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the privilegeName of the privilege to remove - * - * @return the {@link PrivilegeRep} of the privilege removed, or null if the privilege did not exist - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) throws AccessDeniedException, - PrivilegeException; - /** *

    * Adds a new user, or replaces the user with the information from this {@link UserRep} if the user already exists @@ -217,23 +175,6 @@ public interface PrivilegeHandler { public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; - /** - * Adds a new privilege, or replaces the privilege with the information from this {@link PrivilegeRep} if the - * privilege already exists - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeRep - * the {@link PrivilegeRep} containing the information to create the new {@link Privilege} - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) throws AccessDeniedException, - PrivilegeException; - /** * Adds the role with the given roleName to the {@link User} with the given username * @@ -253,21 +194,21 @@ public interface PrivilegeHandler { PrivilegeException; /** - * Adds the privilege with the given privilegeName to the {@link Role} with the given roleName + * Adds the {@link PrivilegeRep} to the {@link Role} with the given roleName * * @param certificate * the {@link Certificate} of the user which has the privilege to perform this action * @param roleName * the roleName of the {@link Role} to which the privilege should be added - * @param privilegeName - * the privilegeName of the {@link Privilege} which should be added to the {@link Role} + * @param privilegeRep + * the representation of the {@link Privilege} which should be added to the {@link Role} * * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) + public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) throws AccessDeniedException, PrivilegeException; /** @@ -349,82 +290,6 @@ public interface PrivilegeHandler { public void setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, PrivilegeException; - /** - * Sets the class name of the {@link PrivilegePolicy} object for the {@link Privilege} registered for the given - * privilegeName - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the name of the {@link Privilege} for which the policy is to be changed - * @param policyName - * class name of the {@link PrivilegePolicy} to be registered for this {@link Privilege} - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate or if the class name of the policy is invalid - */ - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) - throws AccessDeniedException, PrivilegeException; - - /** - * Sets the special flag on the given {@link Privilege} defined by the privilegeName argument so that all is allowed - * and thus the allow and deny list of the privilege are ignored - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the name of the {@link Privilege} for which the allAllowed value is to be changed - * @param allAllowed - * the boolean defining if all privileges are granted for the value true, or not for the value false - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) - throws AccessDeniedException, PrivilegeException; - - /** - * Sets the {@link List} of strings which represent values which denies finer grained privilege rights for the - * {@link Privilege} defined by the privilegeName argument - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the name of the {@link Privilege} for which the deny list is to be changed - * @param denyList - * the {@link List} of strings which represent values which denies finer grained privilege rights - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) - throws AccessDeniedException, PrivilegeException; - - /** - * Sets the {@link List} of strings which represent values which grants finer grained privilege rights for the - * {@link Privilege} defined by the privilegeName argument - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the name of the {@link Privilege} for which the allow list is to be changed - * @param allowList - * the {@link List} of strings which represent values which grants finer grained privilege rights - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) - throws AccessDeniedException, PrivilegeException; - /** * Authenticates a user by validating that a {@link User} for the given username and password exist and then returns * a {@link Certificate} with which this user may then perform actions @@ -548,22 +413,4 @@ public interface PrivilegeHandler { * if the users of the given certificate does not have the privilege to perform this action */ public boolean persist(Certificate certificate) throws AccessDeniedException; - - /** - * Initializes the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this - * {@link PrivilegeHandler} might need. This method may only be called once and this must be enforced by the - * concrete implementation - * - * @param parameterMap - * a map containing configuration properties - * @param encryptionHandler - * the {@link EncryptionHandler} instance for this {@link PrivilegeHandler} - * @param persistenceHandler - * the {@link PersistenceHandler} instance for this {@link PrivilegeHandler} - * - * @throws PrivilegeException - * if the this method is called multiple times or an initialization exception occurs - */ - public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler) throws PrivilegeException; } diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 86c9dd6ae..8d681976f 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -24,7 +25,6 @@ import org.apache.log4j.Logger; import org.dom4j.DocumentFactory; import org.dom4j.Element; -import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -32,11 +32,10 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed - * in {@link #initialize(Map, Map)} + * in {@link #initialize(Map)} * * @author rvonburg */ @@ -46,44 +45,59 @@ public class XmlPersistenceHandler implements PersistenceHandler { private Map userMap; private Map roleMap; - private Map privilegeMap; - private Map> policyMap; private long modelsFileDate; private boolean userMapDirty; private boolean roleMapDirty; - private boolean privilegeMapDirty; private Map parameterMap; - private String basePath; + private String modelPath; /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.internal.Privilege) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getAllUsers() */ @Override - public void addOrReplacePrivilege(Privilege privilege) { - this.privilegeMap.put(privilege.getName(), privilege); - this.privilegeMapDirty = true; + public List getAllUsers() { + synchronized (this.userMap) { + return new LinkedList(this.userMap.values()); + } } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#removePrivilege(java.lang.String) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getAllRoles() */ @Override - public Privilege removePrivilege(String privilegeName) { - Privilege privilege = this.privilegeMap.remove(privilegeName); - this.privilegeMapDirty = privilege != null; - return privilege; + public List getAllRoles() { + synchronized (this.roleMap) { + return new LinkedList(this.roleMap.values()); + } } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.internal.Role) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getUser(java.lang.String) */ @Override - public void addOrReplaceRole(Role role) { - this.roleMap.put(role.getName(), role); - this.roleMapDirty = true; + public User getUser(String username) { + return this.userMap.get(username); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getRole(java.lang.String) + */ + @Override + public Role getRole(String roleName) { + return this.roleMap.get(roleName); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeUser(java.lang.String) + */ + @Override + public User removeUser(String username) { + User user = this.userMap.remove(username); + this.userMapDirty = user != null; + return user; } /** @@ -106,61 +120,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeUser(java.lang.String) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.internal.Role) */ @Override - public User removeUser(String username) { - User user = this.userMap.remove(username); - this.userMapDirty = user != null; - return user; - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPrivilege(java.lang.String) - */ - @Override - public Privilege getPrivilege(String privilegeName) { - return this.privilegeMap.get(privilegeName); - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getRole(java.lang.String) - */ - @Override - public Role getRole(String roleName) { - return this.roleMap.get(roleName); - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getUser(java.lang.String) - */ - @Override - public User getUser(String username) { - return this.userMap.get(username); - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPolicy(java.lang.String) - */ - @Override - public PrivilegePolicy getPolicy(String policyName) { - - // get the policies class - Class policyClazz = this.policyMap.get(policyName); - if (policyClazz == null) { - return null; - } - - // instantiate the policy - PrivilegePolicy policy; - try { - policy = ClassHelper.instantiateClass(policyClazz); - } catch (Exception e) { - throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" - + policyName, e); - } - - return policy; + public void addOrReplaceRole(Role role) { + this.roleMap.put(role.getName(), role); + this.roleMapDirty = true; } /** @@ -176,9 +141,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); } // get model file - File modelFile = new File(this.basePath + "/" + modelFileName); + File modelFile = new File(this.modelPath); boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; - if (!(modelFileUnchanged && this.privilegeMapDirty && this.roleMapDirty && this.userMapDirty)) { + if (!(modelFileUnchanged && this.roleMapDirty && this.userMapDirty)) { logger.warn("Not persisting as current file is unchanged and model data is not dirty"); return false; } @@ -206,20 +171,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { } rootElement.add(rolesElement); - // PRIVILEGES - // build XML DOM of privileges - List privileges = toDomPrivileges(); - Element privilegesElement = docFactory.createElement(XmlConstants.XML_PRIVILEGES); - for (Element privilegeElement : privileges) { - privilegesElement.add(privilegeElement); - } - rootElement.add(privilegesElement); - // reset dirty states and return if something was dirty, false otherwise - if (this.userMapDirty || this.roleMapDirty || this.privilegeMapDirty) { + if (this.userMapDirty || this.roleMapDirty) { this.userMapDirty = false; this.roleMapDirty = false; - this.privilegeMapDirty = false; return true; @@ -227,45 +182,27 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMapDirty = false; this.roleMapDirty = false; - this.privilegeMapDirty = false; return false; } /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#reload() */ @Override - public void initialize(Map parameterMap, Map> policyMap) { - - this.roleMap = Collections.synchronizedMap(new HashMap()); - this.userMap = Collections.synchronizedMap(new HashMap()); - this.privilegeMap = Collections.synchronizedMap(new HashMap()); - this.policyMap = policyMap; - - // get and validate base bath - this.basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); - File basePathF = new File(this.basePath); - if (!basePathF.exists() && !basePathF.isDirectory()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); - } - - // get model file name - String modelFileName = parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); - if (modelFileName == null || modelFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); - } + public boolean reload() { // validate file exists - File modelsFile = new File(this.basePath + "/" + modelFileName); + File modelsFile = new File(this.modelPath); if (!modelsFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " + modelsFile.getAbsolutePath()); } + this.roleMap = Collections.synchronizedMap(new HashMap()); + this.userMap = Collections.synchronizedMap(new HashMap()); + // parse models xml file to XML document Element modelsRootElement = XmlHelper.parseDocument(modelsFile).getRootElement(); this.modelsFileDate = modelsFile.lastModified(); @@ -282,19 +219,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read users readUsers(usersElement); - // PRIVILEGES - // get privileges element - Element privilegesElement = modelsRootElement.element(XmlConstants.XML_PRIVILEGES); - // read privileges - readPrivileges(privilegesElement); - this.userMapDirty = false; this.roleMapDirty = false; - this.privilegeMapDirty = false; logger.info("Read " + this.userMap.size() + " Users"); logger.info("Read " + this.roleMap.size() + " Roles"); - logger.info("Read " + this.privilegeMap.size() + " Privileges"); // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; @@ -310,6 +239,36 @@ public class XmlPersistenceHandler implements PersistenceHandler { logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + "' exists. Privilege modifications will not be possible!"); } + + return true; + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#initialize(java.util.Map) + */ + @Override + public void initialize(Map parameterMap) { + + // get and validate base bath + String basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + File basePathF = new File(basePath); + if (!basePathF.exists() && !basePathF.isDirectory()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); + } + + // get model file name + String modelFileName = parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + if (modelFileName == null || modelFileName.isEmpty()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); + } + + // save path to model + this.modelPath = basePath + "/" + modelFileName; + + if (reload()) + logger.info("Privilege Data loaded."); } /** @@ -369,27 +328,23 @@ public class XmlPersistenceHandler implements PersistenceHandler { String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); - @SuppressWarnings("unchecked") - List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); - Set privileges = new HashSet(); - for (Element privilegeElement : privilegeElements) { + Map privilegeMap = readPrivileges(roleElement); - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - privileges.add(privilegeName); - } - - Role role = new Role(roleName, privileges); + Role role = new Role(roleName, privilegeMap); this.roleMap.put(roleName, role); } } /** - * @param rolesRootElement + * @param roleParentElement + * @return */ - private void readPrivileges(Element privilegesRootElement) { + private Map readPrivileges(Element roleParentElement) { + + Map privilegeMap = new HashMap(); @SuppressWarnings("unchecked") - List privilegeElements = privilegesRootElement.elements(XmlConstants.XML_PRIVILEGE); + List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); for (Element privilegeElement : privilegeElements) { String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); @@ -403,11 +358,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { Set denyList = new HashSet(denyElements.size()); for (Element denyElement : denyElements) { String denyValue = denyElement.getTextTrim(); - if (denyValue.isEmpty()) { - logger.error("Privilege " + privilegeName + " has an empty deny value!"); - } else { + if (!denyValue.isEmpty()) denyList.add(denyValue); - } } @SuppressWarnings("unchecked") @@ -415,89 +367,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { Set allowList = new HashSet(allowElements.size()); for (Element allowElement : allowElements) { String allowValue = allowElement.getTextTrim(); - if (allowValue.isEmpty()) { - logger.error("Privilege " + privilegeName + " has an empty allow value!"); - } else { + if (!allowValue.isEmpty()) allowList.add(allowValue); - } } Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - this.privilegeMap.put(privilegeName, privilege); - } - } - - private List toDomPrivileges() { - - List privilegesAsElements = new ArrayList(this.privilegeMap.size()); - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - synchronized (this.privilegeMap) { - for (String privilegeName : this.privilegeMap.keySet()) { - - // get the privilege object - Privilege privilege = this.privilegeMap.get(privilegeName); - - // create the privilege element - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); - - // add the all allowed element - Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.add(allAllowedElement); - - // add all the deny values - for (String denyValue : privilege.getDenyList()) { - Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); - denyValueElement.setText(denyValue); - privilegeElement.add(denyValueElement); - } - - // add all the allow values - for (String allowValue : privilege.getAllowList()) { - Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); - allowValueElement.setText(allowValue); - privilegeElement.add(allowValueElement); - } - - // add element to return list - privilegesAsElements.add(privilegeElement); - } + privilegeMap.put(privilegeName, privilege); } - return privilegesAsElements; - } - - private List toDomRoles() { - - List rolesAsElements = new ArrayList(this.roleMap.size()); - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - synchronized (this.roleMap) { - for (String roleName : this.roleMap.keySet()) { - - // get the role object - Role role = this.roleMap.get(roleName); - - // create the role element - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - - // add all the privileges - for (String privilegeName : role.getPrivileges()) { - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilegeName); - roleElement.add(privilegeElement); - } - - // add element to return list - rolesAsElements.add(roleElement); - } - } - - return rolesAsElements; + return privilegeMap; } private List toDomUsers() { @@ -505,6 +383,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { List usersAsElements = new ArrayList(this.userMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); + synchronized (this.userMap) { for (String userName : this.userMap.keySet()) { @@ -553,4 +432,66 @@ public class XmlPersistenceHandler implements PersistenceHandler { return usersAsElements; } + + private List toDomRoles() { + + List rolesAsElements = new ArrayList(this.roleMap.size()); + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + + synchronized (this.roleMap) { + for (String roleName : this.roleMap.keySet()) { + + // get the role object + Role role = this.roleMap.get(roleName); + + // create the role element + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + + // add all the privileges + toDomPrivileges(roleElement, role.getPrivilegeMap()); + + // add element to return list + rolesAsElements.add(roleElement); + } + } + + return rolesAsElements; + } + + private void toDomPrivileges(Element roleParentElement, Map privilegeMap) { + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + + for (Privilege privilege : privilegeMap.values()) { + + // create the privilege element + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + + // add the all allowed element + Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.add(allAllowedElement); + + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); + denyValueElement.setText(denyValue); + privilegeElement.add(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setText(allowValue); + privilegeElement.add(allowValueElement); + } + + // add element to parent + roleParentElement.add(privilegeElement); + } + } } diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index 5f7accc50..03c4a60dd 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -36,7 +36,7 @@ public class InitializationHelper { private static final Logger logger = Logger.getLogger(InitializationHelper.class); /** - * Initializes the {@link PrivilegeHandler} from the configuration file + * Initializes the {@link DefaultPrivilegeHandler} from the configuration file * * @param privilegeXmlFile * a {@link File} reference to the XML file containing the configuration for Privilege @@ -65,7 +65,7 @@ public class InitializationHelper { PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); // instantiate privilege handler - PrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); + DefaultPrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); // get policies Element policiesElement = rootElement.element(XmlConstants.XML_POLICIES); @@ -93,7 +93,7 @@ public class InitializationHelper { Map parameterMap = convertToParameterMap(parameterElement); // initialize persistence handler - persistenceHandler.initialize(parameterMap, policyMap); + persistenceHandler.initialize(parameterMap); } catch (Exception e) { logger.error(e, e); @@ -108,7 +108,7 @@ public class InitializationHelper { Map parameterMap = convertToParameterMap(parameterElement); // initialize privilege handler - privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler); + privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { logger.error(e, e); diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index 5b92e4613..73c8727e4 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -14,6 +14,7 @@ import java.io.Serializable; import java.util.Set; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -132,4 +133,46 @@ public class PrivilegeRep implements Serializable { public void setAllowList(Set allowList) { this.allowList = allowList; } + + /** + *

    + * Validates this {@link PrivilegeRep} so that a {@link Privilege} object can later be created from it + *

    + * + * TODO write comment on how validation is done + */ + public void validate() { + + if (this.name == null || this.name.isEmpty()) { + throw new PrivilegeException("No name defined!"); + } + + // if not all allowed, then validate that deny and allow lists are defined + if (this.allAllowed) { + + // all allowed means no policy will be used + if (this.policy != null && !this.policy.isEmpty()) { + throw new PrivilegeException("All is allowed, so Policy may not be set!"); + } + + if (this.denyList != null && !this.denyList.isEmpty()) + throw new PrivilegeException("All is allowed, so deny list must be null"); + if (this.allowList != null && !this.allowList.isEmpty()) + throw new PrivilegeException("All is allowed, so allow list must be null"); + + } else { + + // not all allowed, so policy must be set + if (this.policy == null || this.policy.isEmpty()) { + throw new PrivilegeException("All is not allowed, so Policy must be defined!"); + } + + if (this.denyList == null) { + throw new PrivilegeException("All is not allowed, so deny list must be set, even if empty"); + } + if (this.allowList == null) { + throw new PrivilegeException("All is not allowed, so allow list must be set, even if empty"); + } + } + } } diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index 019f1a408..fde568af3 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -11,7 +11,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; -import java.util.Set; +import java.util.Map; import ch.eitchnet.privilege.model.internal.Role; @@ -27,19 +27,19 @@ public class RoleRep implements Serializable { private static final long serialVersionUID = 1L; private String name; - private Set privileges; + private Map privilegeMap; /** * Default constructor * * @param name * the name of this role - * @param privileges - * the set of privileges granted to this role + * @param privilegeMap + * the map of privileges granted to this role */ - public RoleRep(String name, Set privileges) { + public RoleRep(String name, Map privilegeMap) { this.name = name; - this.privileges = privileges; + this.privilegeMap = privilegeMap; } /** @@ -58,17 +58,17 @@ public class RoleRep implements Serializable { } /** - * @return the privileges + * @return the privilegeMap */ - public Set getPrivileges() { - return this.privileges; + public Map getPrivilegeMap() { + return this.privilegeMap; } /** - * @param privileges - * the privileges to set + * @param privilegeMap + * the privilegeMap to set */ - public void setPrivileges(Set privileges) { - this.privileges = privileges; + public void setPrivileges(Map privilegeMap) { + this.privilegeMap = privilegeMap; } } diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 40056ebab..254df64dd 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -23,8 +23,9 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; /** *

    * {@link Privilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it which - * defines the privileges a logged in user with that role has. For every privilege a {@link PrivilegePolicy} is - * configured which is used to evaluate if privilege is granted to a {@link Restrictable} + * defines the privileges a logged in user with that role has. If the {@link Privilege} has a {@link PrivilegePolicy} + * defined, then that policy will be used for finer granularity and with the deny and allow lists configured which is + * used to evaluate if privilege is granted to a {@link Restrictable} *

    * *

    @@ -52,35 +53,64 @@ public final class Privilege { * @param name * the name of this privilege, which is unique to all privileges known in the {@link PrivilegeHandler} * @param policy - * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted + * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted. If null, then + * privilege is granted * @param allAllowed * a boolean defining if a {@link Role} with this {@link Privilege} has unrestricted access to a - * {@link Restrictable} + * {@link Restrictable} in which case the deny and allow lists are ignored and can be null * @param denyList - * a list of deny rules for this {@link Privilege} + * a list of deny rules for this {@link Privilege}, can be null if all allowed * @param allowList - * a list of allow rules for this {@link Privilege} + * a list of allow rules for this {@link Privilege}, can be null if all allowed */ public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { if (name == null || name.isEmpty()) { throw new PrivilegeException("No name defined!"); } - if (policy == null || policy.isEmpty()) { - throw new PrivilegeException("No policy defined!"); - } - if (denyList == null) { - throw new PrivilegeException("No denyList defined!"); - } - if (allowList == null) { - throw new PrivilegeException("No allowList defined!"); - } - this.name = name; - this.policy = policy; - this.allAllowed = allAllowed; - this.denyList = Collections.unmodifiableSet(denyList); - this.allowList = Collections.unmodifiableSet(allowList); + + // if not all allowed, then validate that deny and allow lists are defined + if (allAllowed) { + + this.allAllowed = true; + + // all allowed means no policy will be used + this.policy = null; + + this.denyList = Collections.emptySet(); + this.allowList = Collections.emptySet(); + } else { + + this.allAllowed = false; + + // not all allowed, so policy must be set + if (policy == null || policy.isEmpty()) { + throw new PrivilegeException("No Policy defined!"); + } + this.policy = policy; + + if (denyList == null) { + throw new PrivilegeException("No denyList defined!"); + } + this.denyList = Collections.unmodifiableSet(denyList); + + if (allowList == null) { + throw new PrivilegeException("No allowList defined!"); + } + this.allowList = Collections.unmodifiableSet(allowList); + } + } + + /** + * Constructs a {@link Privilege} from the {@link PrivilegeRep} + * + * @param privilegeRep + * the {@link PrivilegeRep} from which to create the {@link Privilege} + */ + public Privilege(PrivilegeRep privilegeRep) { + this(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), + privilegeRep.getDenyList()); } /** @@ -136,12 +166,6 @@ public final class Privilege { builder.append(this.name); builder.append(", policy="); builder.append(this.policy); - builder.append(", allAllowed="); - builder.append(this.allAllowed); - builder.append(", denyList="); - builder.append(this.denyList); - builder.append(", allowList="); - builder.append(this.allowList); builder.append("]"); return builder.toString(); } @@ -153,11 +177,7 @@ public final class Privilege { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (this.allAllowed ? 1231 : 1237); - result = prime * result + ((this.allowList == null) ? 0 : this.allowList.hashCode()); - result = prime * result + ((this.denyList == null) ? 0 : this.denyList.hashCode()); result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); - result = prime * result + ((this.policy == null) ? 0 : this.policy.hashCode()); return result; } @@ -173,28 +193,12 @@ public final class Privilege { if (getClass() != obj.getClass()) return false; Privilege other = (Privilege) obj; - if (this.allAllowed != other.allAllowed) - return false; - if (this.allowList == null) { - if (other.allowList != null) - return false; - } else if (!this.allowList.equals(other.allowList)) - return false; - if (this.denyList == null) { - if (other.denyList != null) - return false; - } else if (!this.denyList.equals(other.denyList)) - return false; if (this.name == null) { if (other.name != null) return false; } else if (!this.name.equals(other.name)) return false; - if (this.policy == null) { - if (other.policy != null) - return false; - } else if (!this.policy.equals(other.policy)) - return false; return true; } + } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 6d9205f34..2d6a9e632 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -11,10 +11,11 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.RoleRep; /** @@ -33,27 +34,54 @@ import ch.eitchnet.privilege.model.RoleRep; public final class Role { private final String name; - private final Set privileges; + private final Map privilegeMap; /** * Default constructor * * @param name * the name of the role - * @param privileges - * a set of names of privileges granted to this role + * @param privilegeMap + * a map of {@link Privilege}s granted to this role */ - public Role(String name, Set privileges) { + public Role(String name, Map privilegeMap) { if (name == null || name.isEmpty()) { throw new PrivilegeException("No name defined!"); } - if (privileges == null) { + if (privilegeMap == null) { throw new PrivilegeException("No privileges defined!"); } this.name = name; - this.privileges = Collections.unmodifiableSet(privileges); + this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + } + + /** + * Construct {@link Role} from its representation {@link RoleRep} + * + * @param roleRep + * the representation from which to create the {@link Role} + */ + public Role(RoleRep roleRep) { + + String name = roleRep.getName(); + if (name == null || name.isEmpty()) { + throw new PrivilegeException("No name defined!"); + } + + if (roleRep.getPrivilegeMap() == null) { + throw new PrivilegeException("No privileges defined!"); + } + + // build privileges from reps + Map privilegeMap = new HashMap(roleRep.getPrivilegeMap().size()); + for (String privilegeName : roleRep.getPrivilegeMap().keySet()) { + privilegeMap.put(privilegeName, new Privilege(roleRep.getPrivilegeMap().get(privilegeName))); + } + + this.name = name; + this.privilegeMap = Collections.unmodifiableMap(privilegeMap); } /** @@ -64,12 +92,12 @@ public final class Role { } /** - * Returns the {@link Set} of {@link Privilege} names which is granted to this {@link Role} + * Returns the {@link Map} of {@link Privilege}s which are granted to this {@link Role} * - * @return the {@link Set} of {@link Privilege} names which is granted to this + * @return the {@link Map} of {@link Privilege}s which are granted to this {@link Role} */ - public Set getPrivileges() { - return this.privileges; + public Map getPrivilegeMap() { + return this.privilegeMap; } /** @@ -81,14 +109,18 @@ public final class Role { * @return true if this {@link Role} has the {@link Privilege} with the given name */ public boolean hasPrivilege(String name) { - return this.privileges.contains(name); + return this.privilegeMap.containsKey(name); } /** * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - return new RoleRep(this.name, new HashSet(this.privileges)); + Map privilegeMap = new HashMap(); + for (String privilegeName : this.privilegeMap.keySet()) { + privilegeMap.put(privilegeName, this.privilegeMap.get(privilegeName).asPrivilegeRep()); + } + return new RoleRep(this.name, privilegeMap); } /** @@ -100,7 +132,7 @@ public final class Role { builder.append("Role [name="); builder.append(this.name); builder.append(", privileges="); - builder.append(this.privileges); + builder.append(this.privilegeMap.keySet()); builder.append("]"); return builder.toString(); } @@ -113,7 +145,6 @@ public final class Role { final int prime = 31; int result = 1; result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); - result = prime * result + ((this.privileges == null) ? 0 : this.privileges.hashCode()); return result; } @@ -134,12 +165,6 @@ public final class Role { return false; } else if (!this.name.equals(other.name)) return false; - if (this.privileges == null) { - if (other.privileges != null) - return false; - } else if (!this.privileges.equals(other.privileges)) - return false; return true; } - } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 975a7e7ee..84ee1e2eb 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -213,4 +213,35 @@ public final class User { builder.append("]"); return builder.toString(); } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.userId == null) ? 0 : this.userId.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + User other = (User) obj; + if (this.userId == null) { + if (other.userId != null) + return false; + } else if (!this.userId.equals(other.userId)) + return false; + return true; + } } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index f48579942..908cf2658 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -11,7 +11,9 @@ package ch.eitchnet.privilege.test; import java.io.File; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.ConsoleAppender; @@ -26,16 +28,28 @@ import ch.eitchnet.privilege.helper.InitializationHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; /** - * @author rvonburg + * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more + * tests, especially with deny and allow lists * + * @author rvonburg */ public class PrivilegeTest { + private static final String ADMIN = "admin"; + private static final String PASS_ADMIN = "admin"; + private static final String BOB = "bob"; + private static final String TED = "ted"; + private static final String PASS_BOB = "admin1"; + private static final String ROLE_USER = "user"; + private static final String PASS_BAD = "123"; + private static final Logger logger = Logger.getLogger(PrivilegeTest.class); private static PrivilegeHandler privilegeHandler; @@ -71,7 +85,7 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -82,7 +96,7 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "123"); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_BAD); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -93,7 +107,7 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", null); + Certificate certificate = privilegeHandler.authenticate(ADMIN, null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -102,17 +116,17 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testAddUserBobWithPW() throws Exception { + public void testAddUserBobAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); // let's add a new user bob - UserRep userRep = new UserRep("1", "bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user bob"); + logger.info("Added user " + BOB); // set bob's password - privilegeHandler.setUserPassword(certificate, "bob", "12345678901"); + privilegeHandler.setUserPassword(certificate, BOB, PASS_BOB); logger.info("Set Bob's password"); } @@ -124,8 +138,7 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - - privilegeHandler.authenticate("bob", "12345678901"); + privilegeHandler.authenticate(BOB, PASS_BOB); } /** @@ -134,9 +147,8 @@ public class PrivilegeTest { */ @Test public void testEnableUserBob() throws Exception { - - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); - privilegeHandler.setUserState(certificate, "bob", UserState.ENABLED); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); } /** @@ -148,7 +160,7 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -157,10 +169,23 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testAddUserRoleToBob() throws Exception { + public void testAddRole() throws Exception { + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); - privilegeHandler.addRoleToUser(certificate, "bob", "user"); + Map privilegeMap = new HashMap(); + RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); + + privilegeHandler.addOrReplaceRole(certificate, roleRep); + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testAddRoleToBob() throws Exception { + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); } /** @@ -169,8 +194,7 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - - privilegeHandler.authenticate("bob", "12345678901"); + privilegeHandler.authenticate(BOB, PASS_BOB); } /** @@ -182,13 +206,14 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAddUserTedAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); + // auth as Bog + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - // let's add a new user bob - UserRep userRep = new UserRep("1", "bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + // let's add a new user Ted + UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user bob"); + logger.info("Added user " + TED); } /** @@ -198,8 +223,9 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); - privilegeHandler.addRoleToUser(certificate, "bob", PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); } /** @@ -209,13 +235,13 @@ public class PrivilegeTest { @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted - UserRep userRep = new UserRep("2", "ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user bob"); + logger.info("Added user " + TED); } /** @@ -225,12 +251,12 @@ public class PrivilegeTest { @Test public void testPerformRestrictable() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); boolean actionAllowed = privilegeHandler.actionAllowed(certificate, restrictable); - org.junit.Assert.assertTrue("eitch may not perform restrictable!", actionAllowed); + org.junit.Assert.assertTrue(ADMIN + " may not perform restrictable!", actionAllowed); } } diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/test/ch/eitchnet/privilege/test/TestRestrictable.java index e9badc734..ee8921722 100644 --- a/test/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/test/ch/eitchnet/privilege/test/TestRestrictable.java @@ -23,7 +23,7 @@ public class TestRestrictable implements Restrictable { */ @Override public String getPrivilegeName() { - return "Service"; + return "com.rsp.core.base.service.Service"; } /** From 6191949c3e36d29a8fd56ddef780963dc22b7c03 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 30 Jul 2011 14:10:47 +0000 Subject: [PATCH 058/457] [Minor] logging of authentication attempts --- .../privilege/handler/DefaultPrivilegeHandler.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index f92df13c7..bde9ab4fc 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -572,7 +572,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { CertificateSessionPair certificateSessionPair = this.sessionMap.remove(certificate.getSessionId()); // return true if object was really removed - return certificateSessionPair != null; + boolean loggedOut = certificateSessionPair != null; + if (loggedOut) + logger.info("User " + certificate.getUsername() + " logged out."); + else + logger.warn("User already logged out!"); + return loggedOut; } /** From e8a7fb5cf91964e085a58897282853690d738769 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 30 Jul 2011 17:34:53 +0000 Subject: [PATCH 059/457] [Interface] changed all methods which return boolean on action allowed to be void as they must throw exceptions if the privilege is not granted --- .../handler/DefaultPrivilegeHandler.java | 52 ++++++------- .../privilege/handler/PrivilegeHandler.java | 15 ++-- .../privilege/policy/DefaultPrivilege.java | 22 +++--- .../privilege/policy/PrivilegePolicy.java | 6 +- .../privilege/test/PrivilegeTest.java | 77 +++++++++++++++++-- 5 files changed, 119 insertions(+), 53 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index bde9ab4fc..2dd4be781 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -563,10 +563,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public boolean invalidateSession(Certificate certificate) { // first validate certificate - if (!isCertificateValid(certificate)) { - logger.info("Certificate is not valid, so no session to invalidate: " + certificate.toString()); - return false; - } + isCertificateValid(certificate); // remove registration CertificateSessionPair certificateSessionPair = this.sessionMap.remove(certificate.getSessionId()); @@ -592,15 +589,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + public void actionAllowed(Certificate certificate, Restrictable restrictable) { // TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? // first validate certificate - if (!isCertificateValid(certificate)) { - throw new PrivilegeException("Certificate is not valid, so action is not allowed: " + certificate - + " for restrictable: " + restrictable); - } + isCertificateValid(certificate); // restrictable must not be null if (restrictable == null) @@ -613,9 +607,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { "Oh boy, how did this happen: No User in user map although the certificate is valid!"); } - // default is to not allow the action - // TODO should default deny/allow policy be configurable? - boolean actionAllowed = false; + String privilegeName = restrictable.getPrivilegeName(); // now iterate roles and validate on policies for (String roleName : user.getRoles()) { @@ -626,14 +618,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { continue; } - actionAllowed = actionAllowed(role, restrictable); + // ignore if this role does not have the privilege + if (!role.hasPrivilege(privilegeName)) + continue; // if action is allowed, then break iteration as a privilege match has been made - if (actionAllowed) - break; + if (actionAllowed(role, restrictable)) + return; } - return actionAllowed; + throw new AccessDeniedException("User " + user.getUsername() + " does not have Privilege " + privilegeName + + " needed for Restrictable " + restrictable.getClass().getName()); } /** @@ -644,7 +639,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(RoleRep roleRep, Restrictable restrictable) { + public void actionAllowed(RoleRep roleRep, Restrictable restrictable) { // user and restrictable must not be null if (roleRep == null) @@ -660,7 +655,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("No Role exists with the name " + roleRep.getName()); } - return actionAllowed(role, restrictable); + // delegate to method with Role + actionAllowed(role, restrictable); } /** @@ -669,8 +665,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @param role * @param restrictable - * - * @return true if the privilege is granted, false otherwise */ protected boolean actionAllowed(Role role, Restrictable restrictable) { @@ -691,8 +685,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Privilege privilege = role.getPrivilegeMap().get(privilegeName); // check if all is allowed - if (privilege.isAllAllowed()) + if (privilege.isAllAllowed()) { return true; + } // otherwise delegate checking to the policy configured for this privilege PrivilegePolicy policy = this.getPolicy(privilege.getPolicy()); @@ -702,14 +697,18 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // delegate checking to privilege policy - return policy.actionAllowed(role, privilege, restrictable); + policy.actionAllowed(role, privilege, restrictable); + + // the policy must throw an exception if action not allowed, + // so return true if we arrive here + return true; } /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) */ @Override - public boolean isCertificateValid(Certificate certificate) { + public void isCertificateValid(Certificate certificate) { // certificate must not be null if (certificate == null) @@ -741,8 +740,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { "Oh boy, how did this happen: No User in user map although the certificate is valid!"); } - // everything is ok, so return true as the certificate must be valid - return true; + // everything is ok } /** @@ -752,9 +750,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { // validate certificate - if (!this.isCertificateValid(certificate)) { - throw new PrivilegeException("Certificate " + certificate + " is not valid!"); - } + this.isCertificateValid(certificate); // get user object User user = this.persistenceHandler.getUser(certificate.getUsername()); diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 49608c42d..997d35ec6 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -326,15 +326,13 @@ public interface PrivilegeHandler { * @param restrictable * the {@link Restrictable} to which the user wants access * - * @return true if the access is allowed, false otherwise - * * @throws AccessDeniedException * if the user for this certificate may not perform the action defined by the {@link Restrictable} * implementation * @throws PrivilegeException * if there is anything wrong with this certificate */ - public boolean actionAllowed(Certificate certificate, Restrictable restrictable) throws AccessDeniedException, + public void actionAllowed(Certificate certificate, Restrictable restrictable) throws AccessDeniedException, PrivilegeException; /** @@ -345,12 +343,13 @@ public interface PrivilegeHandler { * @param restrictable * the {@link Restrictable} to which access is to be checked * - * @return true if the {@link RoleRep} has access, false otherwise - * + * @throws PrivilegeException + * if the {@link Role} does not exist * @throws AccessDeniedException * if the role may not perform the action defined by the {@link Restrictable} implementation */ - public boolean actionAllowed(RoleRep roleRep, Restrictable restrictable) throws AccessDeniedException; + public void actionAllowed(RoleRep roleRep, Restrictable restrictable) throws PrivilegeException, + AccessDeniedException; /** * Checks if the given {@link Certificate} is valid. This means that the certificate is for a valid session and that @@ -359,12 +358,10 @@ public interface PrivilegeHandler { * @param certificate * the {@link Certificate} to check * - * @return true if the {@link Certificate} is valid, false otherwise - * * @throws PrivilegeException * if there is anything wrong with this certificate */ - public boolean isCertificateValid(Certificate certificate) throws PrivilegeException; + public void isCertificateValid(Certificate certificate) throws PrivilegeException; /** *

    diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 1eecb1e26..b455ef14b 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.policy; +import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; @@ -27,7 +28,7 @@ public class DefaultPrivilege implements PrivilegePolicy { * ch.eitchnet.privilege.model.internal.Privilege, ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable) { + public void actionAllowed(Role role, Privilege privilege, Restrictable restrictable) { // validate user is not null if (role == null) @@ -39,10 +40,6 @@ public class DefaultPrivilege implements PrivilegePolicy { throw new PrivilegeException("The PrivilegeName for the Restrictable is null or empty: " + restrictable); } - // does this role have privilege for any values? - if (privilege.isAllAllowed()) - return true; - // get the value on which the action is to be performed Object object = restrictable.getPrivilegeValue(); @@ -56,17 +53,24 @@ public class DefaultPrivilege implements PrivilegePolicy { // first check values not allowed for (String denied : privilege.getDenyList()) { - if (denied.equals(privilegeValue)) - return false; + + // if value in deny list + if (denied.equals(privilegeValue)) { + + // then throw access denied + throw new AccessDeniedException("Role " + role.getName() + " does not have Privilege " + privilegeName + + " needed for Restrictable " + restrictable.getClass().getName()); + } } // now check values allowed for (String allowed : privilege.getAllowList()) { if (allowed.equals(privilegeValue)) - return true; + return; } // default is not allowed - return false; + throw new AccessDeniedException("Role " + role.getName() + " does not have Privilege " + privilegeName + + " needed for Restrictable " + restrictable.getClass().getName()); } } diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 865d373cd..63807fe1c 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.policy; +import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -40,7 +41,8 @@ public interface PrivilegePolicy { * @param restrictable * the {@link Restrictable} to which the user wants access * - * @return return true if the action is allowed, false if not + * @throws AccessDeniedException + * if action not allowed */ - public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable); + public void actionAllowed(Role role, Privilege privilege, Restrictable restrictable) throws AccessDeniedException; } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 908cf2658..1a375e233 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -47,6 +47,7 @@ public class PrivilegeTest { private static final String BOB = "bob"; private static final String TED = "ted"; private static final String PASS_BOB = "admin1"; + private static final String ROLE_FEATHERLITE_USER = "FeatherliteUser"; private static final String ROLE_USER = "user"; private static final String PASS_BAD = "123"; @@ -87,6 +88,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + privilegeHandler.invalidateSession(certificate); } /** @@ -98,6 +100,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_BAD); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + privilegeHandler.invalidateSession(certificate); } /** @@ -109,6 +112,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + privilegeHandler.invalidateSession(certificate); } /** @@ -128,6 +132,7 @@ public class PrivilegeTest { // set bob's password privilegeHandler.setUserPassword(certificate, BOB, PASS_BOB); logger.info("Set Bob's password"); + privilegeHandler.invalidateSession(certificate); } /** @@ -138,7 +143,8 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + privilegeHandler.invalidateSession(certificate); } /** @@ -149,6 +155,7 @@ public class PrivilegeTest { public void testEnableUserBob() throws Exception { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); + privilegeHandler.invalidateSession(certificate); } /** @@ -162,6 +169,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + privilegeHandler.invalidateSession(certificate); } /** @@ -176,6 +184,7 @@ public class PrivilegeTest { RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.invalidateSession(certificate); } /** @@ -186,6 +195,7 @@ public class PrivilegeTest { public void testAddRoleToBob() throws Exception { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); + privilegeHandler.invalidateSession(certificate); } /** @@ -194,7 +204,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + privilegeHandler.invalidateSession(certificate); } /** @@ -214,6 +225,7 @@ public class PrivilegeTest { UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); + privilegeHandler.invalidateSession(certificate); } /** @@ -226,6 +238,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); + privilegeHandler.invalidateSession(certificate); } /** @@ -242,6 +255,7 @@ public class PrivilegeTest { UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); + privilegeHandler.invalidateSession(certificate); } /** @@ -249,14 +263,67 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testPerformRestrictable() throws Exception { + public void testPerformRestrictableAsAdmin() throws Exception { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); - boolean actionAllowed = privilegeHandler.actionAllowed(certificate, restrictable); - org.junit.Assert.assertTrue(ADMIN + " may not perform restrictable!", actionAllowed); + privilegeHandler.actionAllowed(certificate, restrictable); + privilegeHandler.invalidateSession(certificate); + } + + /** + * Tests if the user bob, who does not have FeatherliteUser role can perform restrictable + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = AccessDeniedException.class) + public void testFailPerformRestrictableAsBob() throws Exception { + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + try { + privilegeHandler.actionAllowed(certificate, restrictable); + } finally { + privilegeHandler.invalidateSession(certificate); + } + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testAddFeatherliteRoleToBob() throws Exception { + + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_FEATHERLITE_USER); + logger.info("Added " + ROLE_FEATHERLITE_USER + " to " + BOB); + privilegeHandler.invalidateSession(certificate); + } + + /** + * Tests if the user bob, who now has FeatherliteUser role can perform restrictable + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testPerformRestrictableAsBob() throws Exception { + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + try { + privilegeHandler.actionAllowed(certificate, restrictable); + } finally { + privilegeHandler.invalidateSession(certificate); + } } } From 4c455c130aa6164893daeadb1fed479f726f7119 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 31 Jul 2011 15:18:29 +0000 Subject: [PATCH 060/457] --- config/PrivilegeModel.xml | 3 --- src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 493476d57..c5d531266 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -27,9 +27,6 @@ true - - true - diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2dd4be781..800131392 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -857,7 +857,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @author rvonburg */ - private class CertificateSessionPair { + protected class CertificateSessionPair { /** * The {@link Session} */ From bef87b1a184deae571f0d0ea3be038aa8183bf47 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 7 Aug 2011 10:14:40 +0000 Subject: [PATCH 061/457] [Minor] code comments cleanup, added LGPL reference and copyright notice --- COPYING | 674 ++++++++++++++++++ COPYING.LESSER | 165 +++++ build.xml | 25 + config/Privilege.xml | 23 + config/PrivilegeModel.xml | 22 + .../handler/DefaultEncryptionHandler.java | 29 +- .../handler/DefaultPrivilegeHandler.java | 27 +- .../privilege/handler/EncryptionHandler.java | 25 +- .../privilege/handler/PersistenceHandler.java | 25 +- .../privilege/handler/PrivilegeHandler.java | 25 +- .../handler/XmlPersistenceHandler.java | 157 ++-- .../helper/BootstrapConfigurationHelper.java | 25 +- .../privilege/helper/ClassHelper.java | 25 +- .../eitchnet/privilege/helper/HashHelper.java | 25 +- .../helper/InitializationHelper.java | 25 +- .../privilege/helper/PasswordCreaterUI.java | 27 +- .../privilege/helper/PasswordCreator.java | 25 +- .../privilege/helper/XmlConstants.java | 25 +- .../eitchnet/privilege/helper/XmlHelper.java | 25 +- .../privilege/i18n/AccessDeniedException.java | 25 +- .../privilege/i18n/PrivilegeException.java | 25 +- .../eitchnet/privilege/model/Certificate.java | 33 +- .../privilege/model/PrivilegeRep.java | 25 +- .../privilege/model/Restrictable.java | 25 +- src/ch/eitchnet/privilege/model/RoleRep.java | 25 +- src/ch/eitchnet/privilege/model/UserRep.java | 25 +- .../eitchnet/privilege/model/UserState.java | 25 +- .../privilege/model/internal/Privilege.java | 25 +- .../privilege/model/internal/Role.java | 25 +- .../privilege/model/internal/Session.java | 25 +- .../privilege/model/internal/User.java | 25 +- .../privilege/policy/DefaultPrivilege.java | 25 +- .../privilege/policy/PrivilegePolicy.java | 25 +- .../privilege/test/PrivilegeTest.java | 25 +- .../privilege/test/TestRestrictable.java | 25 +- 35 files changed, 1588 insertions(+), 219 deletions(-) create mode 100644 COPYING create mode 100644 COPYING.LESSER diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/build.xml b/build.xml index 8063f0c42..64bb5f254 100644 --- a/build.xml +++ b/build.xml @@ -1,4 +1,28 @@ + + + @@ -34,6 +58,7 @@ + diff --git a/config/Privilege.xml b/config/Privilege.xml index 0913623cb..2cfaf41e1 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -1,4 +1,27 @@ + + diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index c5d531266..a9c40c5fa 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -1,4 +1,26 @@ + diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 976f62463..10d02c49e 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -24,8 +39,8 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; /** *

    - * This default {@link EncryptionHandler} creates tokens using a {@link SecureRandom} object. Hashing is done by - * using {@link MessageDigest} and the configured algorithm which is passed in the parameters + * This default {@link EncryptionHandler} creates tokens using a {@link SecureRandom} object. Hashing is done by using + * {@link MessageDigest} and the configured algorithm which is passed in the parameters *

    * * Required parameters: @@ -33,7 +48,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; *
  • {@link XmlConstants#XML_PARAM_HASH_ALGORITHM}
  • * * - * @author rvonburg + * @author Robert von Burg * */ public class DefaultEncryptionHandler implements EncryptionHandler { diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 800131392..07b38e558 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -50,7 +65,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; *
  • Password requirements are simple: Non null and non empty/length 0
  • * * - * @author rvonburg + * @author Robert von Burg * */ public class DefaultPrivilegeHandler implements PrivilegeHandler { @@ -855,7 +870,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * An internal class used to keep a record of sessions with the certificate * - * @author rvonburg + * @author Robert von Burg */ protected class CertificateSessionPair { /** diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index c4f6b34b9..50d758173 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -16,7 +31,7 @@ import java.util.Map; * The {@link EncryptionHandler} exposes API which is used to handle encrypting of strings, or returning secure tokens * for certificates and so forth * - * @author rvonburg + * @author Robert von Burg * */ public interface EncryptionHandler { diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 65026f2f0..29ad14fac 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -31,7 +46,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * and {@link Privilege} *

    * - * @author rvonburg + * @author Robert von Burg * */ public interface PersistenceHandler { diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 997d35ec6..2af410f25 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -30,7 +45,7 @@ import ch.eitchnet.privilege.model.internal.User; * methods to access Privilege data model objects, modify them and validate if users or roles have privileges to perform * an action * - * @author rvonburg + * @author Robert von Burg * */ public interface PrivilegeHandler { diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 8d681976f..7fb74dd70 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -37,7 +52,7 @@ import ch.eitchnet.privilege.model.internal.User; * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed * in {@link #initialize(Map)} * - * @author rvonburg + * @author Robert von Burg */ public class XmlPersistenceHandler implements PersistenceHandler { @@ -155,7 +170,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // USERS // build XML DOM of users - List users = toDomUsers(); + List users = toDomUsers(this.userMap); Element usersElement = docFactory.createElement(XmlConstants.XML_USERS); for (Element userElement : users) { usersElement.add(userElement); @@ -164,7 +179,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // ROLES // build XML DOM of roles - List roles = toDomRoles(); + List roles = toDomRoles(this.roleMap); Element rolesElement = docFactory.createElement(XmlConstants.XML_ROLES); for (Element roleElement : roles) { rolesElement.add(roleElement); @@ -211,13 +226,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { // get roles element Element rolesElement = modelsRootElement.element(XmlConstants.XML_ROLES); // read roles - readRoles(rolesElement); + Map roles = readRoles(rolesElement); + this.roleMap = roles; // USERS // get users element Element usersElement = modelsRootElement.element(XmlConstants.XML_USERS); // read users - readUsers(usersElement); + Map users = readUsers(usersElement); + this.userMap = users; this.userMapDirty = false; this.roleMapDirty = false; @@ -274,7 +291,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { /** * @param usersRootElement */ - private void readUsers(Element usersRootElement) { + private static Map readUsers(Element usersRootElement) { + + Map userMap = new HashMap(); @SuppressWarnings("unchecked") List userElements = usersRootElement.elements(XmlConstants.XML_USER); @@ -313,14 +332,18 @@ public class XmlPersistenceHandler implements PersistenceHandler { logger.info("Added user " + user); // put user in map - this.userMap.put(username, user); + userMap.put(username, user); } + + return userMap; } /** * @param rolesRootElement */ - private void readRoles(Element rolesRootElement) { + private static Map readRoles(Element rolesRootElement) { + + Map roleMap = new HashMap(); @SuppressWarnings("unchecked") List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); @@ -331,64 +354,23 @@ public class XmlPersistenceHandler implements PersistenceHandler { Map privilegeMap = readPrivileges(roleElement); Role role = new Role(roleName, privilegeMap); - this.roleMap.put(roleName, role); - } - } - - /** - * @param roleParentElement - * @return - */ - private Map readPrivileges(Element roleParentElement) { - - Map privilegeMap = new HashMap(); - - @SuppressWarnings("unchecked") - List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); - for (Element privilegeElement : privilegeElements) { - - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - - String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); - boolean allAllowed = Boolean.valueOf(allAllowedS).booleanValue(); - - @SuppressWarnings("unchecked") - List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - Set denyList = new HashSet(denyElements.size()); - for (Element denyElement : denyElements) { - String denyValue = denyElement.getTextTrim(); - if (!denyValue.isEmpty()) - denyList.add(denyValue); - } - - @SuppressWarnings("unchecked") - List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - Set allowList = new HashSet(allowElements.size()); - for (Element allowElement : allowElements) { - String allowValue = allowElement.getTextTrim(); - if (!allowValue.isEmpty()) - allowList.add(allowValue); - } - - Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); + roleMap.put(roleName, role); } - return privilegeMap; + return roleMap; } - private List toDomUsers() { + private static List toDomUsers(Map userMap) { - List usersAsElements = new ArrayList(this.userMap.size()); + List usersAsElements = new ArrayList(userMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - synchronized (this.userMap) { - for (String userName : this.userMap.keySet()) { + synchronized (userMap) { + for (String userName : userMap.keySet()) { // get the user object - User user = this.userMap.get(userName); + User user = userMap.get(userName); // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); @@ -433,17 +415,17 @@ public class XmlPersistenceHandler implements PersistenceHandler { return usersAsElements; } - private List toDomRoles() { + private static List toDomRoles(Map roleMap) { - List rolesAsElements = new ArrayList(this.roleMap.size()); + List rolesAsElements = new ArrayList(roleMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - synchronized (this.roleMap) { - for (String roleName : this.roleMap.keySet()) { + synchronized (roleMap) { + for (String roleName : roleMap.keySet()) { // get the role object - Role role = this.roleMap.get(roleName); + Role role = roleMap.get(roleName); // create the role element Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); @@ -460,7 +442,50 @@ public class XmlPersistenceHandler implements PersistenceHandler { return rolesAsElements; } - private void toDomPrivileges(Element roleParentElement, Map privilegeMap) { + /** + * @param roleParentElement + * @return + */ + private static Map readPrivileges(Element roleParentElement) { + + Map privilegeMap = new HashMap(); + + @SuppressWarnings("unchecked") + List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); + + String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); + boolean allAllowed = Boolean.valueOf(allAllowedS).booleanValue(); + + @SuppressWarnings("unchecked") + List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); + Set denyList = new HashSet(denyElements.size()); + for (Element denyElement : denyElements) { + String denyValue = denyElement.getTextTrim(); + if (!denyValue.isEmpty()) + denyList.add(denyValue); + } + + @SuppressWarnings("unchecked") + List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); + Set allowList = new HashSet(allowElements.size()); + for (Element allowElement : allowElements) { + String allowValue = allowElement.getTextTrim(); + if (!allowValue.isEmpty()) + allowList.add(allowValue); + } + + Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); + privilegeMap.put(privilegeName, privilege); + } + + return privilegeMap; + } + + private static void toDomPrivileges(Element roleParentElement, Map privilegeMap) { DocumentFactory documentFactory = DocumentFactory.getInstance(); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index b25485d79..386ce8476 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -38,7 +53,7 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; * Note:This class is not yet implemented! *

    * - * @author rvonburg + * @author Robert von Burg */ public class BootstrapConfigurationHelper { diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java index eda6f8eea..7a741681f 100644 --- a/src/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -15,7 +30,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; /** * The {@link ClassHelper} class is a helper to instantiate classes using reflection * - * @author rvonburg + * @author Robert von Burg */ public class ClassHelper { diff --git a/src/ch/eitchnet/privilege/helper/HashHelper.java b/src/ch/eitchnet/privilege/helper/HashHelper.java index 47ed49dc1..628ac518a 100644 --- a/src/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/ch/eitchnet/privilege/helper/HashHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -17,7 +32,7 @@ import java.security.NoSuchAlgorithmException; /** * Helper class to hash a String for a certain hash algorithm, using the Java {@link MessageDigest} classes * - * @author rvonburg + * @author Robert von Burg */ public class HashHelper { diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index 03c4a60dd..f1b1c57e6 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -29,7 +44,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the * configuration * - * @author rvonburg + * @author Robert von Burg */ public class InitializationHelper { diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index 81690984a..ad6aa9546 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -1,17 +1,28 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten - * - * All rights reserved. + * Robert von Burg * */ -/** +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ + package ch.eitchnet.privilege.helper; import java.awt.Dimension; @@ -33,7 +44,7 @@ import javax.swing.SwingConstants; /** * Simple Swing UI to create passwords * - * @author rvonburg + * @author Robert von Burg */ public class PasswordCreaterUI { diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java index a70dda885..f57a5615e 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -23,7 +38,7 @@ import java.security.MessageDigest; * TODO: Note: currently the password input is echoed which is a security risk. This is a TODO *

    * - * @author rvonburg + * @author Robert von Burg */ public class PasswordCreator { diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 26bfb757b..54e3f4042 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -13,7 +28,7 @@ package ch.eitchnet.privilege.helper; /** * The constants used in parsing XML documents which contain the configuration for Privilege * - * @author rvonburg + * @author Robert von Burg */ public class XmlConstants { diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index c77dcec08..50433c212 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -32,7 +47,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; /** * Helper class for performing XML based tasks using Dom4J * - * @author rvonburg + * @author Robert von Burg */ public class XmlHelper { diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java index 339bd6fed..5e9029717 100644 --- a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -13,7 +28,7 @@ package ch.eitchnet.privilege.i18n; /** * Exception thrown if access is denied during login, or if a certain privilege is not granted * - * @author rvonburg + * @author Robert von Burg */ public class AccessDeniedException extends PrivilegeException { diff --git a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java index decd696a9..db281372a 100644 --- a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -13,7 +28,7 @@ package ch.eitchnet.privilege.i18n; /** * Main {@link RuntimeException} thrown if something goes wrong in Privilege * - * @author rvonburg + * @author Robert von Burg */ public class PrivilegeException extends RuntimeException { diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index 7dfe3b6a9..7ae4f5f56 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -22,7 +37,7 @@ import ch.eitchnet.privilege.model.internal.Session; * instance which is always used when performing an access and is returned when a user performs a login through * {@link PrivilegeHandler#authenticate(String, String)} * - * @author rvonburg + * @author Robert von Burg */ public final class Certificate implements Serializable { @@ -38,7 +53,10 @@ public final class Certificate implements Serializable { /** * Default constructor initializing with all information needed for this certificate * - *

    Note, both the authentication token and password are private fields which are generated on login and only known by the {@link PrivilegeHandler}

    + *

    + * Note, both the authentication token and password are private fields which are generated on login and only known + * by the {@link PrivilegeHandler} + *

    * * @param sessionId * the users session id @@ -49,7 +67,8 @@ public final class Certificate implements Serializable { * It corresponds with the authentication token on the {@link Session} * @param authPassword * the password to access the authentication token, this is not known to the client but set by the - * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the {@link Session} + * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the + * {@link Session} * @param locale * the users {@link Locale} */ diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index 73c8727e4..6d39f685e 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -24,7 +39,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * edited by users with the correct privilege, this representational version is allowed to be viewed by remote clients * and simply wraps all public data from the {@link Privilege} * - * @author rvonburg + * @author Robert von Burg */ public class PrivilegeRep implements Serializable { diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index 9122d52c5..d3d401a61 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -21,7 +36,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * for evaluating access *

    * - * @author rvonburg + * @author Robert von Burg * */ public interface Restrictable { diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index fde568af3..4af09ca17 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -20,7 +35,7 @@ import ch.eitchnet.privilege.model.internal.Role; * by users with the correct privilege, this representational version is allowed to be viewed by remote clients and * simply wraps all public data from the {@link Role} * - * @author rvonburg + * @author Robert von Burg */ public class RoleRep implements Serializable { diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index 7efbb8ab9..491d42fda 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -22,7 +37,7 @@ import ch.eitchnet.privilege.model.internal.User; * by users with the correct privilege, this representational version is allowed to be viewed by remote clients and * simply wraps all public data from the {@link User} * - * @author rvonburg + * @author Robert von Burg */ public class UserRep implements Serializable { diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java index ab1fbde5e..d98b9df14 100644 --- a/src/ch/eitchnet/privilege/model/UserState.java +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -22,7 +37,7 @@ import ch.eitchnet.privilege.model.internal.User; *
  • EXPIRED - the user has automatically expired through a predefined time
  • * * - * @author rvonburg + * @author Robert von Burg * */ public enum UserState { diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 254df64dd..96fa4b418 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -37,7 +52,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * for that *

    * - * @author rvonburg + * @author Robert von Burg */ public final class Privilege { diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 2d6a9e632..aa76e6bd6 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -29,7 +44,7 @@ import ch.eitchnet.privilege.model.RoleRep; * that *

    * - * @author rvonburg + * @author Robert von Burg */ public final class Role { diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index 7e0df5272..2210c8fc3 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -26,7 +41,7 @@ import ch.eitchnet.privilege.model.Certificate; * {@link Certificate} which is used for accessing privileges *

    * - * @author rvonburg + * @author Robert von Burg */ public final class Session { diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 84ee1e2eb..e72908652 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -28,7 +43,7 @@ import ch.eitchnet.privilege.model.UserState; * that *

    * - * @author rvonburg + * @author Robert von Burg * */ public final class User { diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java index b455ef14b..bd67adf0e 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -19,7 +34,7 @@ import ch.eitchnet.privilege.model.internal.Role; /** * XXX re-think this implementation... * - * @author rvonburg + * @author Robert von Burg */ public class DefaultPrivilege implements PrivilegePolicy { diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 63807fe1c..9c0d14bb0 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -27,7 +42,7 @@ import ch.eitchnet.privilege.model.internal.User; * need one with out the {@link Privilege} in its signature? *

    * - * @author rvonburg + * @author Robert von Burg */ public interface PrivilegePolicy { diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 1a375e233..72370488f 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -38,7 +53,7 @@ import ch.eitchnet.privilege.model.UserState; * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more * tests, especially with deny and allow lists * - * @author rvonburg + * @author Robert von Burg */ public class PrivilegeTest { diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/test/ch/eitchnet/privilege/test/TestRestrictable.java index ee8921722..e384e89df 100644 --- a/test/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/test/ch/eitchnet/privilege/test/TestRestrictable.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -13,7 +28,7 @@ package ch.eitchnet.privilege.test; import ch.eitchnet.privilege.model.Restrictable; /** - * @author rvonburg + * @author Robert von Burg * */ public class TestRestrictable implements Restrictable { From c594d373d40df1f5ae572041bee761ad4fc6c92a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 7 Aug 2011 13:48:56 +0200 Subject: [PATCH 062/457] [New] added the README file with a general overview, it is not finished, but has the basics --- README | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 000000000..c4c3ef642 --- /dev/null +++ b/README @@ -0,0 +1,103 @@ + +Privilege README file + +Overview +################################ + +Privilege is a light weight library to secure access or grant privileges to +users in an application. Privilege allows a developer to secure the application +in different levels of the application providing API's for different +contexts. + +Privilege is implemented in the Java language and is light weight in that it has +no external dependencies other than a Java runtime environment version 6. Since +the JRE 6 has an LDAP implementation it is possible to store Privilege data in +a LDAP repository with only the Privilege JAR. + +Privilege is distributed under the GNU Lesser General Public License on +Github.com and can be downloaded at + + https://github.com/eitch/Privilege + +The main developer is Robert von Burg who also maintains the +Github repository. He is available for all questions regarding Privilege + +Motivation +################################ +In some cases a developer might want to restrict access to an application +depending on the role which an authenticated user has. In other cases the +developer would need a more finely grained control by restricting access to a +certain object, or a certain method call. + +We were looking for an API which would allows us to restrict access to a given +object in different ways. For instance it was our intention to not simply +restrict access to a specific object type, or instance, but to restrict access +to an instance of the object if it had fields set to a specific value. + +Evaluations on existing libraries which implement access restriction did not +provide an API which suited our needs or which were not easily implemented, thus +leading to the design of Privilege. + +Design Goals +################################ +When a developer needs to implement access restriction an application there are +different questions which the developer will ask: +- Does the user have a specific role? +- Does the user have a specific privilege i.e. is the user allowed to perform a +specific action? +- Is a user allowed to access a specific type of object? +- Is a user allowed to access a specific instance of a type? +- Is a user allowed to access a field on a specific object? + +Privilege's design goals are to allow the developer to answer these questions +with an API which does not mean implementing a lot of additional project +specific code. + +Further in Privilege it should be possible to perform the normal CRUD functions: +- Create users, roles, privileges, etc. +- Read existing users, roles, privileges, etc. +- Update users, roles, privileges, etc. +- Delete users, roles, privileges, etc. + +It should be possible to store Privilege's data in different databases, +depending on the application. For example it should be able to store the data in +XML files, in a LDAP directory and so forth. + +Documentation +################################ + +The current documentation, though a bit outdated, can be found in the docs/ +directory of the Repository + +Compiling +################################ + +Since Privilege is a Java library, it is built using Apache Ant. The build.xml +file is configured to build Privilege directly from the root directory by simply +calling ant at the command line: + +$ ant +Buildfile: /data/src/apixxo_WS/Privilege/build.xml + +dist: + [mkdir] Created dir: /data/src/apixxo_WS/Privilege/dist + [copy] Copying 28 files to /data/src/apixxo_WS/Privilege/bin + [jar] Building jar: /data/src/apixxo_WS/Privilege/dist/Privilege.jar + +BUILD SUCCESSFUL +Total time: 0 seconds + +Using +################################ + +To use Privilege see the ch.eitchnet.privilege.test.PrivilegeTest.java class +which contains a few test cases including showing how to load Privilege. + +This documentation is still in need of more work, but for any questions please +don't hesitate to write an e-mail to the developer and we'll find a solution. + + Switzerland, the 7. August 2011 + Robert von Burg + + + From 8967f78113c086e3c41754effb2b67e48e34c8ba Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 7 Aug 2011 13:52:09 +0200 Subject: [PATCH 063/457] [Minor] Added the build directory dist to the ignore list --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e493dd6ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +dist From bb515756cb6ff767aa1a1644da1c150b42470ebb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 7 Aug 2011 16:00:38 +0200 Subject: [PATCH 064/457] [New] Changed Privilege Users to have a map of properties with which meta data of a user can be stored. This can be used for validationg privileges on user specific data --- config/PrivilegeModel.xml | 4 + .../handler/DefaultPrivilegeHandler.java | 16 ++-- .../handler/XmlPersistenceHandler.java | 96 +++++++++++++++++-- .../helper/InitializationHelper.java | 9 +- .../privilege/helper/XmlConstants.java | 10 ++ src/ch/eitchnet/privilege/model/UserRep.java | 49 +++++++++- .../privilege/model/internal/User.java | 42 +++++++- .../privilege/test/PrivilegeTest.java | 9 +- 8 files changed, 208 insertions(+), 27 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index a9c40c5fa..e43216141 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -34,6 +34,10 @@ along with Privilege. If not, see . PrivilegeAdmin FeatherliteUser + + + + diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 07b38e558..5068b6ed0 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -203,8 +203,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user + // XXX should the collections be recreated and the getRoles() and getProperties() methods be removed? User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), - userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale()); + userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), + userRep.getProperties()); // delegate to persistence handler this.persistenceHandler.addOrReplaceUser(user); @@ -280,7 +282,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { newRoles.add(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), newRoles, user.getLocale()); + user.getSurname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -367,7 +369,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), newRoles, user.getLocale()); + user.getSurname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -413,7 +415,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), locale); + user.getSurname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -437,7 +439,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, - user.getUserState(), user.getRoles(), user.getLocale()); + user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -471,7 +473,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale()); + user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -495,7 +497,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), state, user.getRoles(), user.getLocale()); + user.getSurname(), state, user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 7fb74dd70..b5a962f4d 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -56,7 +56,7 @@ import ch.eitchnet.privilege.model.internal.User; */ public class XmlPersistenceHandler implements PersistenceHandler { - private static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); + protected static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); private Map userMap; private Map roleMap; @@ -289,9 +289,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** + * Parses {@link User} objects from their XML representations + * * @param usersRootElement + * the element containing suer elements + * + * @return the map of converted {@link User} objects */ - private static Map readUsers(Element usersRootElement) { + protected static Map readUsers(Element usersRootElement) { Map userMap = new HashMap(); @@ -313,6 +318,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); Locale locale = new Locale(localeName); + // read roles Element rolesElement = userElement.element(XmlConstants.XML_ROLES); @SuppressWarnings("unchecked") List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); @@ -326,9 +332,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } + // read properties + Element propertiesElement = userElement.element(XmlConstants.XML_PROPERTIES); + Map propertyMap = convertToPropertyMap(propertiesElement); + // create user - User user = new User(userId, username, password, firstname, surname, userState, - Collections.unmodifiableSet(roles), locale); + User user = new User(userId, username, password, firstname, surname, userState, roles, locale, propertyMap); logger.info("Added user " + user); // put user in map @@ -339,9 +348,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** + * Parses {@link Role} objects from their XML representations + * * @param rolesRootElement + * the element containing role elements + * + * @return the map of converted {@link Role} objects */ - private static Map readRoles(Element rolesRootElement) { + protected static Map readRoles(Element rolesRootElement) { Map roleMap = new HashMap(); @@ -360,7 +374,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { return roleMap; } - private static List toDomUsers(Map userMap) { + /** + * Converts {@link User} objects to their XML representations + * + * @param userMap + * the map of users to convert + * + * @return the list of XML User elements + */ + protected static List toDomUsers(Map userMap) { List usersAsElements = new ArrayList(userMap.size()); @@ -415,7 +437,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { return usersAsElements; } - private static List toDomRoles(Map roleMap) { + /** + * Converts {@link Role} objects to their XML representations + * + * @param roleMap + * the roles to convert + * + * @return the list of XML Role elements + */ + protected static List toDomRoles(Map roleMap) { List rolesAsElements = new ArrayList(roleMap.size()); @@ -443,10 +473,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** + * Parses {@link Privilege} objects from their XML representation to their objects + * * @param roleParentElement - * @return + * the parent on which the Privilege XML elements are + * + * @return the map of {@link Privilege} objects */ - private static Map readPrivileges(Element roleParentElement) { + protected static Map readPrivileges(Element roleParentElement) { Map privilegeMap = new HashMap(); @@ -485,7 +519,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { return privilegeMap; } - private static void toDomPrivileges(Element roleParentElement, Map privilegeMap) { + /** + * Converts {@link Privilege} objects to their XML representation + * + * @param roleParentElement + * the XML element of the parent {@link Role} + * @param privilegeMap + * the map of {@link Privilege}s to convert + */ + protected static void toDomPrivileges(Element roleParentElement, Map privilegeMap) { DocumentFactory documentFactory = DocumentFactory.getInstance(); @@ -519,4 +561,38 @@ public class XmlPersistenceHandler implements PersistenceHandler { roleParentElement.add(privilegeElement); } } + + /** + * Converts an {@link XmlConstants#XML_PROPERTIES} element containing {@link XmlConstants#XML_PROPERTY} elements to + * a {@link Map} of String key/value pairs + * + * @param element + * the XML {@link Element} with name {@link XmlConstants#XML_PROPERTIES} containing + * {@link XmlConstants#XML_PROPERTY} elements + * + * @return the {@link Map} of the property name/value combinations from the given {@link Element} + */ + @SuppressWarnings("unchecked") + protected static Map convertToPropertyMap(Element element) { + + // if element is null then there are no properties, so return empty map + if (element == null) + return Collections.emptyMap(); + + List elements = element.elements(XmlConstants.XML_PROPERTY); + + // if elements is null or empty then there are no properties, so return empty map + if (elements == null || elements.isEmpty()) + return Collections.emptyMap(); + + Map propertyMap = new HashMap(); + + for (Element property : elements) { + String name = property.attributeValue(XmlConstants.XML_ATTR_NAME); + String value = property.attributeValue(XmlConstants.XML_ATTR_VALUE); + propertyMap.put(name, value); + } + + return propertyMap; + } } diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index f1b1c57e6..9f2dda7ba 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -26,6 +26,7 @@ package ch.eitchnet.privilege.helper; import java.io.File; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -147,17 +148,17 @@ public class InitializationHelper { @SuppressWarnings("unchecked") public static Map convertToParameterMap(Element element) { - Map parameterMap = new HashMap(); - // if element is null then there are no parameters, so return empty map if (element == null) - return parameterMap; + return Collections.emptyMap(); List elements = element.elements(XmlConstants.XML_PARAMETER); // if elements is null or empty then there are no parameters, so return empty map if (elements == null || elements.isEmpty()) - return parameterMap; + return Collections.emptyMap(); + + Map parameterMap = new HashMap(); for (Element parameter : elements) { String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 54e3f4042..c554473a9 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -112,6 +112,16 @@ public class XmlConstants { */ public static final String XML_PARAMETER = "Parameter"; + /** + * XML_PROPERTIES = "Properties" : + */ + public static final String XML_PROPERTIES = "Properties"; + + /** + * XML_PROPERTY = "Property" : + */ + public static final String XML_PROPERTY = "Property"; + /** * XML_ALL_ALLOWED = "AllAllowed" : */ diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index 491d42fda..38e3562e3 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -27,6 +27,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Locale; +import java.util.Map; import java.util.Set; import ch.eitchnet.privilege.model.internal.Role; @@ -50,6 +51,7 @@ public class UserRep implements Serializable { private UserState userState; private Set roles; private Locale locale; + private Map propertyMap; /** * Default constructor @@ -68,9 +70,11 @@ public class UserRep implements Serializable { * the set of {@link Role}s assigned to this user * @param locale * the user's {@link Locale} + * @param propertyMap + * a {@link Map} containing string value pairs of properties for this user */ public UserRep(String userId, String username, String firstname, String surname, UserState userState, - Set roles, Locale locale) { + Set roles, Locale locale, Map propertyMap) { this.userId = userId; this.username = username; this.firstname = firstname; @@ -78,6 +82,7 @@ public class UserRep implements Serializable { this.userState = userState; this.roles = roles; this.locale = locale; + this.propertyMap = propertyMap; } /** @@ -176,4 +181,46 @@ public class UserRep implements Serializable { public void setLocale(Locale locale) { this.locale = locale; } + + /** + * Returns the property with the given key + * + * @param key + * the key for which the property is to be returned + * + * @return the property with the given key, or null if the property is not defined + */ + public String getProperty(String key) { + return this.propertyMap.get(key); + } + + /** + * Set the property with the key to the value + * + * @param key + * the key of the property to set + * @param value + * the value of the property to set + */ + public void setProperty(String key, String value) { + this.propertyMap.put(key, value); + } + + /** + * Returns the {@link Set} of keys of all properties + * + * @return the {@link Set} of keys of all properties + */ + public Set getPropertyKeySet() { + return this.propertyMap.keySet(); + } + + /** + * Returns the map of properties + * + * @return the map of properties + */ + public Map getProperties() { + return this.propertyMap; + } } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index e72908652..bdfc7a438 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -26,8 +26,10 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Locale; +import java.util.Map; import java.util.Set; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -60,6 +62,8 @@ public final class User { private final Set roles; + private final Map propertyMap; + private final Locale locale; /** @@ -81,9 +85,11 @@ public final class User { * the set of {@link Role}s assigned to this user * @param locale * the user's {@link Locale} + * @param propertyMap + * a {@link Map} containing string value pairs of properties for this user */ public User(String userId, String username, String password, String firstname, String surname, UserState userState, - Set roles, Locale locale) { + Set roles, Locale locale, Map propertyMap) { if (userId == null || userId.isEmpty()) { throw new PrivilegeException("No UserId defined!"); @@ -120,6 +126,8 @@ public final class User { this.roles = Collections.unmodifiableSet(roles); this.locale = locale; + + this.propertyMap = Collections.unmodifiableMap(propertyMap); } /** @@ -202,7 +210,7 @@ public final class User { */ public UserRep asUserRep() { return new UserRep(this.userId, this.username, this.firstname, this.surname, this.userState, - new HashSet(this.roles), this.locale); + new HashSet(this.roles), this.locale, new HashMap(this.propertyMap)); } /** @@ -259,4 +267,34 @@ public final class User { return false; return true; } + + /** + * Returns the property with the given key + * + * @param key + * the key for which the property is to be returned + * + * @return the property with the given key, or null if the property is not defined + */ + public String getProperty(String key) { + return this.propertyMap.get(key); + } + + /** + * Returns the {@link Set} of keys of all properties + * + * @return the {@link Set} of keys of all properties + */ + public Set getPropertyKeySet() { + return this.propertyMap.keySet(); + } + + /** + * Returns the map of properties + * + * @return the map of properties + */ + public Map getProperties() { + return this.propertyMap; + } } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 72370488f..641ad883e 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -140,7 +140,8 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); // let's add a new user bob - UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, + new HashMap()); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + BOB); @@ -237,7 +238,8 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted - UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, + new HashMap()); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); privilegeHandler.invalidateSession(certificate); @@ -267,7 +269,8 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted - UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null, + new HashMap()); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); privilegeHandler.invalidateSession(certificate); From 058e67f10eeb2be95dd4fcc8f8d34bee9ad63b2a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 7 Aug 2011 16:13:23 +0200 Subject: [PATCH 065/457] [New] it is now possible for a user to change their own password --- .../handler/DefaultPrivilegeHandler.java | 13 +++++- .../privilege/handler/PrivilegeHandler.java | 6 +++ .../privilege/test/PrivilegeTest.java | 44 ++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 5068b6ed0..55471cb63 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -452,8 +452,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public void setUserPassword(Certificate certificate, String username, String password) { - // validate who is doing this - validateIsPrivilegeAdmin(certificate); + // check if certificate is for same user, in which case user is changing their own password + if (certificate.getUsername().equals(username)) { + + // validate the certificate + isCertificateValid(certificate); + + } else { + + // otherwise validate the the certificate is for a privilege admin + validateIsPrivilegeAdmin(certificate); + } // get User User user = this.persistenceHandler.getUser(username); diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 2af410f25..03bda0c84 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -227,9 +227,15 @@ public interface PrivilegeHandler { throws AccessDeniedException, PrivilegeException; /** + *

    * Changes the password for the {@link User} with the given username. If the password is null, then the {@link User} * can not login anymore. Otherwise the password must meet the requirements of the implementation under * {@link PrivilegeHandler#validatePassword(String)} + *

    + * + *

    + * It should be possible for a user to change their own password + *

    * * @param certificate * the {@link Certificate} of the user which has the privilege to perform this action diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 641ad883e..859ff4415 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -64,7 +64,9 @@ public class PrivilegeTest { private static final String PASS_BOB = "admin1"; private static final String ROLE_FEATHERLITE_USER = "FeatherliteUser"; private static final String ROLE_USER = "user"; + private static final String PASS_DEF = "def"; private static final String PASS_BAD = "123"; + private static final String PASS_TED = "12345"; private static final Logger logger = Logger.getLogger(PrivilegeTest.class); @@ -269,10 +271,50 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted - UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null, + HashSet roles = new HashSet(); + roles.add(ROLE_USER); + UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); + + privilegeHandler.invalidateSession(certificate); + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testSetTedPwdAsBob() throws Exception { + + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // set ted's password to default + privilegeHandler.setUserPassword(certificate, TED, PASS_DEF); + + privilegeHandler.invalidateSession(certificate); + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testTedChangesOwnPwd() throws Exception { + Certificate certificate = privilegeHandler.authenticate(TED, PASS_DEF); + privilegeHandler.setUserPassword(certificate, TED, PASS_TED); + privilegeHandler.invalidateSession(certificate); + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testAuthAsTed() throws Exception { + Certificate certificate = privilegeHandler.authenticate(TED, PASS_TED); privilegeHandler.invalidateSession(certificate); } From cb6215b2353e848cea054e52e37d1fe6c4756631 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 9 Nov 2011 15:03:50 +0100 Subject: [PATCH 066/457] [Bugfix] fixed a bug where if the tag was not defined in the PrivilegeModel.xml file then a NullPointer was thrown. It is now possible to leave out this tag, in which case false is assumed --- .../eitchnet/privilege/handler/XmlPersistenceHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index b5a962f4d..6910ad735 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -491,8 +491,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); - boolean allAllowed = Boolean.valueOf(allAllowedS).booleanValue(); + Element allAllowedE = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED); + boolean allAllowed = false; + if (allAllowedE != null) { + allAllowed = Boolean.valueOf(allAllowedE.getTextTrim()).booleanValue(); + } @SuppressWarnings("unchecked") List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); From db3c086d391f18c789b5a85942b7a444a485e1c9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 9 Nov 2011 15:05:05 +0100 Subject: [PATCH 067/457] [Minor] minor message change where if is false, the policy, deny and allow must be defined. This is now better communicated in the exception message --- src/ch/eitchnet/privilege/model/internal/Privilege.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 96fa4b418..2ee6121f5 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -101,17 +101,17 @@ public final class Privilege { // not all allowed, so policy must be set if (policy == null || policy.isEmpty()) { - throw new PrivilegeException("No Policy defined!"); + throw new PrivilegeException("All is not allowed and no Policy defined!"); } this.policy = policy; if (denyList == null) { - throw new PrivilegeException("No denyList defined!"); + throw new PrivilegeException("All is not allowed and no denyList defined!"); } this.denyList = Collections.unmodifiableSet(denyList); if (allowList == null) { - throw new PrivilegeException("No allowList defined!"); + throw new PrivilegeException("All is not allowed and no allowList defined!"); } this.allowList = Collections.unmodifiableSet(allowList); } From 3c322c5b8869f0174bfec1761e77fd99f0974b85 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 9 Nov 2011 15:05:36 +0100 Subject: [PATCH 068/457] [Internal] minor project building changes --- MANIFEST.MF | 4 ++-- build.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/MANIFEST.MF b/MANIFEST.MF index c30795c80..ab9c3e6d7 100644 --- a/MANIFEST.MF +++ b/MANIFEST.MF @@ -1,7 +1,7 @@ Manifest-Version: 1.0 Implementation-Vendor: eitchnet.ch Implementation-Title: eitchnet Java Privilege implementation -Implementation-Version: 0.0.1 +Implementation-Version: 0.0.2 Specification-Vendor: eitchnet.ch Specification-Title: eitchnet Java Privilege implementation -Specification-Version: 1.6 +Specification-Version: 0.1 diff --git a/build.xml b/build.xml index 64bb5f254..34f371db8 100644 --- a/build.xml +++ b/build.xml @@ -59,6 +59,7 @@ along with Privilege. If not, see . + From 81cb2aa8ae947e854dc03c8d7034fccc3a56eea1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 11 Nov 2011 10:56:56 +0100 Subject: [PATCH 069/457] [Interface] added a property map to the Certificate which is a copy of the users properties on login. This map can be used to configure the user's session --- .../handler/DefaultPrivilegeHandler.java | 4 +++- .../eitchnet/privilege/model/Certificate.java | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 55471cb63..28442e470 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -564,7 +564,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get next session id String sessionId = nextSessionId(); - certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + // create a new certificate, with details of the user + certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale(), + new HashMap(user.getProperties())); // create and save a new session Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index 7ae4f5f56..762ff0606 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -27,6 +27,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Locale; +import java.util.Map; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -50,6 +51,8 @@ public final class Certificate implements Serializable { private Locale locale; + private Map propertyMap; + /** * Default constructor initializing with all information needed for this certificate * @@ -71,8 +74,12 @@ public final class Certificate implements Serializable { * {@link Session} * @param locale * the users {@link Locale} + * @param propertyMap + * a {@link Map} containing string value pairs of properties for the logged in user. These properties can + * be edited and can be used for the user to change settings of this session */ - public Certificate(String sessionId, String username, String authToken, String authPassword, Locale locale) { + public Certificate(String sessionId, String username, String authToken, String authPassword, Locale locale, + Map propertyMap) { // validate arguments are not null if (sessionId == null || username == null || authToken == null || authPassword == null) { @@ -89,6 +96,15 @@ public final class Certificate implements Serializable { this.locale = Locale.getDefault(); else this.locale = locale; + + this.propertyMap = propertyMap; + } + + /** + * @return the propertyMap + */ + public Map getPropertyMap() { + return this.propertyMap; } /** From c44b30a1e1546893d0c3101a53459c5f0548a64a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 11 Nov 2011 10:58:19 +0100 Subject: [PATCH 070/457] [Internal] bumped the MANIFEST.MF version to 0.0.3 --- MANIFEST.MF | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.MF b/MANIFEST.MF index ab9c3e6d7..8a69d98b1 100644 --- a/MANIFEST.MF +++ b/MANIFEST.MF @@ -1,7 +1,7 @@ Manifest-Version: 1.0 Implementation-Vendor: eitchnet.ch Implementation-Title: eitchnet Java Privilege implementation -Implementation-Version: 0.0.2 +Implementation-Version: 0.0.3 Specification-Vendor: eitchnet.ch Specification-Title: eitchnet Java Privilege implementation Specification-Version: 0.1 From 7017a8cfa0992d7a40f9ab63d6e8e365bfb7ae1a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 8 Jun 2012 09:46:26 -0700 Subject: [PATCH 071/457] Initial commit --- .gitignore | 6 ++++++ README.md | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0f182a034 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.class + +# Package Files # +*.jar +*.war +*.ear diff --git a/README.md b/README.md new file mode 100644 index 000000000..c7088aa88 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +ch.eitchnet.java.utils +====================== + +Generic Java XML persistence layer. Implemented to be light-weight and simple to use \ No newline at end of file From b8ece51dbe531449e372d8377a92a68e66f44c27 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 8 Jun 2012 09:47:44 -0700 Subject: [PATCH 072/457] Initial commit --- .gitignore | 6 ++++++ README.md | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0f182a034 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.class + +# Package Files # +*.jar +*.war +*.ear diff --git a/README.md b/README.md new file mode 100644 index 000000000..816af7f1a --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +ch.eitchnet.java.xmlpers +======================== + +Generic Java XML persistence layer. Implemented to be light-weight and simple to use \ No newline at end of file From 82368adaffd238c63811ef06f57db7554d10817d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 8 Jun 2012 18:59:51 +0200 Subject: [PATCH 073/457] [New] Initial commit of the utils This is initial commit includes * a RMI handler which can be used for easy up- and downloading of files over RMI * a FileHelper * a Log4jConfigurator * a StringHelper * a SystemHelper * a ObjectFilter which is used for caching operations to objects. These objects must implement ITransactionObject --- README.md | 3 +- lib/log4j-1.2.16-src.zip | Bin 0 -> 473361 bytes src/ch/eitchnet/rmi/RMIFileClient.java | 50 ++ src/ch/eitchnet/rmi/RmiFileDeletion.java | 40 ++ src/ch/eitchnet/rmi/RmiFileHandler.java | 249 +++++++ src/ch/eitchnet/rmi/RmiFilePart.java | 147 +++++ src/ch/eitchnet/rmi/RmiHelper.java | 221 +++++++ src/ch/eitchnet/utils/helper/FileHelper.java | 471 ++++++++++++++ .../utils/helper/Log4jConfigurator.java | 169 +++++ .../utils/helper/Log4jPropertyWatchDog.java | 104 +++ .../eitchnet/utils/helper/StringHelper.java | 397 ++++++++++++ .../eitchnet/utils/helper/SystemHelper.java | 73 +++ .../objectfilter/ITransactionObject.java | 37 ++ .../utils/objectfilter/ObjectCache.java | 112 ++++ .../utils/objectfilter/ObjectFilter.java | 611 ++++++++++++++++++ .../utils/objectfilter/Operation.java | 21 + 16 files changed, 2703 insertions(+), 2 deletions(-) create mode 100644 lib/log4j-1.2.16-src.zip create mode 100644 src/ch/eitchnet/rmi/RMIFileClient.java create mode 100644 src/ch/eitchnet/rmi/RmiFileDeletion.java create mode 100644 src/ch/eitchnet/rmi/RmiFileHandler.java create mode 100644 src/ch/eitchnet/rmi/RmiFilePart.java create mode 100644 src/ch/eitchnet/rmi/RmiHelper.java create mode 100644 src/ch/eitchnet/utils/helper/FileHelper.java create mode 100644 src/ch/eitchnet/utils/helper/Log4jConfigurator.java create mode 100644 src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java create mode 100644 src/ch/eitchnet/utils/helper/StringHelper.java create mode 100644 src/ch/eitchnet/utils/helper/SystemHelper.java create mode 100644 src/ch/eitchnet/utils/objectfilter/ITransactionObject.java create mode 100644 src/ch/eitchnet/utils/objectfilter/ObjectCache.java create mode 100644 src/ch/eitchnet/utils/objectfilter/ObjectFilter.java create mode 100644 src/ch/eitchnet/utils/objectfilter/Operation.java diff --git a/README.md b/README.md index c7088aa88..acb872905 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ ch.eitchnet.java.utils ====================== - -Generic Java XML persistence layer. Implemented to be light-weight and simple to use \ No newline at end of file +Java Utilites which ease daily work when programming in the Java language diff --git a/lib/log4j-1.2.16-src.zip b/lib/log4j-1.2.16-src.zip new file mode 100644 index 0000000000000000000000000000000000000000..086789a2076cfa7c88b567f012b5bbf32732c6ab GIT binary patch literal 473361 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 z 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

    UKmIas$A(z~a^EA#X+ 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⊀`RWm9GuT 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%0Dcr!jwAda(Wo?xx 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 literal 0 HcmV?d00001 diff --git a/src/ch/eitchnet/rmi/RMIFileClient.java b/src/ch/eitchnet/rmi/RMIFileClient.java new file mode 100644 index 000000000..de93bb95a --- /dev/null +++ b/src/ch/eitchnet/rmi/RMIFileClient.java @@ -0,0 +1,50 @@ +package ch.eitchnet.rmi; + +import java.rmi.RemoteException; + +/** + * @author Robert von Burg + * + */ +public interface RMIFileClient { + + /** + * 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(RmiFilePart 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 RmiFileDeletion} 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(RmiFileDeletion fileDeletion) throws RemoteException; + + /** + * Remote method which a client can request part of a file. The server will fill the given {@link RmiFilePart} 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 RmiFilePart requestFile(RmiFilePart filePart) throws RemoteException; +} diff --git a/src/ch/eitchnet/rmi/RmiFileDeletion.java b/src/ch/eitchnet/rmi/RmiFileDeletion.java new file mode 100644 index 000000000..91e85c761 --- /dev/null +++ b/src/ch/eitchnet/rmi/RmiFileDeletion.java @@ -0,0 +1,40 @@ +package ch.eitchnet.rmi; + +import java.io.Serializable; + +/** + * @author Robert von Burg + */ +public class RmiFileDeletion 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 RmiFileDeletion(String fileName, String fileType) { + this.fileName = fileName; + this.fileType = fileType; + } + + /** + * @return the fileType + */ + public String getFileType() { + return fileType; + } + + /** + * @return the fileName + */ + public String getFileName() { + return fileName; + } +} diff --git a/src/ch/eitchnet/rmi/RmiFileHandler.java b/src/ch/eitchnet/rmi/RmiFileHandler.java new file mode 100644 index 000000000..c2a55a70c --- /dev/null +++ b/src/ch/eitchnet/rmi/RmiFileHandler.java @@ -0,0 +1,249 @@ +package ch.eitchnet.rmi; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.apache.log4j.Logger; + +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(RmiFilePart)} and the downloading a file is done by calling {@link #requestFile(RmiFilePart)} + * + * @author Robert von Burg + */ +public class RmiFileHandler { + + private static final Logger logger = Logger.getLogger(RmiFileHandler.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; + + /** + * + */ + public RmiFileHandler(String basePath) { + + File basePathF = new File(basePath); + if (!basePathF.exists()) + throw new RuntimeException("Base Path does not exist " + basePathF.getAbsolutePath()); + if (!basePathF.canWrite()) + throw new RuntimeException("Can not write to base path " + basePathF.getAbsolutePath()); + + this.basePath = basePath; + } + + /** + * Method which a client can request part of a file. The server will fill the given {@link RmiFilePart} 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 RmiFilePart requestFile(RmiFilePart 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 + File file = new File(basePath + "/" + fileType, filePart.getFileName()); + + // now evaluate the file exists + if (!file.canRead()) { + throw new RuntimeException("The file " + fileName + + " could not be found in the location for files of type " + fileType); + } + + // 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 > MAX_PART_SIZE) { + throw new RuntimeException("The requested part size " + requestSize + " is greater than the allowed " + + MAX_PART_SIZE); + } + + // validate lengths and offsets + if (filePart.getFileLength() != fileSize) { + throw new RuntimeException("The part request has a file size " + filePart.getFileLength() + + ", but the file is actually " + fileSize); + } else if (requestOffset > fileSize) { + throw new RuntimeException("The requested file part offset " + requestOffset + + " is greater than the size of the file " + fileSize); + } + // 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) + throw new RuntimeException("Something went wrong. Min of requestSize and remaining is > MAX_PART_SIZE!"); + + // 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 + FileInputStream fin = null; + try { + + // position the stream + fin = new FileInputStream(file); + long skip = fin.skip(requestOffset); + if (skip != requestOffset) + throw new IOException("Asked to skip " + requestOffset + " but only skipped " + skip); + + // read the data + byte[] bytes = new byte[requestSize]; + int read = fin.read(bytes); + if (read != requestSize) + throw new IOException("Asked to read " + requestSize + " but only read " + read); + + // set the return result + filePart.setPartBytes(bytes); + + } catch (FileNotFoundException e) { + throw new RuntimeException("The file " + fileName + + " could not be found in the location for files of type " + fileType); + } catch (IOException e) { + throw new RuntimeException("There was an error while reading from the file " + fileName); + } finally { + if (fin != null) { + try { + fin.close(); + } catch (IOException e) { + logger.error("Error while closing FileInputStream: " + e.getLocalizedMessage()); + } + } + } + + // we are returning the same object as the user gave us, just edited + 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(RmiFilePart 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 + File dstFile = new File(basePath + "/" + fileType, filePart.getFileName()); + + // if the file already exists, then this may not be a start part + if (filePart.getPartOffset() == 0 && dstFile.exists()) { + throw new RuntimeException("The file " + fileName + " already exist for type " + fileType); + } + + // 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())) { + throw new RuntimeException("Uploading the file " + filePart.getFileName() + + " failed because the hashes don't match. Expected: " + filePart.getFileHash() + " / Actual: " + + dstFileHash); + } + } + } + + /** + * Method with which a client can delete files from the server. It only deletes single files if they exist + * + * @param fileDeletion + * the {@link RmiFileDeletion} defining the deletion request + * + * @return true if the file was deleted, false if the file did not exist + * + */ + public boolean deleteFile(RmiFileDeletion 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 + File fileToDelete = new File(basePath + "/" + fileType, fileDeletion.getFileName()); + + // delete the file + return FileHelper.deleteFiles(new File[] { fileToDelete }, true); + } + + /** + * 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!"); + } else if (fileName.contains("/")) { + throw new RuntimeException( + "The given file name contains illegal characters. The file name may not contain slashes!"); + } + } + + /** + * 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!"); + } else if (fileType.contains("/")) { + throw new RuntimeException( + "The given file type contains illegal characters. The file type may not contain slashes!"); + } + } +} diff --git a/src/ch/eitchnet/rmi/RmiFilePart.java b/src/ch/eitchnet/rmi/RmiFilePart.java new file mode 100644 index 000000000..a4a355a37 --- /dev/null +++ b/src/ch/eitchnet/rmi/RmiFilePart.java @@ -0,0 +1,147 @@ +package ch.eitchnet.rmi; + +import java.io.Serializable; + +/** + * @author Robert von Burg + */ +public class RmiFilePart 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 RmiFilePart(String fileName, String fileType) { + + if (fileName == null || fileName.isEmpty()) + throw new RuntimeException("fileName may not be empty!"); + if (fileType == null || fileType.isEmpty()) + throw new RuntimeException("fileType may not be empty!"); + + this.fileName = fileName; + this.fileType = fileType; + + this.partOffset = 0; + this.partLength = RmiFileHandler.MAX_PART_SIZE; + this.partBytes = null; + } + + /** + * @return the fileLength + */ + public long getFileLength() { + return fileLength; + } + + /** + * @param fileLength + * the fileLength to set + */ + public void setFileLength(long fileLength) { + this.fileLength = fileLength; + } + + /** + * @return the fileHash + */ + public String getFileHash() { + return fileHash; + } + + /** + * @param fileHash + * the fileHash to set + */ + public void setFileHash(String fileHash) { + this.fileHash = fileHash; + } + + /** + * @return the fileType + */ + public String getFileType() { + return fileType; + } + + /** + * @return the partOffset + */ + public long getPartOffset() { + return partOffset; + } + + /** + * @param partOffset + * the partOffset to set + */ + public void setPartOffset(long partOffset) { + this.partOffset = partOffset; + } + + /** + * @return the partLength + */ + public int getPartLength() { + return partLength; + } + + /** + * @param partLength + * the partLength to set + */ + public void setPartLength(int partLength) { + this.partLength = partLength; + } + + /** + * @return the partBytes + */ + public byte[] getPartBytes() { + return partBytes; + } + + /** + * @param partBytes + * the partBytes to set + */ + public void setPartBytes(byte[] partBytes) { + this.partBytes = partBytes; + } + + /** + * @return the lastPart + */ + public boolean isLastPart() { + return lastPart; + } + + /** + * @param lastPart + * the lastPart to set + */ + public void setLastPart(boolean lastPart) { + this.lastPart = lastPart; + } + + /** + * @return the fileName + */ + public String getFileName() { + return fileName; + } +} diff --git a/src/ch/eitchnet/rmi/RmiHelper.java b/src/ch/eitchnet/rmi/RmiHelper.java new file mode 100644 index 000000000..4755dce00 --- /dev/null +++ b/src/ch/eitchnet/rmi/RmiHelper.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2010 - 2011 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.rmi; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.rmi.RemoteException; + +import org.apache.log4j.Logger; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + * + */ +public class RmiHelper { + + private static final Logger logger = Logger.getLogger(RmiHelper.class); + + /** + * @param rmiFileClient + * @param filePart + * @param dstFile + */ + public static void downloadFile(RMIFileClient rmiFileClient, RmiFilePart filePart, File dstFile) { + + // here we don't overwrite, the caller must make sure the destination file does not exist + if (dstFile.exists()) + throw new RuntimeException("The destination file " + dstFile.getAbsolutePath() + + " already exists. Delete it first, if you want to overwrite it!"); + + try { + int loops = 0; + int startLength = filePart.getPartLength(); + while (true) { + loops += 1; + + // get the next part + filePart = rmiFileClient.requestFile(filePart); + + // validate length of data + if (filePart.getPartLength() != filePart.getPartBytes().length) + throw new RuntimeException("Invalid FilePart. Part length is not as long as the bytes passed " + + filePart.getPartLength() + " / " + filePart.getPartBytes().length); + + // validate offset is size of file + if (filePart.getPartOffset() != dstFile.length()) { + throw new RuntimeException("The part offset $offset is not at the end of the file " + + filePart.getPartOffset() + " / " + dstFile.length()); + } + + // append the part + FileHelper.appendFilePart(dstFile, filePart.getPartBytes()); + + // update the offset + filePart.setPartOffset(filePart.getPartOffset() + filePart.getPartBytes().length); + + // break if the offset is past the length of the file + if (filePart.getPartOffset() >= filePart.getFileLength()) + break; + } + logger.info(filePart.getFileType() + ": " + filePart.getFileName() + ": Requested " + loops + + " parts. StartSize: " + startLength + " EndSize: " + filePart.getPartLength()); + + // validate that the offset is at the end of the file + if (filePart.getPartOffset() != filePart.getFileLength()) { + throw new RuntimeException("Offset " + filePart.getPartOffset() + " is not at file length " + + filePart.getFileLength() + " after reading all the file parts!"); + } + + // now validate hashes + String dstFileHash = StringHelper.getHexString(FileHelper.hashFileSha256(dstFile)); + if (!dstFileHash.equals(filePart.getFileHash())) { + throw new RuntimeException("Downloading the file " + filePart.getFileName() + + " failed because the hashes don't match. Expected: " + filePart.getFileHash() + " / Actual: " + + dstFileHash); + } + + } catch (Exception e) { + if (e instanceof RuntimeException) + throw (RuntimeException) e; + throw new RuntimeException("Downloading the file " + filePart.getFileName() + + " failed because of an underlying exception " + e.getLocalizedMessage()); + } + } + + /** + * @param rmiFileClient + * @param srcFile + * @param fileType + */ + public static void uploadFile(RMIFileClient rmiFileClient, File srcFile, String fileType) { + + // make sure the source file exists + if (!srcFile.canRead()) + throw new RuntimeException("The source file does not exist at " + srcFile.getAbsolutePath()); + + BufferedInputStream inputStream = null; + try { + + // get the size of the file + long fileLength = srcFile.length(); + String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(srcFile)); + + // create the file part to send + RmiFilePart filePart = new RmiFilePart(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 > RmiFileHandler.MAX_PART_SIZE) + partLength = RmiFileHandler.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 + inputStream = new BufferedInputStream(new FileInputStream(srcFile)); + + 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!"); + if (read != bytes.length) { + throw new IOException( + "Something went wrong while reading the bytes as the wrong number of bytes were read. Expected " + + bytes.length + " Actual: " + read); + } + + // 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 > RmiFileHandler.MAX_PART_SIZE) + throw new RuntimeException("Something went wrong as the remaining part " + remaining + + " is larger than MAX_PART_SIZE " + RmiFileHandler.MAX_PART_SIZE + "!"); + partLength = (int) remaining; + bytes = new byte[partLength]; + } + + // and save the offset for the next loop + offset = nextOffset; + } + + logger.info(filePart.getFileType() + ": " + filePart.getFileName() + ": Sent " + loops + + " parts. StartSize: " + startLength + " EndSize: " + filePart.getPartLength()); + + } catch (Exception e) { + if (e instanceof RuntimeException) + throw (RuntimeException) e; + throw new RuntimeException("Uploading the file " + srcFile.getAbsolutePath() + + " failed because of an underlying exception " + e.getLocalizedMessage()); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + logger.error("Exception while closing FileInputStream " + e.getLocalizedMessage()); + } + } + } + } + + /** + * @param rmiFileClient + * @param fileDeletion + * @param dstFile + */ + public static void deleteFile(RMIFileClient rmiFileClient, RmiFileDeletion fileDeletion, File dstFile) { + + try { + rmiFileClient.deleteFile(fileDeletion); + } catch (RemoteException e) { + throw new RuntimeException("Deleting the file " + fileDeletion.getFileName() + + " failed because of an underlying exception " + e.getLocalizedMessage()); + } + } +} diff --git a/src/ch/eitchnet/utils/helper/FileHelper.java b/src/ch/eitchnet/utils/helper/FileHelper.java new file mode 100644 index 000000000..9e9de3735 --- /dev/null +++ b/src/ch/eitchnet/utils/helper/FileHelper.java @@ -0,0 +1,471 @@ +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.security.MessageDigest; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.log4j.Logger; + +/** + * Helper class for dealing with files + * + * @author Robert von Burg + */ +public class FileHelper { + + private static final Logger logger = Logger.getLogger(FileHelper.class); + + /** + * 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"); + } + + return sb.toString(); + + } catch (FileNotFoundException e) { + throw new RuntimeException("Filed does not exist " + file.getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException("Could not read file " + file.getAbsolutePath()); + } + } + + /** + * Writes the string to dstFile + * + * @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); + + bufferedwriter.close(); + + } catch (FileNotFoundException e) { + throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException("Could not write to file " + dstFile.getAbsolutePath()); + } + } + + /** + * 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 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 = deleteFiles(file.listFiles(), log); + if (!done) { + worked = false; + logger.warn("Could not empty the directory: " + file.getAbsolutePath()); + } else { + done = file.delete(); + if (done) { + if (log) + logger.info("Deleted DIR " + file.getAbsolutePath()); + } else { + worked = false; + logger.warn("Could not delete the directory: " + file.getAbsolutePath()); + } + } + } else { + boolean done = file.delete(); + if (done) { + if (log) + logger.info("Deleted FILE " + file.getAbsolutePath()); + } else { + worked = false; + logger.warn(("Could not delete the file: " + file.getAbsolutePath())); + } + } + } + return worked; + } + + /** + * Copy a {@link File} The renameTo method does not allow action across NFS mounted filesystems this method is the + * workaround + * + * @param fromFile + * The existing File + * @param toFile + * The new File + * @param checksum + * if true, then a MD5 checksum is made to validate copying + * @return true if and only if the renaming succeeded; false otherwise + */ + public final static boolean copy(File fromFile, File toFile, boolean checksum) { + + BufferedInputStream inBuffer = null; + BufferedOutputStream outBuffer = null; + try { + + inBuffer = new BufferedInputStream(new FileInputStream(fromFile)); + outBuffer = new BufferedOutputStream(new FileOutputStream(toFile)); + + int theByte = 0; + + while ((theByte = inBuffer.read()) > -1) { + outBuffer.write(theByte); + } + + inBuffer.close(); + outBuffer.flush(); + outBuffer.close(); + + if (checksum) { + String fromFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(fromFile)); + String toFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(toFile)); + if (!fromFileMD5.equals(toFileMD5)) { + logger.error("Copying failed, as MD5 sums are not equal: " + fromFileMD5 + " / " + toFileMD5); + toFile.delete(); + + return false; + } + } + + // cleanup if files are not the same length + if (fromFile.length() != toFile.length()) { + logger.error("Copying failed, as new files are not the same length: " + fromFile.length() + " / " + + toFile.length()); + toFile.delete(); + + return false; + } + + } catch (Exception e) { + + logger.error(e, e); + return false; + + } finally { + + if (inBuffer != null) { + try { + inBuffer.close(); + } catch (IOException e) { + logger.error("Error closing BufferedInputStream" + e); + } + } + + if (outBuffer != null) { + try { + outBuffer.close(); + } catch (IOException e) { + logger.error("Error closing BufferedOutputStream" + e); + } + } + } + + return true; + } + + /** + * Move a File The renameTo method does not allow action across NFS mounted filesystems this method is the + * workaround + * + * @param fromFile + * The existing File + * @param toFile + * The new File + * @return true if and only if the renaming succeeded; false otherwise + */ + public final static boolean move(File fromFile, File toFile) { + + if (fromFile.renameTo(toFile)) { + return true; + } + + logger.warn("Simple File.renameTo failed, trying copy/delete..."); + + // delete if copy was successful, otherwise move will fail + if (FileHelper.copy(fromFile, toFile, true)) { + logger.info("Deleting fromFile: " + fromFile.getAbsolutePath()); + return fromFile.delete(); + } + + return false; + } + + /** + * Finds the common parent for the files in the given list + * + * @param files + * the files to find the common parent for + * + * @return the {@link File} representing the common parent, or null if there is none + */ + public static File findCommonParent(List files) { + + // be gentle with bad input data + if (files.size() == 0) + return null; + if (files.size() == 1) + return files.get(0).getParentFile(); + + File commonParent = null; + int commonParentDepth = -1; + + // find the common parent among all the files + for (int i = 0; i < files.size() - 1; i++) { + + // get first file + File file = files.get(i); + + // get list of parents for this file + List parents = new ArrayList(); + File parent = file.getParentFile(); + while (parent != null) { + parents.add(parent); + parent = parent.getParentFile(); + } + // reverse + Collections.reverse(parents); + + // and now the same for the next file + File fileNext = files.get(i + 1); + List parentsNext = new ArrayList(); + File parentNext = fileNext.getParentFile(); + while (parentNext != null) { + parentsNext.add(parentNext); + parentNext = parentNext.getParentFile(); + } + // reverse + Collections.reverse(parentsNext); + + //logger.info("Files: " + file + " / " + fileNext); + + // now find the common parent + File newCommonParent = null; + int newCommonParentDepth = -1; + for (int j = 0; j < (parents.size()); j++) { + + // don't overflow the size of the next list of parents + if (j >= parentsNext.size()) + break; + + // if we once found a common parent, and our current depth is + // greater than the one for the common parent, then stop as + // there can't be a deeper parent + if (commonParent != null && j > commonParentDepth) + break; + + // get the next parents to compare + File aParent = parents.get(j); + File bParent = parentsNext.get(j); + + //logger.info("Comparing " + aParent + " |||| " + bParent); + + // if they parent are the same, then break, as we won't + // have another match + if (!aParent.equals(bParent)) + break; + + // save the parent and the depth where we found the parent + newCommonParent = aParent; + newCommonParentDepth = j; + } + + // if no common parent was found, then break as there won't be one + if (commonParent == null && newCommonParent == null) + break; + + // if there is no new common parent, then check the next file + if (newCommonParent == null) + continue; + + // store the common parent + commonParent = newCommonParent; + commonParentDepth = newCommonParentDepth; + //logger.info("Temporary common parent: (" + commonParentDepth + ") " + commonParent); + } + + //logger.info("Common parent: " + commonParent); + return commonParent; + } + + /** + * Returns the size of the file in a human readable form. Everything smaller than 1024 bytes is returned as x bytes, + * next is KB, then MB and then GB + * + * @param file + * the file for which the humanized size is to be returned + * + * @return the humanized form of the files size + */ + public final static String humanizeFileSize(File file) { + return 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 + */ + 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 hashFile(file, "MD5"); + } + + /** + * 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 hashFile(file, "SHA-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 hashFile(file, "SHA-256"); + } + + /** + * 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); + fis.close(); + + return complete.digest(); + } catch (Exception e) { + throw new RuntimeException("Something went wrong while hashing file: " + file.getAbsolutePath()); + } + } + + /** + * 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) { + FileOutputStream outputStream = null; + try { + + 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()); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + logger.error("Exception while closing FileOutputStream " + e.getLocalizedMessage()); + } + } + } + } +} diff --git a/src/ch/eitchnet/utils/helper/Log4jConfigurator.java b/src/ch/eitchnet/utils/helper/Log4jConfigurator.java new file mode 100644 index 000000000..09150aa6b --- /dev/null +++ b/src/ch/eitchnet/utils/helper/Log4jConfigurator.java @@ -0,0 +1,169 @@ +package ch.eitchnet.utils.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.Properties; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; +import org.apache.log4j.PropertyConfigurator; + +/** + * A simple configurator to configure log4j, with fall back default configuration + * + * @author Robert von Burg + */ +public class Log4jConfigurator { + + /** + * system property used to override the log4j configuration file + */ + public static final String PROP_FILE_LOG4J = "rsp.log4j.properties"; + + /** + * default log4j configuration file + */ + public static final String FILE_LOG4J = "log4j.properties"; + + /** + * runtime log4j configuration file which is a copy of the original file but has any place holders overwritten + */ + public static final String FILE_LOG4J_TEMP = "log4j.properties.tmp"; + + private static final Logger logger = Logger.getLogger(Log4jConfigurator.class); + private static Log4jPropertyWatchDog watchDog; + + /** + * Configures log4j with the default {@link ConsoleAppender} + */ + public static synchronized void configure() { + cleanupOldWatchdog(); + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(getDefaulLayout())); + Logger.getRootLogger().setLevel(Level.INFO); + } + + /** + * Returns the default layout: %d %5p [%t] %C{1} %M - %m%n + * + * @return the default layout + */ + public static PatternLayout getDefaulLayout() { + return new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"); + } + + /** + *

    + * Loads the log4j configuration + *

    + * + *

    + * This file is configurable through the {@link Log4jConfigurator#PROP_FILE_LOG4J} system property, but uses the + * default {@link Log4jConfigurator#FILE_LOG4J} file, if no configuration option is set. The path used is + * /config/ whereas is a system property + *

    + * + *

    + * Any properties in the properties are substituted using + * {@link StringHelper#replaceProperties(Properties, Properties)} and then the configuration file is written to a + * new file /tmp/{@link Log4jConfigurator#FILE_LOG4J_TEMP} and then finally + * {@link PropertyConfigurator#configureAndWatch(String)} is called so that the configuration is loaded and log4j + * watches the temporary file for configuration changes + *

    + */ + public static synchronized void loadLog4jConfiguration() { + + // first clean up any old watch dog in case of a programmatic re-load of hte configuration + cleanupOldWatchdog(); + + // get a configured log4j properties file, or use default RSPConfigConstants.FILE_LOG4J + String fileLog4j = SystemHelper.getProperty(Log4jConfigurator.class.getName(), + Log4jConfigurator.PROP_FILE_LOG4J, Log4jConfigurator.FILE_LOG4J); + + // get the root directory + String userDir = System.getProperty("user.dir"); + String configDir = userDir + "/config/"; + String tmpDir = userDir + "/tmp/"; + + // load the log4j.properties file + String pathNameToLog4j = configDir + fileLog4j; + File log4JPath = new File(pathNameToLog4j); + if (!log4JPath.exists()) + throw new RuntimeException("The log4j configuration file does not exist at " + log4JPath.getAbsolutePath()); + + String pathNameToLog4jTemp = tmpDir + Log4jConfigurator.FILE_LOG4J_TEMP; + Properties log4jProperties = new Properties(); + FileInputStream fin = null; + FileOutputStream fout = null; + try { + fin = new FileInputStream(pathNameToLog4j); + log4jProperties.load(fin); + fin.close(); + + // replace any variables + StringHelper.replaceProperties(log4jProperties, System.getProperties()); + + // write this as the temporary log4j file + File logsFileDir = new File(tmpDir); + if (!logsFileDir.exists() && !logsFileDir.mkdirs()) + throw new RuntimeException("Could not create log path " + logsFileDir.getAbsolutePath()); + + fout = new FileOutputStream(pathNameToLog4jTemp); + log4jProperties.store(fout, "Running instance log4j configuration " + new Date()); + fout.close(); + + // XXX if the server is in a web context, then we may not use the FileWatchDog + BasicConfigurator.resetConfiguration(); + watchDog = new Log4jPropertyWatchDog(pathNameToLog4jTemp); + watchDog.start(); + + logger.info("Log4j is configured to use and watch file " + pathNameToLog4jTemp); + + } catch (Exception e) { + Log4jConfigurator.configure(); + logger.error(e, e); + logger.error("Log4j COULD NOT BE INITIALIZED. Please check the " + fileLog4j + " file at " + + pathNameToLog4j); + } finally { + if (fin != null) { + try { + fin.close(); + } catch (IOException e) { + logger.error("Exception closing input file: " + e, e); + } + } + if (fout != null) { + try { + fout.close(); + } catch (IOException e) { + logger.error("Exception closing output file: " + e, e); + } + } + } + } + + /** + * Cleanup a running watch dog + */ + public static synchronized void cleanupOldWatchdog() { + // clean up an old watch dog + if (watchDog != null) { + logger.info("Stopping old Log4j watchdog."); + watchDog.interrupt(); + try { + watchDog.join(1000l); + } catch (InterruptedException e) { + logger.error("Oops. Could not terminate an old WatchDog."); + } finally { + watchDog = null; + } + logger.info("Done."); + } + } +} diff --git a/src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java b/src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java new file mode 100644 index 000000000..fa177b74b --- /dev/null +++ b/src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java @@ -0,0 +1,104 @@ +package ch.eitchnet.utils.helper; + +import java.io.File; + +import org.apache.log4j.LogManager; +import org.apache.log4j.PropertyConfigurator; +import org.apache.log4j.helpers.LogLog; + +/** + * @author Robert von Burg + * + */ +public class Log4jPropertyWatchDog extends Thread { + + /** + * The default delay between every file modification check, set to 60 seconds. + */ + public static final long DEFAULT_DELAY = 60000; + + /** + * The name of the file to observe for changes. + */ + protected String filename; + + /** + * The delay to observe between every check. By default set {@link #DEFAULT_DELAY}. + */ + protected long delay = DEFAULT_DELAY; + + protected File file; + protected long lastModif = 0; + protected boolean warnedAlready = false; + protected boolean interrupted = false; + + /** + * @param filename + */ + protected Log4jPropertyWatchDog(String filename) { + super("FileWatchdog"); + this.filename = filename; + file = new File(filename); + setDaemon(true); + checkAndConfigure(); + } + + /** + * Set the delay to observe between each check of the file changes. + */ + public void setDelay(long delay) { + this.delay = delay; + } + + /** + * + */ + protected void checkAndConfigure() { + boolean fileExists; + try { + fileExists = file.exists(); + } catch (SecurityException e) { + LogLog.warn("Was not allowed to read check file existance, file:[" + filename + "]."); + interrupted = true; // there is no point in continuing + return; + } + + if (fileExists) { + long l = file.lastModified(); // this can also throw a SecurityException + if (l > lastModif) { // however, if we reached this point this + lastModif = l; // is very unlikely. + doOnChange(); + warnedAlready = false; + } + } else { + if (!warnedAlready) { + LogLog.debug("[" + filename + "] does not exist."); + warnedAlready = true; + } + } + } + + /** + * Call {@link PropertyConfigurator#configure(String)} with the filename to reconfigure log4j. + */ + public void doOnChange() { + PropertyConfigurator propertyConfigurator = new PropertyConfigurator(); + propertyConfigurator.doConfigure(filename, LogManager.getLoggerRepository()); + } + + /** + * @see java.lang.Thread#run() + */ + @Override + public void run() { + while (!interrupted) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + // no interruption expected + interrupted = true; + } + checkAndConfigure(); + } + } +} diff --git a/src/ch/eitchnet/utils/helper/StringHelper.java b/src/ch/eitchnet/utils/helper/StringHelper.java new file mode 100644 index 000000000..d8ed6d588 --- /dev/null +++ b/src/ch/eitchnet/utils/helper/StringHelper.java @@ -0,0 +1,397 @@ +package ch.eitchnet.utils.helper; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +import org.apache.log4j.Logger; + +/** + * A helper class to perform different actions on {@link String}s + * + * @author Robert von Burg + */ +public class StringHelper { + + private static final Logger logger = Logger.getLogger(StringHelper.class); + + /** + * Hex char table for fast calculating of hex value + */ + 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"); + + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Something went wrong while converting to HEX: " + e.getLocalizedMessage(), 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."); + + 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 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); + } + + /** + * 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); + } + + /** + * 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); + } + + /** + * 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) { + throw new RuntimeException("Algorithm " + algorithm + " does not exist!", 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) { + + while (value.length() != length) { + if (beginning) { + value = c + value; + } else { + value = value + c; + } + } + + return value; + + } else if (shorten) { + + logger.warn("Shortening length of value: " + value); + logger.warn("Length is: " + value.length() + " max: " + length); + + 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) { + + // keep copy of original value + String origValue = value; + + // get first occurrence of $ character + int pos = -1; + int stop = 0; + + // loop on $ character positions + while ((pos = value.indexOf('$', pos + 1)) != -1) { + + // if pos+1 is not { character then continue + if (value.charAt(pos + 1) != '{') { + continue; + } + + // find end of sequence with } character + stop = value.indexOf('}', pos + 1); + + // if no stop found, then break as another sequence should be able to start + if (stop == -1) { + logger.error("Sequence starts at offset " + pos + " but does not end!"); + value = origValue; + break; + } + + // get sequence enclosed by pos and stop + String sequence = value.substring(pos + 2, stop); + + // make sure sequence doesn't contain $ { } characters + if (sequence.contains("$") || sequence.contains("{") || sequence.contains("}")) { + logger.error("Enclosed sequence in offsets " + pos + " - " + stop + + " contains one of the illegal chars: $ { }: " + sequence); + value = origValue; + break; + } + + // sequence is good, so see if we have a property for it + String property = properties.getProperty(sequence, ""); + + // 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 + value = value.replace("${" + sequence + "}", property); + } + + return value; + } + + /** + * 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 = StringHelper.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 = StringHelper.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 + ". Showing " + maxContext + + " extra characters and start and end:\n"); + sb.append("context s1: " + s1.substring(start, end) + "\n"); + sb.append("context s2: " + s2.substring(start, end) + "\n"); + + return sb.toString(); + } +} diff --git a/src/ch/eitchnet/utils/helper/SystemHelper.java b/src/ch/eitchnet/utils/helper/SystemHelper.java new file mode 100644 index 000000000..070fe7272 --- /dev/null +++ b/src/ch/eitchnet/utils/helper/SystemHelper.java @@ -0,0 +1,73 @@ +package ch.eitchnet.utils.helper; + +/** + * A helper class for {@link System} methods + * + * @author Robert von Burg + */ +public class SystemHelper { + + /** + * 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 { + String property = System.getProperty(key, def); + if (property == null) + throw new RuntimeException("[" + context + "] Property " + key + " is not set, and no default was given!"); + + return property; + } + + /** + * 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(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(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())); + } +} diff --git a/src/ch/eitchnet/utils/objectfilter/ITransactionObject.java b/src/ch/eitchnet/utils/objectfilter/ITransactionObject.java new file mode 100644 index 000000000..a9a88b783 --- /dev/null +++ b/src/ch/eitchnet/utils/objectfilter/ITransactionObject.java @@ -0,0 +1,37 @@ +package ch.eitchnet.utils.objectfilter; + +/** + * This interface serves for objects which are required, at some point, to have a unique ID within a transaction. + * + * @author Michael Gatto + */ +public interface ITransactionObject { + + /** + * UNSET Marker to determine if ids have not been set. + *

    + * Beware: this is set to 0 due to transient field in the {@link ITransactionObject} implementations that store the + * ID, which are set to zero when de-serialized, and that are not allowed to be serialized. + *

    + */ + public static final long UNSET = 0; + + /** + * Set the ID of this object. This ID is unique for this object within the transaction. + * + * @param id + * The ID to set. + */ + public void setTransactionID(long id); + + /** + * @return The ID of this object, as set within the transaction. This ID shall guarantee that it is unique within + * this transaction. + */ + public long getTransactionID(); + + /** + * Reset / anul the transaction ID of this object + */ + public void resetTransactionID(); +} diff --git a/src/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/ch/eitchnet/utils/objectfilter/ObjectCache.java new file mode 100644 index 000000000..9e4aeb134 --- /dev/null +++ b/src/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -0,0 +1,112 @@ +package ch.eitchnet.utils.objectfilter; + +import org.apache.log4j.Logger; + +/** + * This class is a cache for objects whose operations (additions, modifications, removals) are first collected and then + * "deployed" in one go. + *

    + * Thus, this class keeps: + *

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

    + * + * @author Michael Gatto + * + * @param + */ +public class ObjectCache { + + private final static Logger logger = Logger.getLogger(ObjectCache.class); + + /** + * 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; + /** + * object The object that shall be cached + */ + private T object; + /** + * operation The operation that has occurred on this object. + */ + private Operation operation; + + /** + * @param key + * @param object + * @param operation + */ + public ObjectCache(String key, T object, Operation operation) { + + this.id = object.getTransactionID(); + this.key = key; + this.object = object; + this.operation = operation; + + if (logger.isDebugEnabled()) { + logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation + " / " + + object.toString()); + } + } + + /** + * Set the new object version of this cache. + * + * @param object + */ + public void setObject(T object) { + if (logger.isDebugEnabled()) { + logger.debug("Updating ID " + this.id + " to value " + object.toString()); + } + this.object = object; + } + + /** + * Change the operation to execute for this object. + * + * @param newOperation + */ + public void setOperation(Operation newOperation) { + if (logger.isDebugEnabled()) { + logger.debug("Updating Operation of ID " + this.id + " from " + this.operation + " to " + newOperation); + } + this.operation = newOperation; + } + + /** + * @return the id + */ + public long getId() { + return id; + } + + /** + * @return the key + */ + public String getKey() { + return key; + } + + /** + * @return the object + */ + public T getObject() { + return object; + } + + /** + * @return the operation + */ + public Operation getOperation() { + return operation; + } +} diff --git a/src/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/ch/eitchnet/utils/objectfilter/ObjectFilter.java new file mode 100644 index 000000000..76dded822 --- /dev/null +++ b/src/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -0,0 +1,611 @@ +package ch.eitchnet.utils.objectfilter; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.apache.log4j.Logger; + +/** + * This class implements a filter where modifications to an object are collected, and only the most recent action and + * version of the object is returned. + *

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

    + *

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

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Action \ State in CacheN/AAdd(01)Update(O1)Remove(O1)
    Add (O2)Add(O2)Err!Err!Update(O2)
    Update (O2)Update(O2)Add(O2)Update(O2)Err!
    Remove (O2)Remove(O2)N/ARemove(O2)Err!
    + * + * @author Michael Gatto (initial version) + * @author Robert von Burg + * + * @param + */ +public class ObjectFilter { + + // XXX think about removing the generic T, as there is no sense in it + + private final static Logger logger = Logger.getLogger(ObjectFilter.class); + + private HashMap> cache = new HashMap>(); + private HashSet keySet = new HashSet(); + + private static long id = ITransactionObject.UNSET; + + /** + * Register, under the given key, the addition of the given object. + *

    + * This is the single point where the updating logic is applied for the cache in case of addition. The logic is: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Action\State in CacheN/AAdd(01)Update(O1)Remove(O1)
    Add (O2)Add(O2)Err!Err!Update(O2)
    + * + * @param key + * the key to register the object with + * @param objectToAdd + * The object for which addition shall be registered. + */ + public void add(String key, T objectToAdd) { + + if (logger.isDebugEnabled()) + logger.debug("add object " + objectToAdd + " with key " + key); + + // add the key to the set + keySet.add(key); + + // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. + long id = objectToAdd.getTransactionID(); + if (id == ITransactionObject.UNSET) { + // The ID of the object has not been set, so it has not been in the cache during this + // run. Hence, we create an ID and add it to the cache. + id = dispenseID(); + objectToAdd.setTransactionID(id); + ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); + cache.put(id, cacheObj); + } else { + ObjectCache cached = cache.get(Long.valueOf(objectToAdd.getTransactionID())); + if (cached == null) { + // The object got an ID during this run, but was not added to the cache. + // Hence, we add it now, with the current operation. + ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); + cache.put(id, cacheObj); + } else { + String existingKey = cached.getKey(); + if (!existingKey.equals(key)) { + throw new RuntimeException( + "Invalid key provided for object with transaction ID " + + Long.toString(id) + + " and operation " + + Operation.ADD.toString() + + ": existing key is " + + existingKey + + ", new key is " + + key + + ". Object may be present in the same filter instance only once, registered using one key only. Object:" + + 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 RuntimeException("Stale State exception. Invalid + after +"); + case MODIFY: + throw new RuntimeException("Stale State exception. Invalid + after +="); + case REMOVE: + cached.setObject(objectToAdd); + cached.setOperation(Operation.MODIFY); + break; + } // switch + }// else of object not in cache + }// else of ID not set + } + + /** + * Register, under the given key, the update of the given object. *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Action \ State in CacheN/AAdd(01)Update(O1)Remove(O1)
    Update (O2)Update(O2)Add(O2)Update(O2)Err!
    + * + * @param key + * the key to register the object with + * @param objectToUpdate + * The object for which update shall be registered. + */ + public void update(String key, T objectToUpdate) { + + if (logger.isDebugEnabled()) + logger.debug("update object " + objectToUpdate + " with key " + key); + + // add the key to the keyset + keySet.add(key); + // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. + + long id = objectToUpdate.getTransactionID(); + if (id == ITransactionObject.UNSET) { + id = dispenseID(); + objectToUpdate.setTransactionID(id); + ObjectCache cacheObj = new ObjectCache(key, objectToUpdate, Operation.MODIFY); + cache.put(id, cacheObj); + } else { + ObjectCache cached = cache.get(Long.valueOf(objectToUpdate.getTransactionID())); + 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(key, objectToUpdate, Operation.MODIFY); + cache.put(id, cacheObj); + } else { + String existingKey = cached.getKey(); + if (!existingKey.equals(key)) { + + throw new RuntimeException( + "Invalid key provided for object with transaction ID " + + Long.toString(id) + + " and operation " + + Operation.MODIFY.toString() + + ": existing key is " + + existingKey + + ", new key is " + + key + + ". Object may be present in the same filter instance only once, registered using one key only. Object:" + + objectToUpdate.toString()); + } + // The object is in cache: update the version as required. + Operation op = cached.getOperation(); + switch (op) { + case ADD: + cached.setObject(objectToUpdate); + break; + case MODIFY: + cached.setObject(objectToUpdate); + break; + case REMOVE: + throw new RuntimeException("Stale State exception: Invalid += after -"); + } // switch + }// else of object not in cache + }// else of ID not set + } + + /** + * Register, under the given key, the removal of the given object. *

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Action \ State in CacheN/AAdd(01)Update(O1)Remove(O1)
    Remove (O2)Remove(O2)N/ARemove(O2)Err!
    + * + * @param key + * the key to register the object with + * @param objectToRemove + * The object for which removal shall be registered. + */ + public void remove(String key, T objectToRemove) { + + if (logger.isDebugEnabled()) + logger.debug("remove object " + objectToRemove + " with key " + key); + + // add the key to the keyset + keySet.add(key); + // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. + long id = objectToRemove.getTransactionID(); + if (id == ITransactionObject.UNSET) { + id = dispenseID(); + objectToRemove.setTransactionID(id); + ObjectCache cacheObj = new ObjectCache(key, objectToRemove, Operation.REMOVE); + cache.put(id, cacheObj); + } else { + ObjectCache cached = cache.get(Long.valueOf(id)); + 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(key, objectToRemove, Operation.REMOVE); + cache.put(id, cacheObj); + } else { + String existingKey = cached.getKey(); + if (!existingKey.equals(key)) { + throw new RuntimeException( + "Invalid key provided for object with transaction ID " + + Long.toString(id) + + " and operation " + + Operation.REMOVE.toString() + + ": existing key is " + + existingKey + + ", new key is " + + key + + ". Object may be present in the same filter instance only once, registered using one key only. Object:" + + 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. + cache.remove(Long.valueOf(id)); + break; + case MODIFY: + cached.setObject(objectToRemove); + cached.setOperation(Operation.REMOVE); + break; + case REMOVE: + throw new RuntimeException("Stale State exception. Invalid - after -"); + } // switch + } + } + } + + /** + * Register, under the given key, the addition of the given list of objects. + * + * @param key + * the key to register the objects with + * @param addedObjects + * The objects for which addition shall be registered. + */ + public void addAll(String key, Collection addedObjects) { + for (T addObj : addedObjects) { + add(key, addObj); + } + } + + /** + * Register, under the given key, the update of the given list of objects. + * + * @param key + * the key to register the objects with + * @param updatedObjects + * The objects for which update shall be registered. + */ + public void updateAll(String key, Collection updatedObjects) { + for (T update : updatedObjects) { + update(key, update); + } + } + + /** + * Register, under the given key, the removal of the given list of objects. + * + * @param key + * the key to register the objects with + * @param removedObjects + * The objects for which removal shall be registered. + */ + public void removeAll(String key, Collection removedObjects) { + for (T 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(T 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(T 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(T object) { + remove(object.getClass().getName(), object); + } + + /** + * Register the addition of all objects in the list. Since no key is provided, the class name of each object is used + * as a key. + * + * @param objects + * The objects that shall be registered for addition + */ + public void addAll(List objects) { + for (T addedObj : objects) { + add(addedObj.getClass().getName(), addedObj); + } + } + + /** + * Register the updating of all objects in the list. Since no key is provided, the class name of each object is used + * as a key. + * + * @param updateObjects + * The objects that shall be registered for updating + */ + public void updateAll(List updateObjects) { + for (T update : updateObjects) { + update(update.getClass().getName(), update); + } + } + + /** + * Register the removal of all objects in the list. Since no key is provided, the class name of each object is used + * as a key. + * + * @param removedObjects + * The objects that shall be registered for removal + */ + public void removeAll(List removedObjects) { + for (T removed : removedObjects) { + remove(removed.getClass().getName(), removed); + } + } + + /** + * Get all objects that were registered under the given key and that have as a resulting final action an addition. + * + * @param key + * The registration key of the objects to match + * @return The list of all objects registered under the given key and that need to be added. + */ + public List getAdded(String key) { + List addedObjects = new LinkedList(); + Collection> allObjs = cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { + addedObjects.add(objectCache.getObject()); + } + } + return addedObjects; + } + + /** + * Get all objects that were registered under the given key and that have as a resulting final action an addition. + * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. + * @param key + * The registration key of the objects to match + * @return The list of all objects registered under the given key and that need to be added. + */ + public List getAdded(Class clazz, String key) { + List addedObjects = new LinkedList(); + Collection> allObjs = cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + addedObjects.add(object); + } + } + } + return addedObjects; + } + + /** + * Get all objects that were registered under the given key and that have as a resulting final action an update. + * + * @param key + * registration key of the objects to match + * @return The list of all objects registered under the given key and that need to be updated. + */ + public List getUpdated(String key) { + List updatedObjects = new LinkedList(); + Collection> allObjs = 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 key + * registration key of the objects to match + * @return The list of all objects registered under the given key and that need to be updated. + */ + public List getUpdated(Class clazz, String key) { + List updatedObjects = new LinkedList(); + Collection> allObjs = cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + updatedObjects.add(object); + } + } + } + return updatedObjects; + } + + /** + * Get all objects that were registered under the given key that have as a resulting final action their removal. + * + * @param key + * The registration key of the objects to match + * @return The list of object registered under the given key that have, as a final action, removal. + */ + public List getRemoved(String key) { + List removedObjects = new LinkedList(); + Collection> allObjs = 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 key + * The registration key of the objects to match + * @return The list of object registered under the given key that have, as a final action, removal. + */ + public List getRemoved(Class clazz, String key) { + List removedObjects = new LinkedList(); + Collection> allObjs = 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 the objects that were processed in this transaction, that were registered under the given key. No action + * is associated to the object. + * + * @param key + * The registration key for which the objects shall be retrieved + * @return The list of objects matching the given key. + */ + public List getAll(String key) { + List allObjects = new LinkedList(); + Collection> allObjs = cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getKey().equals(key)) { + allObjects.add(objectCache.getObject()); + } + } + return allObjects; + } + + /** + * Get all the objects that were processed in this transaction, that were registered under the given key. No action + * is associated to the object. + * + * @param key + * The registration key for which the objects shall be retrieved + * @return The list of objects matching the given key. + */ + public List> getCache(String key) { + List> allCache = new LinkedList>(); + Collection> allObjs = cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getKey().equals(key)) { + allCache.add(objectCache); + } + } + return allCache; + } + + /** + * Return the set of keys that are currently present in the object filter. + * + * @return The set containing the keys of that have been added to the filter. + */ + public Set keySet() { + return keySet; + } + + /** + * Clear the cache. + */ + public void clearCache() { + cache.clear(); + keySet.clear(); + } + + /** + * @return get a unique transaction ID + */ + public synchronized long dispenseID() { + id++; + if (id == Long.MAX_VALUE) { + logger.error("Rolling IDs of objectFilter back to 1. Hope this is fine."); + id = 1; + } + return id; + } +} diff --git a/src/ch/eitchnet/utils/objectfilter/Operation.java b/src/ch/eitchnet/utils/objectfilter/Operation.java new file mode 100644 index 000000000..1ca926c80 --- /dev/null +++ b/src/ch/eitchnet/utils/objectfilter/Operation.java @@ -0,0 +1,21 @@ +package ch.eitchnet.utils.objectfilter; + +/** + * A discrete set of operations associated to some object / state + * + * @author Michael Gatto + */ +public enum Operation { + /** + * ADD The operation associated to something is addition. + */ + ADD, + /** + * MODIFY The operation associated to something is a modification. + */ + MODIFY, + /** + * REMOVE The operation associated to something is removal + */ + REMOVE; +} From 49569253290f57a2f78ccf1cc15b52c7050fed83 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 8 Jun 2012 19:04:35 +0200 Subject: [PATCH 074/457] [New] Initial commit of the XML Persistence implementation This is a rudimentary implementation which works in that objects can be written to the DB, read back and helper methods like keySet, size are also implemented --- .gitignore | 3 + config/log4j.properties | 12 + src/ch/eitchnet/xmlpers/XmlDao.java | 66 +++ src/ch/eitchnet/xmlpers/XmlDaoFactory.java | 20 + src/ch/eitchnet/xmlpers/XmlFilePersister.java | 428 ++++++++++++++++++ .../xmlpers/XmlPersistenceExecption.java | 23 + .../xmlpers/XmlPersistenceHandler.java | 120 +++++ .../xmlpers/XmlPersistencePathBuilder.java | 144 ++++++ .../xmlpers/XmlPersistenceTransaction.java | 300 ++++++++++++ .../xmlpers/test/XmlPersistenceTest.java | 311 +++++++++++++ .../plugin/xmlpers/test/impl/MyClass.java | 106 +++++ .../plugin/xmlpers/test/impl/MyClassDao.java | 126 ++++++ .../xmlpers/test/impl/MyDaoFactory.java | 34 ++ 13 files changed, 1693 insertions(+) create mode 100644 config/log4j.properties create mode 100644 src/ch/eitchnet/xmlpers/XmlDao.java create mode 100644 src/ch/eitchnet/xmlpers/XmlDaoFactory.java create mode 100644 src/ch/eitchnet/xmlpers/XmlFilePersister.java create mode 100644 src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java create mode 100644 src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java create mode 100644 src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java create mode 100644 src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java create mode 100644 test/ch/eitchnet/featherlite/plugin/xmlpers/test/XmlPersistenceTest.java create mode 100644 test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClass.java create mode 100644 test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClassDao.java create mode 100644 test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyDaoFactory.java diff --git a/.gitignore b/.gitignore index 0f182a034..c77d8a98c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ *.jar *.war *.ear + +# Project files +tmp diff --git a/config/log4j.properties b/config/log4j.properties new file mode 100644 index 000000000..a745f967b --- /dev/null +++ b/config/log4j.properties @@ -0,0 +1,12 @@ +log4j.rootLogger = info, stdout, file + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] %C{1} %M - %m%n + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.File=${user.dir}/logs/app.log +log4j.appender.file.MaxFileSize=10000KB +log4j.appender.file.MaxBackupIndex=0 +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %5p [%t] %C{1} %M - %m%n \ No newline at end of file diff --git a/src/ch/eitchnet/xmlpers/XmlDao.java b/src/ch/eitchnet/xmlpers/XmlDao.java new file mode 100644 index 000000000..d1dd3c456 --- /dev/null +++ b/src/ch/eitchnet/xmlpers/XmlDao.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2010 - 2011 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.xmlpers; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.ContentHandler; + +/** + * @author Robert von Burg + * + */ +public interface XmlDao { + + /** + * @param object + * @return + */ + public String getType(T object); + + /** + * @param object + * @return + */ + public String getSubType(T object); + + /** + * @param object + * @return + */ + public String getId(T object); + + /** + * + * @param object + * @param domImplementation + * @return + */ + // XXX think about returning a document, instead of an element, or use document as input + public Document serializeToDom(T object, DOMImplementation domImplementation); + + /** + * @param element + * @return + */ + public T parseFromDom(Element element); + + /** + * @param object + * @param contentHandler + */ + // XXX Use the XMLSerializer object for serializing to SAX... + public void serializeToSax(T object, ContentHandler contentHandler); + + // XXX parse from SAX is missing... + +} diff --git a/src/ch/eitchnet/xmlpers/XmlDaoFactory.java b/src/ch/eitchnet/xmlpers/XmlDaoFactory.java new file mode 100644 index 000000000..81f929572 --- /dev/null +++ b/src/ch/eitchnet/xmlpers/XmlDaoFactory.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2010 - 2011 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.xmlpers; + +/** + * @author Robert von Burg + * + */ +public interface XmlDaoFactory { + + public XmlDao getDao(String type); +} diff --git a/src/ch/eitchnet/xmlpers/XmlFilePersister.java b/src/ch/eitchnet/xmlpers/XmlFilePersister.java new file mode 100644 index 000000000..c02acfb25 --- /dev/null +++ b/src/ch/eitchnet/xmlpers/XmlFilePersister.java @@ -0,0 +1,428 @@ +package ch.eitchnet.xmlpers; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; + +import org.apache.log4j.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import ch.eitchnet.utils.helper.FileHelper; + +import com.sun.org.apache.xml.internal.serialize.OutputFormat; +import com.sun.org.apache.xml.internal.serialize.XMLSerializer; + +/** + *@author Robert von Burg + * + */ +public class XmlFilePersister { + + // + private static final String XML_DEFAULT_ENCODING = "UTF-8"; + + private static final Logger logger = Logger.getLogger(XmlFilePersister.class); + + private boolean verbose; + private XmlPersistencePathBuilder xmlPathHelper; + + /** + * @param xmlPathHelper + * @param verbose + */ + public XmlFilePersister(XmlPersistencePathBuilder xmlPathHelper, boolean verbose) { + this.xmlPathHelper = xmlPathHelper; + this.verbose = verbose; + } + + /** + * @param type + * @param subType + * @param id + * @param document + */ + public void saveOrUpdate(String type, String subType, String id, Document document) { + + File pathF; + if (subType != null) + pathF = xmlPathHelper.getPathF(type, subType, id); + else + pathF = xmlPathHelper.getPathF(type, id); + + // if this is a new file, then check create parents, if the don't exist + if (!pathF.exists()) { + File parentFile = pathF.getParentFile(); + if (!parentFile.exists() && !parentFile.mkdirs()) { + throw new XmlPersistenceExecption("Could not create path for " + type + " / " + subType + " / " + id + + " at " + pathF.getAbsolutePath()); + } + } + + if (verbose) + logger.info("Persisting " + type + " / " + subType + " / " + id + " to " + pathF.getAbsolutePath() + "..."); + + BufferedOutputStream outStream = null; + try { + + outStream = new BufferedOutputStream(new FileOutputStream(pathF)); + + OutputFormat outputFormat = new OutputFormat("XML", XML_DEFAULT_ENCODING, true); + outputFormat.setIndent(1); + outputFormat.setIndenting(true); + //of.setDoctype(null, null); + + XMLSerializer serializer = new XMLSerializer(outStream, outputFormat); + serializer.asDOMSerializer(); + serializer.serialize(document); + outStream.flush(); + + } catch (Exception e) { + throw new XmlPersistenceExecption("Could not persist object " + type + " / " + subType + " / " + id + + " to " + pathF.getAbsolutePath(), e); + } finally { + if (outStream != null) { + try { + outStream.close(); + } catch (IOException e) { + logger.error(e, e); + } + } + } + + if (verbose) + logger.info("Done."); + } + + /** + * @param type + * @param subType + * @param id + */ + public void remove(String type, String subType, String id) { + + File pathF; + if (subType != null) + pathF = xmlPathHelper.getPathF(type, subType, id); + else + pathF = xmlPathHelper.getPathF(type, id); + + if (verbose) + logger.info("Remove persistence file for " + type + " / " + subType + " / " + id + " from " + + pathF.getAbsolutePath() + "..."); + + if (!pathF.exists()) { + logger.error("Persistence file for " + type + " / " + subType + " / " + id + " does not exist at " + + pathF.getAbsolutePath()); + } else if (!pathF.delete()) { + throw new XmlPersistenceExecption("Could not delete persistence file for " + type + " / " + subType + " / " + + id + " at " + pathF.getAbsolutePath()); + } + + if (verbose) + logger.info("Done."); + } + + /** + * @param type + * @param subType + */ + public void removeAll(String type, String subType) { + + File pathF; + if (subType == null) + pathF = xmlPathHelper.getPathF(type); + else + pathF = xmlPathHelper.getPathF(type, subType); + + if (!pathF.exists()) { + if (subType == null) + logger.error("Path for " + type + " at " + pathF.getAbsolutePath() + + " does not exist, so removing not possible!"); + else + logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + + " does not exist, so removing not possible!"); + + } else { + + File[] filesToRemove = pathF.listFiles(); + boolean removed = FileHelper.deleteFiles(filesToRemove, verbose); + + if (!removed) { + if (subType == null) + throw new XmlPersistenceExecption("Could not delete persistence files for " + type + " at " + + pathF.getAbsolutePath()); + + throw new XmlPersistenceExecption("Could not delete persistence files for " + type + " / " + subType + + " at " + pathF.getAbsolutePath()); + } + } + } + + /** + * + * @param type + * @param subType + * + * @return + */ + public Set queryKeySet(String type, String subType) { + + // if a sub type is required, then it's simple: + if (subType != null) { + + File pathF = this.xmlPathHelper.getPathF(type, subType); + if (!pathF.exists()) { + logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + + " does not exist, so no objects exist!"); + return Collections.emptySet(); + } + + Set keySet = new HashSet(); + for (File f : pathF.listFiles()) { + String name = f.getName(); + keySet.add(name.substring(0, name.length() - XmlPersistencePathBuilder.FILE_EXT.length())); + } + + if (verbose) + logger.info("Found " + keySet.size() + " elements for " + type + " / " + subType); + + return keySet; + } + + // otherwise we need to iterate any existing subTypes and create a combined key set + File pathF = this.xmlPathHelper.getPathF(type); + if (!pathF.exists()) { + logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + + " does not exist, so no objects exist!"); + return Collections.emptySet(); + } + + Set keySet = new HashSet(); + + File[] subTypeFiles = pathF.listFiles(); + for (File subTypeFile : subTypeFiles) { + + if (subTypeFile.isFile()) { + keySet.add(xmlPathHelper.getId(subTypeFile.getName())); + } else { + + for (File f : subTypeFile.listFiles()) { + keySet.add(xmlPathHelper.getId(f.getName())); + } + } + } + + if (verbose) + logger.info("Found " + keySet.size() + " elements for " + type); + + return keySet; + } + + /** + * + * @param type + * @param subType + * + * @return + */ + public long querySize(String type, String subType) { + + // if a sub type is required, then it's simple: + if (subType != null) { + + File pathF = this.xmlPathHelper.getPathF(type, subType); + if (!pathF.exists()) { + logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + + " does not exist, so no objects exist!"); + return 0l; + } + + int length = pathF.listFiles().length; + + if (verbose) + logger.info("Found " + length + " elements for " + type + " / " + subType); + + return length; + } + + // otherwise we need to iterate any existing sub types and + // return the size of the combined collection + + File pathF = this.xmlPathHelper.getPathF(type); + if (!pathF.exists()) { + logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + + " does not exist, so no objects exist!"); + return 0l; + } + + long numberOfFiles = 0l; + + File[] subTypeFiles = pathF.listFiles(); + for (File subTypeFile : subTypeFiles) { + + if (subTypeFile.isFile()) { + numberOfFiles++; + } else { + numberOfFiles += subTypeFile.listFiles().length; + } + } + + if (verbose) + logger.info("Found " + numberOfFiles + " elements for " + type); + + return numberOfFiles; + } + +// XXX think about allowing paged loading... +// /** +// * @param type +// * @param subType +// * @param firstResult +// * @param maxResults +// * +// * @return +// */ +// public List queryFrom(String type, String subType, int firstResult, int maxResults) { +// +// File pathF = this.xmlPathHelper.getPathF(type, subType); +// if (!pathF.exists()) { +// logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() +// + " does not exist, so no objects exist!"); +// return Collections.emptyList(); +// } +// +// File[] listFiles = pathF.listFiles(); +// Arrays.sort(listFiles, new Comparator() { +// +// @Override +// public int compare(File file1, File file2) { +// return file1.getName().compareTo(file2.getName()); +// } +// }); +// +// // make sure positions are not illegal +// int size = listFiles.length; +// if (firstResult >= size) +// return Collections.emptyList(); +// +// if ((firstResult + maxResults) > size) +// maxResults = size - firstResult; +// +// File[] result = Arrays.copyOfRange(listFiles, firstResult, firstResult + maxResults); +// +// List list = new ArrayList(); +// for (File f : result) { +// list.add(loadObject(clazz, f)); +// } +// +// return list; +// } + + /** + * + * @param type + * @param subType + * + * @return + */ + public List queryAll(String type, String subType, DocumentBuilder docBuilder) { + + // if a sub type is required, then it's simple: + if (subType != null) { + + File pathF = this.xmlPathHelper.getPathF(type, subType); + if (!pathF.exists()) { + logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + + " does not exist, so no objects exist!"); + return Collections.emptyList(); + } + + List list = new ArrayList(); + for (File subTypeF : pathF.listFiles()) { + list.add(parseFile(subTypeF, docBuilder)); + } + + if (verbose) + logger.info("Loaded " + list.size() + " elements for " + type + " / " + subType); + + return list; + } + + // otherwise we need to iterate any existing sub types and + // return those elements as well + + File pathF = this.xmlPathHelper.getPathF(type); + if (!pathF.exists()) { + logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + + " does not exist, so no objects exist!"); + return Collections.emptyList(); + } + + List list = new ArrayList(); + + File[] subTypeFiles = pathF.listFiles(); + for (File subTypeFile : subTypeFiles) { + + if (subTypeFile.isFile()) { + list.add(parseFile(subTypeFile, docBuilder)); + + } else { + for (File subTypeF : subTypeFile.listFiles()) { + list.add(parseFile(subTypeF, docBuilder)); + } + } + } + + if (verbose) + logger.info("Loaded " + list.size() + " elements for " + type); + + return list; + } + + /** + * + * @param type + * @param subType + * @param id + * + * @return + */ + public Element queryById(String type, String subType, String id, DocumentBuilder docBuilder) { + + File pathF = this.xmlPathHelper.getPathF(type, subType, id); + if (!pathF.exists()) { + logger.error("Path for " + type + " / " + subType + " / " + id + " at " + pathF.getAbsolutePath() + + " does not exist, so object does not exist!"); + return null; + } + + return parseFile(pathF, docBuilder); + } + + /** + * @param subTypeF + * @return + */ + private Element parseFile(File subTypeF, DocumentBuilder docBuilder) { + try { + + Document document = docBuilder.parse(subTypeF); + return document.getDocumentElement(); + + } catch (SAXException e) { + throw new XmlPersistenceExecption("Failed to parse file " + subTypeF.getAbsolutePath(), e); + } catch (IOException e) { + throw new XmlPersistenceExecption("Failed to read file " + subTypeF.getAbsolutePath(), e); + } + } +} diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java b/src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java new file mode 100644 index 000000000..868f7d83f --- /dev/null +++ b/src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java @@ -0,0 +1,23 @@ +package ch.eitchnet.xmlpers; + +/** + * @author Robert von Burg + */ +public class XmlPersistenceExecption extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * @param message + * @param cause + */ + public XmlPersistenceExecption(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param message + */ + public XmlPersistenceExecption(String message) { + super(message); + } +} diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java b/src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java new file mode 100644 index 000000000..0681a2c2a --- /dev/null +++ b/src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java @@ -0,0 +1,120 @@ +package ch.eitchnet.xmlpers; + +import org.apache.log4j.Logger; + +import ch.eitchnet.utils.helper.SystemHelper; +import ch.eitchnet.utils.objectfilter.ITransactionObject; +import ch.eitchnet.utils.objectfilter.ObjectFilter; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceHandler { + + /** + * + */ + public static final String CONFIG_VERBOSE = "ch.eitchnet.xmlpers.config.verbose"; + + /** + * + */ + public static final String CONFIG_BASEPATH = "ch.eitchnet.xmlpers.config.basepath"; + + /** + * + */ + public static final String CONFIG_DAO_FACTORY_CLASS = "ch.eitchnet.xmlpers.config.daoFactoryClass"; + + protected static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); + + protected boolean verbose; + protected ThreadLocal xmlPersistenceTxThreadLocal; + protected XmlFilePersister persister; + protected XmlDaoFactory xmlDaoFactory; + + /** + * + */ + public void initialize() { + + String basePath = SystemHelper.getProperty(XmlPersistenceHandler.class.getSimpleName(), CONFIG_BASEPATH, null); + verbose = SystemHelper.getPropertyBool(XmlPersistenceHandler.class.getSimpleName(), CONFIG_VERBOSE, + Boolean.FALSE).booleanValue(); + + // get class to use as transaction + String daoFactoryClassName = SystemHelper.getProperty(XmlPersistenceHandler.class.getSimpleName(), + CONFIG_DAO_FACTORY_CLASS, null); + try { + @SuppressWarnings("unchecked") + Class xmlDaoFactoryClass = (Class) Class.forName(daoFactoryClassName); + + xmlDaoFactory = xmlDaoFactoryClass.newInstance(); + + } catch (ClassNotFoundException e) { + throw new XmlPersistenceExecption("XmlDaoFactory class does not exist " + daoFactoryClassName, e); + } catch (Exception e) { + throw new XmlPersistenceExecption("Failed to load class " + daoFactoryClassName, e); + } + + XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(basePath); + persister = new XmlFilePersister(pathBuilder, verbose); + + // initialize the Thread local object which is used per transaction + xmlPersistenceTxThreadLocal = new ThreadLocal(); + } + + /** + * + */ + public XmlPersistenceTransaction openTx() { + + if (verbose) + logger.info("Opening new transaction..."); + + // make sure no previous filter exists + XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); + if (xmlPersistenceTx != null) + throw new XmlPersistenceExecption("Previous transaction not properly closed"); + + // set a new persistence transaction object + ObjectFilter objectFilter = new ObjectFilter(); + xmlPersistenceTx = new XmlPersistenceTransaction(); + xmlPersistenceTx.initialize(persister, xmlDaoFactory, objectFilter, verbose); + + this.xmlPersistenceTxThreadLocal.set(xmlPersistenceTx); + + return xmlPersistenceTx; + } + + /** + * + */ + public XmlPersistenceTransaction getTx() { + XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); + if (xmlPersistenceTx == null) + throw new XmlPersistenceExecption("No transaction currently open!"); + + return xmlPersistenceTx; + } + + /** + * + */ + public void commitTx() { + + if (verbose) + logger.info("Committing transaction..."); + + try { + XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); + if (xmlPersistenceTx == null) + throw new XmlPersistenceExecption("No transaction currently open!"); + + xmlPersistenceTx.commitTx(); + } finally { + this.xmlPersistenceTxThreadLocal.set(null); + } + } +} diff --git a/src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java b/src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java new file mode 100644 index 000000000..a8ed1d638 --- /dev/null +++ b/src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java @@ -0,0 +1,144 @@ +package ch.eitchnet.xmlpers; + +import java.io.File; +import java.io.IOException; + +import org.apache.log4j.Logger; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistencePathBuilder { + private static final Logger logger = Logger.getLogger(XmlPersistencePathBuilder.class); + + /** + * + */ + public static final String FILE_EXT = ".xml"; + + /** + * + */ + public static final int EXT_LENGTH = FILE_EXT.length(); + + private String basePath; + + /** + * @param basePath + */ + public XmlPersistencePathBuilder(String basePath) { + File basePathF = new File(basePath); + if (!basePathF.exists()) + throw new XmlPersistenceExecption("The database store path does not exist at " + + basePathF.getAbsolutePath()); + if (!basePathF.canWrite()) + throw new XmlPersistenceExecption("The database store path is not writeable at " + + basePathF.getAbsolutePath()); + + try { + this.basePath = basePathF.getCanonicalPath(); + } catch (IOException e) { + throw new XmlPersistenceExecption("Failed to build canonical path from " + basePath, e); + } + + logger.info("Using base path " + basePath); + } + + /** + * @param id + * @return + */ + public String getFilename(String id) { + return id.concat(FILE_EXT); + } + + /** + * @param filename + * @return + */ + public String getId(String filename) { + if (filename.charAt(filename.length() - EXT_LENGTH) != '.') + throw new XmlPersistenceExecption("The filename does not have a . at index " + + (filename.length() - EXT_LENGTH)); + + return filename.substring(0, filename.length() - EXT_LENGTH); + } + + /** + * @param type + * + * @return + */ + public String getPath(String type) { + + StringBuilder sb = new StringBuilder(basePath); + sb.append("/"); + sb.append(type); + + return sb.toString(); + } + + /** + * @param type + * + * @return + */ + public File getPathF(String type) { + return new File(getPath(type)); + } + + /** + * @param type + * @param subType + * @return + */ + public String getPath(String type, String subType) { + + StringBuilder sb = new StringBuilder(basePath); + sb.append("/"); + sb.append(type); + sb.append("/"); + sb.append(subType); + + return sb.toString(); + } + + /** + * @param type + * @param subType + * @return + */ + public File getPathF(String type, String subType) { + return new File(getPath(type, subType)); + } + + /** + * @param type + * @param subType + * @param id + * @return + */ + public String getPath(String type, String subType, String id) { + + StringBuilder sb = new StringBuilder(basePath); + sb.append("/"); + sb.append(type); + sb.append("/"); + sb.append(subType); + sb.append("/"); + sb.append(getFilename(id)); + + return sb.toString(); + } + + /** + * @param type + * @param subType + * @param id + * @return + */ + public File getPathF(String type, String subType, String id) { + return new File(getPath(type, subType, id)); + } +} diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java new file mode 100644 index 000000000..1e3dfeba8 --- /dev/null +++ b/src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2010 - 2011 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.xmlpers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.log4j.Logger; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.utils.objectfilter.ITransactionObject; +import ch.eitchnet.utils.objectfilter.ObjectFilter; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceTransaction { + + private static final Logger logger = Logger.getLogger(XmlPersistenceTransaction.class); + + private boolean verbose; + private XmlFilePersister persister; + private XmlDaoFactory xmlDaoFactory; + private ObjectFilter objectFilter; + private DocumentBuilder docBuilder; + private DOMImplementation domImplementation; + + /** + * @param persister + * @param xmlDaoFactory + * @param objectFilter + */ + public void initialize(XmlFilePersister persister, XmlDaoFactory xmlDaoFactory, + ObjectFilter objectFilter, boolean verbose) { + this.persister = persister; + this.xmlDaoFactory = xmlDaoFactory; + this.objectFilter = objectFilter; + this.verbose = verbose; + } + + private DocumentBuilder getDocBuilder() { + if (docBuilder == null) { + try { + docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new XmlPersistenceExecption("Failed to load document builder: " + e.getLocalizedMessage(), e); + } + } + return docBuilder; + } + + /** + * @return + */ + protected DOMImplementation getDomImpl() { + if (domImplementation == null) + domImplementation = getDocBuilder().getDOMImplementation(); + return domImplementation; + } + + /* + * modifying methods + */ + + /** + * @param object + */ + public void add(ITransactionObject object) { + this.objectFilter.add(object); + } + + /** + * @param objects + */ + public void addAll(List objects) { + this.objectFilter.addAll(objects); + } + + /** + * @param object + */ + public void update(ITransactionObject object) { + this.objectFilter.update(object); + } + + /** + * @param objects + */ + public void updateAll(List objects) { + this.objectFilter.updateAll(objects); + } + + /** + * @param object + */ + public void remove(ITransactionObject object) { + this.objectFilter.remove(object); + } + + /** + * @param objects + */ + public void removeAll(List objects) { + this.objectFilter.removeAll(objects); + } + + /* + * querying methods + */ + + /** + * @param type + * @return + */ + public Set queryKeySet(String type) { + return queryKeySet(type, null); + } + + /** + * @param type + * @param subType + * @return + */ + public Set queryKeySet(String type, String subType) { + return this.persister.queryKeySet(type, subType); + } + + /** + * @param type + * @return + */ + public long querySize(String type) { + return querySize(type, null); + } + + /** + * @param type + * @param subType + * @return + */ + public long querySize(String type, String subType) { + return this.persister.querySize(type, subType); + } + + /** + * @param type + * @return + */ + public List queryAll(String type) { + return queryAll(type, null); + } + + /** + * @param type + * @param subType + * @return + */ + public List queryAll(String type, String subType) { + + // XXX ok, this is very ugly, but for starters it will have to do + XmlDao dao = xmlDaoFactory.getDao(type); + + List elements = this.persister.queryAll(type, subType, getDocBuilder()); + List objects = new ArrayList(elements.size()); + + for (Element element : elements) { + @SuppressWarnings("unchecked") + T object = (T) dao.parseFromDom(element); + objects.add(object); + } + + return objects; + } + + /** + * @param type + * @param id + * @return + */ + public T queryById(String type, String id) { + return queryById(type, null, id); + } + + /** + * @param type + * @param subType + * @param id + * @return + */ + public T queryById(String type, String subType, String id) { + + XmlDao dao = xmlDaoFactory.getDao(type); + + Element element = this.persister.queryById(type, subType, id, getDocBuilder()); + if (element == null) + throw new XmlPersistenceExecption("No object exists for " + type + " / " + subType + " / " + id); + + @SuppressWarnings("unchecked") + T object = (T) dao.parseFromDom(element); + + return object; + } + + /* + * committing + */ + + /** + * + */ + void commitTx() { + + if (verbose) + logger.info("Committing..."); + + Set keySet = objectFilter.keySet(); + if (keySet.isEmpty()) + return; + + for (String key : keySet) { + + XmlDao dao = xmlDaoFactory.getDao(key); + + List removed = objectFilter.getRemoved(key); + if (removed.isEmpty()) { + if (verbose) + logger.info("No objects removed in this tx."); + } else { + if (verbose) + logger.info(removed.size() + " objects removed in this tx."); + + for (ITransactionObject object : removed) { + + String type = dao.getType(object); + String subType = dao.getSubType(object); + String id = dao.getId(object); + + persister.remove(type, subType, id); + } + } + + List updated = objectFilter.getUpdated(key); + if (updated.isEmpty()) { + if (verbose) + logger.info("No objects updated in this tx."); + } else { + if (verbose) + logger.info(updated.size() + " objects updated in this tx."); + + for (ITransactionObject object : updated) { + + String type = dao.getType(object); + String subType = dao.getSubType(object); + String id = dao.getId(object); + + Document asDom = dao.serializeToDom(object, getDomImpl()); + persister.saveOrUpdate(type, subType, id, asDom); + } + } + + List added = objectFilter.getAdded(key); + if (added.isEmpty()) { + if (verbose) + logger.info("No objects added in this tx."); + } else { + if (verbose) + logger.info(updated.size() + " objects added in this tx."); + + for (ITransactionObject object : added) { + + String type = dao.getType(object); + String subType = dao.getSubType(object); + String id = dao.getId(object); + + Document asDom = dao.serializeToDom(object, getDomImpl()); + persister.saveOrUpdate(type, subType, id, asDom); + } + } + } + + objectFilter.clearCache(); + logger.info("Completed TX"); + } +} diff --git a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/XmlPersistenceTest.java b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/XmlPersistenceTest.java new file mode 100644 index 000000000..be194acbe --- /dev/null +++ b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/XmlPersistenceTest.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2010 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ + +/** + * + */ +package ch.eitchnet.featherlite.plugin.xmlpers.test; + +import java.io.File; +import java.util.List; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.featherlite.plugin.xmlpers.test.impl.MyClass; +import ch.eitchnet.featherlite.plugin.xmlpers.test.impl.MyDaoFactory; +import ch.eitchnet.utils.helper.Log4jConfigurator; +import ch.eitchnet.utils.objectfilter.ITransactionObject; +import ch.eitchnet.xmlpers.XmlPersistenceExecption; +import ch.eitchnet.xmlpers.XmlPersistenceHandler; +import ch.eitchnet.xmlpers.XmlPersistenceTransaction; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceTest { + + private static final Logger logger = Logger.getLogger(XmlPersistenceTest.class.getName()); + + private static XmlPersistenceHandler persistenceHandler; + + /** + * @throws Exception + * if something goes wrong + */ + @BeforeClass + public static void init() throws Exception { + + try { + // set up log4j + Log4jConfigurator.configure(); + + String userDir = System.getProperty("user.dir"); + String basePath = userDir + "/tmp/testdb"; + File basePathF = new File(basePath); + if (!basePathF.exists() && !basePathF.mkdirs()) + Assert.fail("Could not create temporaray database store in " + basePathF.getAbsolutePath()); + + System.setProperty(XmlPersistenceHandler.CONFIG_BASEPATH, "tmp/testdb"); + System.setProperty(XmlPersistenceHandler.CONFIG_VERBOSE, "true"); + System.setProperty(XmlPersistenceHandler.CONFIG_DAO_FACTORY_CLASS, MyDaoFactory.class.getName()); + + persistenceHandler = new XmlPersistenceHandler(); + persistenceHandler.initialize(); + + logger.info("Initialized persistence handler."); + + } catch (Exception e) { + logger.error(e, e); + + throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); + } + } + + /** + * + */ + @Test + public void testCreate() { + + try { + logger.info("Trying to create..."); + + // new instance + MyClass myClass = new MyClass("@id", "@name", "@subtype"); + + // persist instance + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + tx.add(myClass); + persistenceHandler.commitTx(); + + logger.info("Done creating."); + + } catch (Exception e) { + logger.error(e, e); + Assert.fail("Failed: " + e.getLocalizedMessage()); + } + } + + /** + * + */ + @Test + public void testRead() { + + try { + logger.info("Trying to read..."); + + // query MyClass with id @id + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); + logger.info("Found MyClass: " + myClass); + persistenceHandler.commitTx(); + + logger.info("Done reading."); + + } catch (Exception e) { + logger.error(e, e); + Assert.fail("Failed: " + e.getLocalizedMessage()); + } + } + + /** + * + */ + @Test + public void testUpdate() { + + try { + logger.info("Trying to update an object..."); + + // query the instance + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); + logger.info("Found MyClass: " + myClass); + + // modify the instance + myClass.setName("@name_modified"); + + // update the instance + tx.update(myClass); + persistenceHandler.commitTx(); + + logger.info("Done updating."); + + } catch (Exception e) { + logger.error(e, e); + Assert.fail("Failed: " + e.getLocalizedMessage()); + } + } + + /** + * + */ + @Test + public void testRemove() { + + logger.info("Trying to remove..."); + + // query the instance + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); + logger.info("Found MyClass: " + myClass); + + tx.remove(myClass); + persistenceHandler.commitTx(); + + logger.info("Done removing."); + } + + /** + * + */ + @Test(expected = XmlPersistenceExecption.class) + public void testQueryFail() { + + try { + logger.info("Trying to query removed object..."); + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); + logger.info("Found MyClass: " + myClass); + logger.info("Done querying removed object"); + } finally { + persistenceHandler.commitTx(); + } + } + + /** + * + */ + @Test + public void testReCreate() { + + try { + logger.info("Trying to recreate..."); + + // new instance + MyClass myClass = new MyClass("@id", "@name", "@subtype"); + + // persist instance + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + tx.add(myClass); + persistenceHandler.commitTx(); + + logger.info("Done creating."); + + } catch (Exception e) { + logger.error(e, e); + Assert.fail("Failed: " + e.getLocalizedMessage()); + } + } + +// /** +// * +// */ +// @Test +// public void testQueryFromTo() { +// Assert.fail("Not yet implemented"); +// } + + /** + * + */ + @Test + public void testQueryAll() { + + try { + + logger.info("Trying to query all..."); + + // query all + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + List list = tx.queryAll(MyClass.class.getName()); + Assert.assertTrue("Expected only one object, found " + list.size(), list.size() == 1); + + // also with subtype + list = tx.queryAll(MyClass.class.getName(), "@subtype"); + Assert.assertTrue("Expected only one object, found " + list.size(), list.size() == 1); + + // and now something useless + list = tx.queryAll(MyClass.class.getName(), "@inexistant"); + Assert.assertTrue("Expected no objects, found " + list.size(), list.size() == 0); + + logger.info("Done querying."); + + } finally { + persistenceHandler.commitTx(); + } + } + + /** + * + */ + @Test + public void testKeySet() { + + try { + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + + Set keySet = tx.queryKeySet(MyClass.class.getName()); + Assert.assertTrue("Expected one key, found " + keySet.size(), keySet.size() == 1); + + // also with subtype + keySet = tx.queryKeySet(MyClass.class.getName(), "@subtype"); + Assert.assertTrue("Expected one key, found " + keySet.size(), keySet.size() == 1); + + // and now something useless + keySet = tx.queryKeySet(MyClass.class.getName(), "@inexistant"); + Assert.assertTrue("Expected no keys, found " + keySet, keySet.size() == 0); + + } finally { + persistenceHandler.commitTx(); + } + } + + /** + * + */ + @Test + public void testRemoveAll() { + + try { + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + + List objects = tx.queryAll(MyClass.class.getName(), "@subType"); + tx.removeAll(objects); + + } finally { + persistenceHandler.commitTx(); + } + } + + /** + * + */ + @Test + public void testSize() { + + try { + XmlPersistenceTransaction tx = persistenceHandler.openTx(); + + long size = tx.querySize(MyClass.class.getName(), "@subType"); + Assert.assertTrue("Expected size = 0, found: " + size, size == 0); + + } finally { + persistenceHandler.commitTx(); + } + } +} diff --git a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClass.java b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClass.java new file mode 100644 index 000000000..9b333a08e --- /dev/null +++ b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClass.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2010 - 2011 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.featherlite.plugin.xmlpers.test.impl; + +import ch.eitchnet.utils.objectfilter.ITransactionObject; + +/** + * @author Robert von Burg + * + */ +public class MyClass implements ITransactionObject { + + private long txId; + private String id; + private String name; + private String type; + + /** + * @param id + * @param name + * @param type + */ + public MyClass(String id, String name, String type) { + super(); + this.id = id; + this.name = name; + this.type = type; + } + + /** + * @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 name + */ + public String getName() { + return this.name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the type + */ + public String getType() { + return this.type; + } + + /** + * @param type + * the type to set + */ + public void setType(String type) { + this.type = type; + } + + /** + * @see ch.eitchnet.utils.objectfilter.ITransactionObject#setTransactionID(long) + */ + @Override + public void setTransactionID(long id) { + this.txId = id; + } + + /** + * @see ch.eitchnet.utils.objectfilter.ITransactionObject#getTransactionID() + */ + @Override + public long getTransactionID() { + return this.txId; + } + + /** + * @see ch.eitchnet.utils.objectfilter.ITransactionObject#resetTransactionID() + */ + @Override + public void resetTransactionID() { + this.txId = ITransactionObject.UNSET; + } +} diff --git a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClassDao.java b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClassDao.java new file mode 100644 index 000000000..ec148642d --- /dev/null +++ b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClassDao.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2010 - 2011 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.featherlite.plugin.xmlpers.test.impl; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import ch.eitchnet.xmlpers.XmlDao; + +/** + * @author Robert von Burg + * + */ +public class MyClassDao implements XmlDao { + + /** + * @see ch.eitchnet.xmlpers.XmlDao#getType(java.lang.Object) + */ + @Override + public String getType(MyClass object) { + return MyClass.class.getName(); + } + + /** + * @see ch.eitchnet.xmlpers.XmlDao#getSubType(java.lang.Object) + */ + @Override + public String getSubType(MyClass object) { + return object.getType(); + } + + /** + * @see ch.eitchnet.xmlpers.XmlDao#getId(java.lang.Object) + */ + @Override + public String getId(MyClass object) { + return object.getId(); + } + + /** + * @see ch.eitchnet.xmlpers.XmlDao#serializeToDom(java.lang.Object, org.w3c.dom.DOMImplementation) + */ + @Override + public Document serializeToDom(MyClass object, DOMImplementation domImplementation) { + + Document document = domImplementation.createDocument(null, null, null); + Element element = document.createElement("MyClass"); + document.appendChild(element); + + element.setAttribute("id", object.getId()); + element.setAttribute("type", object.getType()); + + Element nameElement = document.createElement("Name"); + element.appendChild(nameElement); + Text textNode = document.createTextNode(object.getName()); + nameElement.appendChild(textNode); + + return document; + } + + /** + * @see ch.eitchnet.xmlpers.XmlDao#parseFromDom(org.w3c.dom.Element) + */ + @Override + public MyClass parseFromDom(Element element) { + + String id = element.getAttribute("id"); + String type = element.getAttribute("type"); + Element nameElement = (Element) element.getElementsByTagName("Name").item(0); + String name = nameElement.getTextContent(); + + MyClass myClass = new MyClass(id, name, type); + + return myClass; + } + + /** + * @see ch.eitchnet.xmlpers.XmlDao#serializeToSax(java.lang.Object, org.xml.sax.ContentHandler) + */ + @Override + public void serializeToSax(MyClass object, ContentHandler contentHandler) { + + try { + contentHandler.startDocument(); + + // MyClass element / root + { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", "id", "", object.getId()); + atts.addAttribute("", "", "type", "", object.getType()); + contentHandler.startElement("", "", "MyClass", atts); + + // name element + { + contentHandler.startElement("", "", "Name", null); + char[] nameArr = object.getName().toCharArray(); + contentHandler.characters(nameArr, 0, nameArr.length); + contentHandler.endElement("", "", "name"); + } + + // MyClass end + contentHandler.endElement("", "", "MyClass"); + } + + // end document + contentHandler.endDocument(); + + } catch (SAXException e) { + throw new RuntimeException("Failed to serialize " + object + " to SAX", e); + } + } + +} diff --git a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyDaoFactory.java b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyDaoFactory.java new file mode 100644 index 000000000..1a2b4fcd7 --- /dev/null +++ b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyDaoFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010 - 2011 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.featherlite.plugin.xmlpers.test.impl; + +import ch.eitchnet.xmlpers.XmlDao; +import ch.eitchnet.xmlpers.XmlDaoFactory; + +/** + * @author Robert von Burg + * + */ +public class MyDaoFactory implements XmlDaoFactory { + + /** + * @see ch.eitchnet.xmlpers.XmlDaoFactory#getDao(java.lang.String) + */ + @SuppressWarnings("unchecked") + @Override + public XmlDao getDao(String type) { + if (type.equals(MyClass.class.getName())) + return (XmlDao) new MyClassDao(); + + throw new RuntimeException("Class with type " + type + " is unknown!"); + } + +} From 97bd5cf5a4ca3f9ee1c7adb7fee4c3543c265db7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 14 Jun 2012 23:38:50 +0200 Subject: [PATCH 075/457] [New] Added packaging information and files and licence This includes setting the licence to LGPL, setting the headers, adding a build script and testing the build --- COPYING | 674 ++++++++++++++++++ COPYING.LESSER | 165 +++++ build.xml | 73 ++ src/ch/eitchnet/rmi/RMIFileClient.java | 24 + src/ch/eitchnet/rmi/RmiFileDeletion.java | 24 + src/ch/eitchnet/rmi/RmiFileHandler.java | 24 + src/ch/eitchnet/rmi/RmiFilePart.java | 24 + src/ch/eitchnet/rmi/RmiHelper.java | 24 +- src/ch/eitchnet/utils/helper/FileHelper.java | 24 + .../utils/helper/Log4jConfigurator.java | 24 + .../utils/helper/Log4jPropertyWatchDog.java | 24 + .../eitchnet/utils/helper/StringHelper.java | 24 + .../eitchnet/utils/helper/SystemHelper.java | 24 + .../objectfilter/ITransactionObject.java | 24 + .../utils/objectfilter/ObjectCache.java | 24 + .../utils/objectfilter/ObjectFilter.java | 24 + .../utils/objectfilter/Operation.java | 24 + 17 files changed, 1243 insertions(+), 5 deletions(-) create mode 100644 COPYING create mode 100644 COPYING.LESSER create mode 100644 build.xml diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..ee31a36fb --- /dev/null +++ b/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cleaning build folder ${buildFolder} + + + + + + + Cleaning dist folder ${distFolder} + + + + + + + diff --git a/src/ch/eitchnet/rmi/RMIFileClient.java b/src/ch/eitchnet/rmi/RMIFileClient.java index de93bb95a..d5ad7c2cd 100644 --- a/src/ch/eitchnet/rmi/RMIFileClient.java +++ b/src/ch/eitchnet/rmi/RMIFileClient.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.rmi; import java.rmi.RemoteException; diff --git a/src/ch/eitchnet/rmi/RmiFileDeletion.java b/src/ch/eitchnet/rmi/RmiFileDeletion.java index 91e85c761..0c68caba3 100644 --- a/src/ch/eitchnet/rmi/RmiFileDeletion.java +++ b/src/ch/eitchnet/rmi/RmiFileDeletion.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.rmi; import java.io.Serializable; diff --git a/src/ch/eitchnet/rmi/RmiFileHandler.java b/src/ch/eitchnet/rmi/RmiFileHandler.java index c2a55a70c..47aba008b 100644 --- a/src/ch/eitchnet/rmi/RmiFileHandler.java +++ b/src/ch/eitchnet/rmi/RmiFileHandler.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.rmi; import java.io.File; diff --git a/src/ch/eitchnet/rmi/RmiFilePart.java b/src/ch/eitchnet/rmi/RmiFilePart.java index a4a355a37..8bddfed1c 100644 --- a/src/ch/eitchnet/rmi/RmiFilePart.java +++ b/src/ch/eitchnet/rmi/RmiFilePart.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.rmi; import java.io.Serializable; diff --git a/src/ch/eitchnet/rmi/RmiHelper.java b/src/ch/eitchnet/rmi/RmiHelper.java index 4755dce00..96aa27879 100644 --- a/src/ch/eitchnet/rmi/RmiHelper.java +++ b/src/ch/eitchnet/rmi/RmiHelper.java @@ -1,11 +1,25 @@ /* - * Copyright (c) 2010 - 2011 + * Copyright (c) 2012 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ package ch.eitchnet.rmi; diff --git a/src/ch/eitchnet/utils/helper/FileHelper.java b/src/ch/eitchnet/utils/helper/FileHelper.java index 9e9de3735..4f310ec8a 100644 --- a/src/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/ch/eitchnet/utils/helper/FileHelper.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.helper; import java.io.BufferedInputStream; diff --git a/src/ch/eitchnet/utils/helper/Log4jConfigurator.java b/src/ch/eitchnet/utils/helper/Log4jConfigurator.java index 09150aa6b..e70672640 100644 --- a/src/ch/eitchnet/utils/helper/Log4jConfigurator.java +++ b/src/ch/eitchnet/utils/helper/Log4jConfigurator.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.helper; import java.io.File; diff --git a/src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java b/src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java index fa177b74b..a41f8dc2a 100644 --- a/src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java +++ b/src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.helper; import java.io.File; diff --git a/src/ch/eitchnet/utils/helper/StringHelper.java b/src/ch/eitchnet/utils/helper/StringHelper.java index d8ed6d588..c5807b2c1 100644 --- a/src/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/ch/eitchnet/utils/helper/StringHelper.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.helper; import java.io.UnsupportedEncodingException; diff --git a/src/ch/eitchnet/utils/helper/SystemHelper.java b/src/ch/eitchnet/utils/helper/SystemHelper.java index 070fe7272..2449d9038 100644 --- a/src/ch/eitchnet/utils/helper/SystemHelper.java +++ b/src/ch/eitchnet/utils/helper/SystemHelper.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.helper; /** diff --git a/src/ch/eitchnet/utils/objectfilter/ITransactionObject.java b/src/ch/eitchnet/utils/objectfilter/ITransactionObject.java index a9a88b783..a243ed9ba 100644 --- a/src/ch/eitchnet/utils/objectfilter/ITransactionObject.java +++ b/src/ch/eitchnet/utils/objectfilter/ITransactionObject.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Michael Gatto + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.objectfilter; /** diff --git a/src/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/ch/eitchnet/utils/objectfilter/ObjectCache.java index 9e4aeb134..b329c4f0b 100644 --- a/src/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/src/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Michael Gatto + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.objectfilter; import org.apache.log4j.Logger; diff --git a/src/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/ch/eitchnet/utils/objectfilter/ObjectFilter.java index 76dded822..4a29eec99 100644 --- a/src/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Michael Gatto + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.objectfilter; import java.util.Collection; diff --git a/src/ch/eitchnet/utils/objectfilter/Operation.java b/src/ch/eitchnet/utils/objectfilter/Operation.java index 1ca926c80..8d23723ba 100644 --- a/src/ch/eitchnet/utils/objectfilter/Operation.java +++ b/src/ch/eitchnet/utils/objectfilter/Operation.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Michael Gatto + * + */ + +/* + * This file is part of ch.eitchnet.java.utils + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.utils.objectfilter; /** From a3d73329bdd3c822f3aa746c9e4df4d2174006b3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 14 Jun 2012 23:40:29 +0200 Subject: [PATCH 076/457] [New] Added packaging information and files and licence This includes setting the licence to LGPL, setting the headers, adding a build script and testing the build --- COPYING | 674 ++++++++++++++++++ COPYING.LESSER | 165 +++++ build.xml | 73 ++ src/ch/eitchnet/xmlpers/XmlDao.java | 24 +- src/ch/eitchnet/xmlpers/XmlDaoFactory.java | 24 +- src/ch/eitchnet/xmlpers/XmlFilePersister.java | 24 + .../xmlpers/XmlPersistenceExecption.java | 24 + .../xmlpers/XmlPersistenceHandler.java | 24 + .../xmlpers/XmlPersistencePathBuilder.java | 24 + .../xmlpers/XmlPersistenceTransaction.java | 24 +- .../xmlpers/test/impl/MyDaoFactory.java | 34 - .../java}/test/XmlPersistenceTest.java | 26 +- .../java}/test/impl/MyClass.java | 28 +- .../java}/test/impl/MyClassDao.java | 28 +- .../xmlpers/java/test/impl/MyDaoFactory.java | 48 ++ 15 files changed, 1173 insertions(+), 71 deletions(-) create mode 100644 COPYING create mode 100644 COPYING.LESSER create mode 100644 build.xml delete mode 100644 test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyDaoFactory.java rename test/ch/eitchnet/{featherlite/plugin/xmlpers => xmlpers/java}/test/XmlPersistenceTest.java (89%) rename test/ch/eitchnet/{featherlite/plugin/xmlpers => xmlpers/java}/test/impl/MyClass.java (65%) rename test/ch/eitchnet/{featherlite/plugin/xmlpers => xmlpers/java}/test/impl/MyClassDao.java (78%) create mode 100644 test/ch/eitchnet/xmlpers/java/test/impl/MyDaoFactory.java diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..2fd58d18c --- /dev/null +++ b/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cleaning build folder ${buildFolder} + + + + + + + Cleaning dist folder ${distFolder} + + + + + + + diff --git a/src/ch/eitchnet/xmlpers/XmlDao.java b/src/ch/eitchnet/xmlpers/XmlDao.java index d1dd3c456..998f81f04 100644 --- a/src/ch/eitchnet/xmlpers/XmlDao.java +++ b/src/ch/eitchnet/xmlpers/XmlDao.java @@ -1,11 +1,25 @@ /* - * Copyright (c) 2010 - 2011 + * Copyright (c) 2012 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/ch/eitchnet/xmlpers/XmlDaoFactory.java b/src/ch/eitchnet/xmlpers/XmlDaoFactory.java index 81f929572..338aa5e3f 100644 --- a/src/ch/eitchnet/xmlpers/XmlDaoFactory.java +++ b/src/ch/eitchnet/xmlpers/XmlDaoFactory.java @@ -1,11 +1,25 @@ /* - * Copyright (c) 2010 - 2011 + * Copyright (c) 2012 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/ch/eitchnet/xmlpers/XmlFilePersister.java b/src/ch/eitchnet/xmlpers/XmlFilePersister.java index c02acfb25..f4f71f44b 100644 --- a/src/ch/eitchnet/xmlpers/XmlFilePersister.java +++ b/src/ch/eitchnet/xmlpers/XmlFilePersister.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.xmlpers; import java.io.BufferedOutputStream; diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java b/src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java index 868f7d83f..cdf49b88a 100644 --- a/src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java +++ b/src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.xmlpers; /** diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java b/src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java index 0681a2c2a..81796a96e 100644 --- a/src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.xmlpers; import org.apache.log4j.Logger; diff --git a/src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java b/src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java index a8ed1d638..28df70a79 100644 --- a/src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java +++ b/src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java @@ -1,3 +1,27 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ package ch.eitchnet.xmlpers; import java.io.File; diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java index 1e3dfeba8..de6bbc607 100644 --- a/src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java +++ b/src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java @@ -1,11 +1,25 @@ /* - * Copyright (c) 2010 - 2011 + * Copyright (c) 2012 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyDaoFactory.java b/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyDaoFactory.java deleted file mode 100644 index 1a2b4fcd7..000000000 --- a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyDaoFactory.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2010 - 2011 - * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten - * - * All rights reserved. - * - */ -package ch.eitchnet.featherlite.plugin.xmlpers.test.impl; - -import ch.eitchnet.xmlpers.XmlDao; -import ch.eitchnet.xmlpers.XmlDaoFactory; - -/** - * @author Robert von Burg - * - */ -public class MyDaoFactory implements XmlDaoFactory { - - /** - * @see ch.eitchnet.xmlpers.XmlDaoFactory#getDao(java.lang.String) - */ - @SuppressWarnings("unchecked") - @Override - public XmlDao getDao(String type) { - if (type.equals(MyClass.class.getName())) - return (XmlDao) new MyClassDao(); - - throw new RuntimeException("Class with type " + type + " is unknown!"); - } - -} diff --git a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/XmlPersistenceTest.java b/test/ch/eitchnet/xmlpers/java/test/XmlPersistenceTest.java similarity index 89% rename from test/ch/eitchnet/featherlite/plugin/xmlpers/test/XmlPersistenceTest.java rename to test/ch/eitchnet/xmlpers/java/test/XmlPersistenceTest.java index be194acbe..a380e56a5 100644 --- a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/XmlPersistenceTest.java +++ b/test/ch/eitchnet/xmlpers/java/test/XmlPersistenceTest.java @@ -1,18 +1,28 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2012 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten - * - * All rights reserved. + * Robert von Burg * */ -/** +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ -package ch.eitchnet.featherlite.plugin.xmlpers.test; +package ch.eitchnet.java.xmlpers.test; import java.io.File; import java.util.List; diff --git a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClass.java b/test/ch/eitchnet/xmlpers/java/test/impl/MyClass.java similarity index 65% rename from test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClass.java rename to test/ch/eitchnet/xmlpers/java/test/impl/MyClass.java index 9b333a08e..903f947c9 100644 --- a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClass.java +++ b/test/ch/eitchnet/xmlpers/java/test/impl/MyClass.java @@ -1,14 +1,28 @@ /* - * Copyright (c) 2010 - 2011 + * Copyright (c) 2012 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten - * - * All rights reserved. + * Robert von Burg * */ -package ch.eitchnet.featherlite.plugin.xmlpers.test.impl; + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ +package ch.eitchnet.java.xmlpers.test.impl; import ch.eitchnet.utils.objectfilter.ITransactionObject; diff --git a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClassDao.java b/test/ch/eitchnet/xmlpers/java/test/impl/MyClassDao.java similarity index 78% rename from test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClassDao.java rename to test/ch/eitchnet/xmlpers/java/test/impl/MyClassDao.java index ec148642d..6468b2710 100644 --- a/test/ch/eitchnet/featherlite/plugin/xmlpers/test/impl/MyClassDao.java +++ b/test/ch/eitchnet/xmlpers/java/test/impl/MyClassDao.java @@ -1,14 +1,28 @@ /* - * Copyright (c) 2010 - 2011 + * Copyright (c) 2012 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten - * - * All rights reserved. + * Robert von Burg * */ -package ch.eitchnet.featherlite.plugin.xmlpers.test.impl; + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ +package ch.eitchnet.java.xmlpers.test.impl; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; diff --git a/test/ch/eitchnet/xmlpers/java/test/impl/MyDaoFactory.java b/test/ch/eitchnet/xmlpers/java/test/impl/MyDaoFactory.java new file mode 100644 index 000000000..9114f1ce4 --- /dev/null +++ b/test/ch/eitchnet/xmlpers/java/test/impl/MyDaoFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012 + * + * Robert von Burg + * + */ + +/* + * This file is part of ch.eitchnet.java.xmlpers + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ +package ch.eitchnet.java.xmlpers.test.impl; + +import ch.eitchnet.xmlpers.XmlDao; +import ch.eitchnet.xmlpers.XmlDaoFactory; + +/** + * @author Robert von Burg + * + */ +public class MyDaoFactory implements XmlDaoFactory { + + /** + * @see ch.eitchnet.xmlpers.XmlDaoFactory#getDao(java.lang.String) + */ + @SuppressWarnings("unchecked") + @Override + public XmlDao getDao(String type) { + if (type.equals(MyClass.class.getName())) + return (XmlDao) new MyClassDao(); + + throw new RuntimeException("Class with type " + type + " is unknown!"); + } + +} From 2a3222ba66daa2575c24f9f24bd5cb9afc340efe Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 15:34:37 +0200 Subject: [PATCH 077/457] [Major] rebuilt project using maven - Now "mvn compile" will build the project and also download dependencies as needed. - The ant script does not work anymore --- .gitignore | 9 +- lib/log4j-1.2.16-src.zip | Bin 473361 -> 0 bytes pom.xml | 166 ++++++++++++++++++ .../java}/ch/eitchnet/rmi/RMIFileClient.java | 0 .../ch/eitchnet/rmi/RmiFileDeletion.java | 0 .../java}/ch/eitchnet/rmi/RmiFileHandler.java | 0 .../java}/ch/eitchnet/rmi/RmiFilePart.java | 0 .../java}/ch/eitchnet/rmi/RmiHelper.java | 0 .../ch/eitchnet/utils/helper/FileHelper.java | 0 .../utils/helper/Log4jConfigurator.java | 0 .../utils/helper/Log4jPropertyWatchDog.java | 0 .../eitchnet/utils/helper/StringHelper.java | 0 .../eitchnet/utils/helper/SystemHelper.java | 0 .../objectfilter/ITransactionObject.java | 0 .../utils/objectfilter/ObjectCache.java | 0 .../utils/objectfilter/ObjectFilter.java | 0 .../utils/objectfilter/Operation.java | 0 17 files changed, 171 insertions(+), 4 deletions(-) delete mode 100644 lib/log4j-1.2.16-src.zip create mode 100644 pom.xml rename src/{ => main/java}/ch/eitchnet/rmi/RMIFileClient.java (100%) rename src/{ => main/java}/ch/eitchnet/rmi/RmiFileDeletion.java (100%) rename src/{ => main/java}/ch/eitchnet/rmi/RmiFileHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/rmi/RmiFilePart.java (100%) rename src/{ => main/java}/ch/eitchnet/rmi/RmiHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/helper/FileHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/helper/Log4jConfigurator.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/helper/StringHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/helper/SystemHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/objectfilter/ITransactionObject.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/objectfilter/ObjectCache.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/objectfilter/ObjectFilter.java (100%) rename src/{ => main/java}/ch/eitchnet/utils/objectfilter/Operation.java (100%) diff --git a/.gitignore b/.gitignore index 0f182a034..2595e2a6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.class -# Package Files # -*.jar -*.war -*.ear +tmp/ + +# Maven target +target/ + diff --git a/lib/log4j-1.2.16-src.zip b/lib/log4j-1.2.16-src.zip deleted file mode 100644 index 086789a2076cfa7c88b567f012b5bbf32732c6ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473361 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 z 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

    UKmIas$A(z~a^EA#X+ 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⊀`RWm9GuT zGrqk)9v9?s$e>Bt6k)+yA*eD*vY~t@y7-QEfn1YB<$Jmg+@i{2U1I&Fn1h<7nXtOw zLd15W=bEAtuJbUTp7GqJn)kX-6iq@ddfv}x14&v z-(@!TC5xqSfNb~Q?0 zS=6QUKeP{9=s8Ta?pv8EV*|>kd^s&qhGMCGbYURh_aBjZDMpMiAITc0C#AyQaq32e z>?^#W5cMX~ zUz&%N7~ye0X=;EOOhE5>wnU>U+{esmUVk>sd5Vuj3wH5XwzGcSTp>aE3}B&iznb;o z%1bl&IMPr_$+r{D%lvd@&SK{`VjO)cw{h62YpHLcX7xO;R1 zOE2iT8yBCjLN{eE$ScdeEdL`cvS>N*JGS~E>naRozg`-HaN>Do-?-`gNX16dgW0Cf z-14zZb$EZ@)p3q}+qAn&#N%1N>^=UdjowwtpWV`zHA_a@A&N522k&?}E?q@pq>|T9 z?#&JmW6R<&?#WNa@TwO=X%uF~lSqa(4GBjRB&*hZl$p&pX6*&m$=EXPltZ|$*<@w+VFM|BZbb$CQ5gj|KjGt%CY0gzp(Q#vV_5qP`(+2#x_gAL2L7Dom=klz z_R9ve0d+#_sXp(EG_nh)po&s0V|(Fz9XpW6iyov?o&p1;>UpW)N{b-)xh(}CYN5Tl zl~TzZ@1kR{Bl)UH7Q9h0s2Y9(QQ0W@QILeu)}TXtzf4d$G-(sdBnzhUL z=JtrY}#(*xT zZSwh>#sR1Z8*2Sc3T67y?t{{{FMh%Elg&%dUJOrrUA%LjJ6lKh34IMvU=GH#??fJ0 zWaYzVefD)@Muz{eBuP-JUuAuC&U)Xfn9<*8nDyA3R~PGw4s?SD)7%9VMV;z8L7_mCOy`6}^cXv7&X z3s5)rwNLvreoaLF_^(B#ok~XoV|mR#`Ut`d!c-_Ml#%qquG+1WO?2xrz1CbPSD#*Z z`*a}d*RzeYn}l<#+LN-Ql;)%5t7{=3c@`+#+DeT{XQUC0v+Vn%1-^x?S};`*k2!7LR1`Y9=rYd?^kp?9MfPd#-|fus=HT zHV8ju83*0z(nhGt>_KS{epJSu^ssejN=J@2jpc$iCX z#i`jUR|b#xURWAp$FN)-*&d?AjXm z|C&V#`W=C?z8}({HXM)k^43aydXA<732ifDF2zASZ;4UG+h_y=DQb)YGkyL+7Tgp` z=|LwO%Vnq*^-?>B)Yc5}^8R^VnMdgBDeZY9+r`cI8u=;K92BW1Iw{HUcFJlBq0WIk zlBK6A`M%EHy)UAK@Xo_J?wdh&^qOVr{ArnJ4~TWPVIP{5jx#+07;a247=%}R9%ojI)%QakrjfPAEmwV{Z*b| z333#!9_KC}YiUo@()wFo#i;1ExYgm|JSl$>=*e83u%-c3tXmxV(Cur6H;jmTqx*1P zG3L&Wn2?bR6Z3Me!icDO@*R6JL_elW_^OiC+}tB)7SnEcw>$oX+dZsV%s8%=F}pU^ z-n$2OrAUi#`|Bw9(1OqDtNP-$DT+>(=N%@3y-$?fy<$3@9XFmtt>#ymw}6U1v{>p3 zL4?7&t+krMhE$FkbEZnzynXQRGi9ZhCY1n+y}Lmd+nsi?Fy;=4H{8lb#}+={abr-?oI}Q3p}kGRTGrNA!UL4@&#Lw#>?|pjp?rx=mD}XY`TWqKYEkB5v@C*f(U8%QN+c^~CQLp;(iO+X#nZ^q z3YLH(^VQ@CVv+Z3xD{WuYIOO)gqnW9M4n5xJ;?hDwgJ~Cmhg`FelszfXF$G(V7ir@ z6-xp$fFMCLAW``j;LPaKL{DyRm|8XXRx``gXd=ooT;nCNSdIG>pq12nlEwP4Kcc@-k1Qe~SKecQsbzQ~jwNREg&-+4#WmLW;e18U?M8C65 zpW}Rng-eD-Ct@J^Qv7|qI@-+>4OKDGIE809)yo*~bXr&LJZRv`nuh_ruaxQIA2Ss1 zU5xPSmMc!H`z5j$X=dA^i3EB7+%p$Pw_bwiK&e{GgOavn3z?IyD-yVOe+LBEvNNl zPES*2^Ov>5gMLx zs+p77>_V1HGH`FIRP^Fsg{D6vuUq$J?m#RmpQAkWUr1T3zi7CDRSi1+ww=++j?_~_HTW$-w`o4Fo?U>US|D+jH z-VTuwvI9ZG&JNMyw$`gA%%C>cg>>Rrmi|Y*5CZzV1Y*fLjbkdBoI)`#p;T8qMgo8O za~0^pQ~L+ngw|eg@>5(js*I42|DdRhazw>mU)!&m*&Dkk=dV!vbc*cQbWEf~)PCeB zJw({gD-xcBJa$j?7H$2t&(;Kp?LQ|au6kwLcS7|+Z19S*FD3=+&PuqcYpjN|YZ5)( z_dA38g$ctW)=YUhaxb_sYZ*QmU;L3b1#Iwl!&Y^Rg=)pT94{$PaCY&kw$WdGB>TQg zS=&XK7DO5mKzCBb%Bx)6ZgF8Fu)}_rWT?L$yn-lsZ-LfE7@z$9#vkp*;%4LaUwIMY zkK#hHgA&i`?MC~;s{d+XPS4??D&}eLnRU2Q5tY$cJD+c6jSouvO%$Ru*zC)cf2w-8 zIQ3lz;>`@p>9wM)W>4RGJ7D+H+NScpHV~{e0kGj;P5}O7*Fv`@`izr7&jIPapDh+qiudK|_&^SPE}MXi@ib z!1C^a9YRu@`pereRmw!M-peHCLF%m>Sl7=~c4c|(!?92j&m~bS_->#v`M3!X`Io)P zLGuE!i{{*eQ=t9e?dq~;?NN91{rDl%)8;y$r_a6>S(@CnA027&BoYe=>~Wb0nJ}&R zjdZsw$+>AI-y%zJsdGVLGo47(NwqU)~{4%Mv z^r8n^Z`s@KQs@6 z_PHWL>DKuY;r$E z!SF5b)#R(pf>c#pee*R^0u%FeI9%iuVnBlo)|Q>?PIVi;u73glH-mZlAAPDVq@h+B z7y#h*pG%4BzoJiBo3hZkT9~+*IMOLQ8W>qw*qR9#Te>(o+nCroOItWOo7nzmS32H* z&7oRR9k4xO{ij={0_EMPAZihLZ4`~{+y!RIY(YyvA}R|^9Yt(HtVm2oDO&ftg(lgE zO)5-#s|SqcM|}Se(VkE!8c(~w7DiEHy8qHXW3U8;;Ro7zOjU?L`$CFjaFXVLKH?5^ z0PcdnD)=UU0Su#LbqbTC4u@wNTR6UAkb1r&D?m6r&zE;YwoIJ?kyEhnrYVw0UC9*s zxXGem&5YVQe6l#=M1csb;zCp*fgwE#X#!Z>rI?jOT_2T&_{lS@u*9%o18HYRc}HlkZ10b7F)MMqq?o=I5dE}60c@uWHO1!< zNO89Oe4a3U)W_W?ai$0nsK!jHAN5UIedhL#%<0sI3vd)yI_+tm>C}dytQ;GC2H#*_ z(|Z{Leub#RT?cbE;6idWIcaZqP8KKR6^d^w$*3cdk>vmMVfKg=ty7X@3ToC?lLSHx z9XH0$YEAaeMgfKJEWT46#V_Xk?U42JfP9z<)2C_>Wx9C|&tiqm9aaS{bR8h}sH`?h z7^~|&NYyOV8Q5`{nfvX#N+lLODGEXBbs~e*^$X=pasW-{H|f3(Ida4X9{>nq#vc-C zSn%jYH67P)nRfciC3OA*0i?2o8D^slp*Z9DZpW-9Dlbs3^JNG~+(e{zqR&GMPtO%J z_-{$7CT9tRA>!X$u@2XItRT{^{(;xPd)&-FYVT{DF08RrXBw?Ko*Q4I8*z50Omp^F0p38EvV~ zF{w90<*=sOjk46{g1%w^Gm=62p74n{c6-ssf^$%c{BA%GeXs?aZWgXCB1fhN6=o8u zDJ86DPot)iR@-r!O|RX0P**v|q+P~Y9xgw+ZtqrJSkZ*YmxhTMU$2`#6T**S^HBPU zFTAMZ!mMD=c38sXfSxk}?KtsiC{9Z!czSuRUQ6UKxx!wyp~)~sIU>R0sg z?u3W+vhccP#(YY}1Y1djZQs}y5gb+%ddRmaJ}CsuDUjz@}=Z3 z-m=rsaA&1fZ`omL-vw$8$^GJIJ;<3#U#{O`t|Easj-g(rO{Zq+7sa%+Hs`S^gGn*g z52_n!4rVNbBA?rR<2=9rE0JOG&~OX=^Awyw0s!#-KN6X=otd(Mp|y#SowbXNt*D)& zje+xjq4fRla+%72>_4S%&l#0y_8&;r5J4^ZlG1>3TY`#uK!@#hB(JER%bQxG!ph&> z3>QU0m5T^$Lkh`mjXsatnQj;N`5>V7R0_%{40f1We)Ltsk~;M{9V+NSbv8;QSxb;_ z1MXe0P7xqY0&}1g)^YL*)!A{4BlDUoc!6Wk2Eh|+0C*zLw+ZG5Q8oHB5 z5-Rj@ltsa+8Kh|lN#l&;#X{f;^MZ?T^y!d^;>ilz9%e>5?7yl74I$M)p%Us6+`3K; zIJI?>oOmuxi46trCc^+X5Mfo=U}DfKXFOh2ey=Zd`Vw+#h7gim=G3y$5pTT2qUW_(55 z%c*^TI+sJap!slVMg-Hrtrb`vp-yQk+=fi^I{!NHnit+23a$1IaAgUAZrY?k*IT2J z_%Q?XA6j3wXoASs{YU!PL%<+z;fb_!oj-6xTU!Uj5w(WtyMyMO9np#AoCRGB&}4CU zG@074023^gL31qzAi_$GMd|M^wPhAE0cIh$HZc|1Q^~~@-3T0lwP@zZags5miK$#x z5&EO_CDp}`L?*v0BO!yh?_b00$4{G2if49yz$ahK>f+W5+E1Pb0|IO=p`-Oqj6_K| zO~|uFrNX)#ya_7>qJa zGePR%BCi0?YkxPzDQD$%%+;axd=O)(#;dS0Py_kckA@B~ap5BJCnwH#d<>F-jEjrH6C0R9M<3q#gT|d|(Ga$+p z{~hP_yZ)m^__bpmM)7)~8}G6lj#IcRGcb$rDrA_Mtm~?E&^@{Kk8>K=1(aX7lrUT7 zlSc)xHP5i7N2fc#d0W#`XXFv&TxiU-)RoqKg?&#;!zJn!m^vft?6cl8Ci=C?|0^1o zEX0I%&YCD^bX&*wf7T#Bt9iLb|NPsMDE|!s^FP%f|3X*Pz{uIokww_Tz}n92e;XnG zyJ%J4vHSN&`n~Eoj7we1CM@OqRIC}yhN;=r>I~Zk0RVw&7Lv3wo+~IPTaW+tawRGn zYwZrf^HGZ%Ms_&%<>>>bqi(n@@;^rbKrYDb?Pd-UhG9gB)DO(Ql=+Om#z+Jph&sh5()gkd3tmk6p&Msol|~z(Mqq`4S5Yr(}v^3S(dQ? z+FR#=(y$nmg62~VXAVlGkw748-Kn7p_-CcNv5Qg0TBXw*Mxz!GcjgPs{KB6JHrQxntl@Zg*6VUDPDF^R zkLh-jZWo>6YZ@#@KgL2RQUADWnD4N#)iy^RA}ehwS*#RS#OjvqgG*B}b3E@8w^-XY z44W{ZE~;PJK$fZo*OziMG{&DqH{Gr!OpgqmVh6K>`Pu>e`zoh*H8yCFvSrQ~ee#)x zR=+TL7p|S4v@5pWTHdDUIjj0=K97gwT&Ig`)M0sm05> z1CY9I%brVWh0;`QCIyR6SD)fWm72M_Ag{@bL(^ZY_n@5g9s{eHFLEe2i>lX9#PmzDP0Y$kN>W15pq1Tx z+dZTxLH4S6h_V|1Y`DF?s1gk)D_OKQdCArLaeVnYZ?tzc$+^)z;M-JqbuC8yhX3@clxo61R5W7f8T0ycAGYiJ=@Hw#yYr5 zK6TbUsi)her6VhUf*mi8i{c9@k$^FdYyuYx4dI`-wOsVRY4tot@;R>=i|bO!&)e{Q zKjxDGQriQv(3o2YAu)E1t3&ujwkBIhF2UTDns@-`?hIYT?Xf;4BxbJou1Wr2!G60b zG*amdR+4PkOqy}ou$Ikd7Dr+#usF>$g2Jx^&oLM~T(w^Q*`ta}ZRU+%{8a>txt$&K zDeNT1+|b+1EQR(!%Ej6A1v;(^9>*+LA zBb@qMGvdtJGpelbo)DYj!xZ?+9AS}Q1h*yBrd^{GU~hT1b@@IR)F@57XxR%kYAlS_ zugWfX7nXNgI^Q_qQEqfIr}pf!+q>4V7brSIG*rW*)4IlMnt~x0sl#uWosmIxHL4L+Htsc*tUsVT44b=)b5i<{|ko$(#5^#F`vLuPsGpB4OZ!OyMMyWM87HvLMf2<0s=PzP*%Hl| z?dz~@F_Ip;=bds!$qsbG*u99bV(%-zAqCto8855n)_`%&oyc+d%|V-o;Fx%b)z}Io zx+W^(1&G)tFM806N3~!JUS#8X%^Ci@(L4O?yyI%0Q#n<tY~X6!vZ)#1*s?jd1RNd1eZ#S-Iq@q- z@9zqkumcxRXedZ;+{0}#%kS#$d1)CC!ZYSCDyv4q5G;r>M`#rbDoIWxHwn|5qzR_X zH_@2*{ZeUA7fX0TnM$uocxp$uU=5dmkWwAf|UNSNi8b5bZ8NY>Hy-aCfsI{8-lw`WgLD zMei4L5L`S^;N)W6H;7A)eZA4Azsx%sQFPh1?frIpIE0za8@nXe0~$k3ugn2fGnUiY zUZrzuOPtul`uD9FEO0xb0lcoVX!qRYS087%9Svfq54D`P=I>UIEJQpS%;ZUWYIK1N z2bj`+Y#PwQaj}UiXsfEu5#BG4X@(K#ghsqVc>k=9WxhSK?A8t741X+@w&;3yUDWAu zFBZREsteN@JXZz&yrTb482bgzPRREQWBpJ70BHZy!xv>ya{GPnD$W+xPX7gl zm9g7k|5eY_U>xhA#jeq&iq~k*OJJ8f!jXS5ES^Pp^V(=pO3^=zjn5Y!A<1v^tRiUU zP_SAfiNndv8aAOP-NsYU>~dvGGN${GPi@5>1d8wI$wjq(NlupH=^!;xr4ByMD+JnN zl0p(pDQF`SDT(N3RT;@xFOMW~L(^hy@hcrc<^;wteBCH;GJJ~)M~QSt2joVz@fnS{ zTq?2TIyUBD@_3U7mB_55;b;*M!z1Zc=`+knG^$EdC%KaO`SvzSBs3;dDbZ3`H1s2| zs@jK03M-~&>T2A%;+ePsOtOYG)*MN)7U78vWi&kbzg7x| zlBdeH8qLdqe%|H5Y2oPVbO9vBB^wjs?%Siew`?!n?h6+xD#>B~91O0@R??T*eRTtK zBLi4$e!OSeoZx<6N|exaa;n5o08&FB_D%{8FhW^^pMXX6_N_TCV@r^|hRwiDcUpl+ z%XW1BCqX5F-(g^dz@9t!u%bF>jyxDY9(NYfD_@nB-}c;;ijoNXAs7*XKpLfYAi zey_pbcrbj<68%=QM0U^DGTRa5J?UeQ;H_!6~T{Ai5#-H@xw`OPSanY3Hl^F zlEV0e3@K2cm%$wlo!QUk6rq*5l5=mFTR1u>%3Ai~WG4Cqo4$P_N*B$2|qf993* zt_5XpzuXE2Ev3{bvjhK}YMvqts@8~+!3qsAV$8@uuD60Muw$JvTQo&D28`!1ql>pT zEEG1y;(ToooAPw00?H96x|zG%oZp(~t%PP*{La3YWe$R z)`?G0>cvC17u*I-GknS$%P!h?pW7N26V7;4CWEeb*ttQ#P^zw`V?Pp>r~9}I?6Pbl zp0Yxb{}X5kXs>+%d{Un3b2JD^%j1zLtu5XDsYXzxE)aJ7(~grw7P!Bj;Z@>T37P5j zS{**ifLYqeOj*gImtHXyme;+}%4mLOJ3;WQMP^SLpJC_^1-;WvqSH^c(;W;t<?gssg*9xY=*>SHc(B^x>F z06Y6i8p4l3cW=JzWspx5vDs>fK4-h4Lnp?uD8Ua6u&Zyv&3&><7+I-&3)2=^yQYqD za_~s{CWg#?E%jS$zL3I5c|YzBDklnJp>P)xZb>y(`$ft|3gCEjvnb#}OA679p#d?K zJ0%xdUn|sdQhZ%2LFd7ki)w|-G|6*i*xrP*sBI`k7;;?9fFyT6XWzx5-VUus*Q|`d6EqAYj#uv}Lc2n^%zUOYq z18CpviiptyOO{f{lQaWxcZQJ8q+_lzRN(bC;Qkn$c@G4K%Q3d?Pu4N%5p_9Pfc9DD z@s|K;Dh3$60r6hvxn%b4Xmi`vK(~}MLi(Xz^~?LxMB$DuK8aJ$C>wR%iV~NPam8!b z)NvWMZbXn?G_0+hg#qM47gr#G|xMbH;5bJ*8)9(#wzNqc4N}t!x zlgN`Sk6gfKghJ2)cGjUiOf!J&+c&ikHQIc|62jzbGb%B{AMpSAO^q<|FZ4tFIzG4n z0IdJvn-aCKb~bS|G5+6zsf?YmiS>W{u9`5d><)(SI(&!tK{d}Pl_WE)0-bP`?|@aT zCjJ(83=lxTQ8^M{;f|@KN=-=G|L`z%NZk=oy8cZB&_ZzAzR`fwbJ<1R97kAyA)<$x z#4_kaB=DJ9oVN}ZZB_8Vf+SLb>bkoufqz71DMJyk5Kj^iNqB_po&h@ zx2y-y4>2E;yE#dmuu>n#AV^3TBD~hJSe73BX9A{G%r}rA9!{PxsX6#Z$uo{!fVny- z#<-oZJ2N|_vI($BM#0Rg4Ki&qizR&vrQSHE`bg5#SH4ZHu%M1@5MUb-_xRq1-cXSz zFeC z0yG>ulICOfee69KWX+P%y&a4H7}$SI8CPY!DlVb11}N@G7;5VdhO~k9lRZbq6qxkh zm+|N2YlEW`pN~7L^kM7?<{E$Z-q({QUGBQ{@d9jk0seMp?9mcHT$PCTwQ=qrU^vBE z0fxp4+MIG&Z(V;+18j&Rg^(hR&XHkotzvAM5?nIuxv=TwS`lE)bM^_SCY*i~g)D8b$rZ}dh`XE!X|P0Q7|v{&?z|#@deV*n zCQ?yiJqm|VWQ1Ip65{K8hCMbQ47i!@^^pXk6k;z^kfm8Rz0AIFkZ%d9K!VKo?AZiZ zXQVkcFV<*c|5#ejT_D9xL9dd>_Y)T^l!{x0K?lS*IFDTTt6M32a=dalLJA~4(tV2z zGU&yZ3CTvi-itYNW$;|gn3r6?zp?odrGx@tA5{|?E4a?PYjSSg%j5;AecQi#^99eP zsegC&iH7Nbvvqa4VQv#pv?#hhdO&>hTPYh^zJ^>2=kh;}KAj;6@Fmt3N(ROxW2c2G zu1h5h)SR^@Y-;SNDD8pC=bt9VxDP?vge(c?C{%ou83SNs8n9TT(x|9|A;$xnA8ocb z`ju=9Yv3?ZikLIBOF@T73Dg2HeiVm15*#tl9dHw6J_|&@sQoOE3|@JHr8My-0bhW) z!WeYYpAezoEjJ=$#n_(uH!i@_KT)X^6ia_Cy1|iAm5Ogf%9ltv<-+t?F2oad)@4#< z7O)gR3#GWt%`WmL!IbHwt$VmxsLVc8wcDTXADQ^R}|!Uk|N^4{502|gFIyd>5b4AP%Du|?P?x%&G8HS zLa^9PK>@Vo(ZbjV6hZAoJ{l2{ep-elqS1wDEa>V@LidR>XIE>=v0p6r*viBT#fr2g zo}I0}i!#DZy7u?b%|*KuI2*ml1*_)owue42&vC_-Cc82DVrFlr6AB7S4Z zVSf0H1S2)=7LSvuqT;g|v3kpP)tW9zhHx>X2rcchidDz*%X3?lMvEjVs07aCrZq@D$u=xdD!FWppAO;v5L^% zHwT)N_+xbmAaohDmLm=V`cs7}IZs)&u2ncM)wu=?to#m%DsD(Qzw-zi!_vrjBi9KQ$^NXbXspb4KVM{PTZ}BrDp7Td}6RV|)rKD5% zk4O{aJLENQZOmBxK|}htuJV-xX`p|vADYhROF7|#%=f0?4NKa*f|c}AzYk>8Y%^Ld zLGnTQlEH6z)xoFX9Lbflow6yF(xKx@x7a9ykMokcI zONXOCyOSNLC+vLuRe-CtcWh1$@l>s0BvQbFV7xq)Aqk`j-|e936bn;+N1v9Egq`M%za<}Oxe{KEQKTxFVS7^k&2?e08(UsJ zivagQRHK+#dZf(O7Py(I-@*Y@UrjNwz`khe7b_p$UTyt$yM?`&dz5=073N3#>IX8r zQ|+hi5Rf+7_GWA$$|s+Fc3Fcw^$k+BB1Qu-gyit$|<-pPDM&THWSM=euJbwK$h`qeGO3ngo_K zRdkCbwaL-}R+why2PRRabZ&;w?!hbil8>#l@*^f7&HVavGXmcF;c%Gy8S{NH*GN2l z!kkMgV8xAr`86FWk(51{Ma{n#MQ@I^?bkgPw>+{fv5v&;aaJS6HeWV#VOmM^WC$XtB?55$C#3zxn1tLWkXW@P>oV*3C5 zDG^6UJIDW8&)cyp6h-iT(POwrj=?Fb*q+cxwj^^E%4{Gy=pc`^!WbB?m2yK*EF1sX zUIRtie|VyJ(U*c4nDl*^vI~d=)b6E;RWO(C6{pASZ!~7uB5Y$~QG$Th2oJ)f1$AFA zX8`gP237nc4jQa%u3$(JwZAbw+tDcil0rEM6g*hvg&RY9D_six3?qnD#{8UjM=?B)pt7%$ z#9>A_oMTqQN{$Cl6Sj#Z`abCa{7&h(n6dvPH-swU@d5LX*pOiZX)9}qHI!D4_D8r#6~#V2MxX^$-{3vmGg50L zq4S1H{xHpdk2!!ItKS+ccy2XdGc+@EX$5vuZsp?45qX+Oy=u?0sS}QE&r(7TI*k^8 z_r~6X2dtP`N`kh5nDl8+q1*+oP1TYZl--O?Z#cYI=s=4LXZ-^EQlrK|!9c<`7E2{IhxOm_mvTdim{NYk!@y68e9<%~ZC^h>lr2f+RVtpCD3R}Cw>`JBugx2CJ{_(H0Enqh382h)lxnMkt5V496Gj1uB4C63e#xI+YcTez= z*xiWw$prurFaiI;!EUKwSOr@c|MFG{`$^pICW#eg6W7BqbfwNtbpF{3lY{S|MqE+< z^m>;CxZcgahN!!+a@*R_!XUh8#EcD{QOA5FT1^MwEHW^E;T&@PX%91qDVF$G$-)eL zl;;x;=;At@IZ2fZSiLKE%fk z$e##ZDpl4o^>DEKsj*c9=VwJ`S9(R5e+g`k&j0!`-nm9f>Hbc6ZyDxtFSa^4Qx|~x zEac3m|LYpQo|rk`CEe1CQx$Mzz4~BfoQZK~qMwO+Mrwk67EMWQd2~n|NZxuq<%JTW zv}!V9IFDuhzQ_Nc^lPvGX($~M0ATtL006~*pkM!+cKt`jHKbwVw80MdeXaL*f~-yE zsOV-x+icn;tGXk+3z_4}`Gpfxv%sW;@u;|nz0LOf1F*`>dvVj6tgQ>YKALLXdaXDMIFVu8rBBksN}PPc>LW@->5$E} zwPCRL1E+H0>?4??^liu@Cp8##S?DvhAL@JWr;-(|nibxKv@hi$o{phy3QkuXnB1h+ zvll^9?ul7FKNP|IRI`>|RS0Thrl8DPmh609GM{e<ealTpB9PGLV6G|gtPycK_!u2zi&yw z$higsC|X2{C>4JZH=gl9DAWb_{zi2sAg5JIX7TwsE_Y1#Y1Ox)jqAc0Mx(-^U1xRN z;BuX-&xz9%ljGp5oW?@7L)EOwY#O0W{4I|rRn0t|vZf&GRBoBhIb>vk`8_H`b2Gj9 zx`x$t=$EEwj?*M}m)TjvZk0`<1t=%a($6hLl+ze-XGI*;ej69kt(MkpKc-UYExYQ| z6)2E_BK78@NTNx#;kmf(FhYGomJvLiHdyut{}~SxOU9@wz96LgCld7ysPJAo2znU# zbyw(5cM0W(=Y?!;DN_t-AgETyzzs^|ZdAE(N=0KB`V)sU6i6C+nEctFt4B}zS{Unj z@Af>GH!|aS7*WPYu51;Cfyq#MgEV{Gc=-vsH)HT0bLVjY4zXd7bV+P{ z>%B1mKNa&h2WYW}L_#z9fqvZ1oOD(q`o`K7&Rsm%ODa`3k^6M-G=c-T9rc*-26QnFYViN2E$@8WtCt`{!Yqm4*}fQt^Z zyj(hYOhj3H*13EG8!+ic+SCfi79Eom_qu|iE4)0sc=Fp#a&QPbSruAI?stEHP~1IY z-kMBdY$*%4J;>6he%c9b zRkr5Ig6jA{0vLm)NGE~5XT}lkc&`z{bI+r(3czx&xK)ac`4J-hQ+7AL%1p0TIYZ${ z@s_74DRC0>((z-8OaBDHP3k8l?)4N%ZdI$eMW^N(z%xlplLfg-?f_1W9wn@}&C0J9 z*^}B-Zrff5$#`_|0?r{m$&wUvM!lN9zUJ1k8}8Vs^i}bmMtv7_%2KP_y4jC>0$;>L zF+=zRrKz%iXY_D4J<4`L*GmjL9ff?s?I z{4;lQkzUb%qbE(4dNQE@xN%$W=jq9jmZh5&4GeqmYrX34<5FFVU|ofPmOTUio7#TR z-pP>pE}R}@u3l6HtTQ;i5b2^M$(*cTVN3ob1Vg2m;CJD8(6EHyETqLha7%~gsElFpWa`_bz)FaVBK)08!MRzKhlHq zJ~IwD!*%SWO6)O2wzaA#+ai@7V!xS)C`3mfL zPIl5H>5>n0Lky-^xlJtJ0%-{M?=$HP9b%KXsUfb;3a=?E^VkH>XOJX@t~Bvr44eN2 za?E8*?&0{+po1x(_L;eZ>m~=zY4Z-k&#!%L8CrvN;lcm0wR^ zDT$C(ck)7rLk87DPgEUlhFQd#RXwv-YNEqu0&VNgsIzdZ1DB^?+B_-b@`1E?+6~tk znpbZ(*q14t4M5ze%W>)l5BpK|a1NiFCw)bdaYyz7(`ddMPasORNGlzRgsZhY>3DbO z7GB9ut=loDSYOsgLOL>AVI{7-1+BR!i%w#>F;m4s*-AiaZRA;K=8?sdD;J++Mr~X}hV#nvK;EuEG3;4LI=wRPVHeBpR z=rn!@{AOoNBlgZtv=*%GXS5RSvK5s(vqywfEFZY44q>0EgaCLn>0$2sjM(+wEo=)E zsY~IrJ{1P_47IfSL&nX?a4EioafRjB>WHXw#~0NG2HUrX>E;NUDZK>5_JAgJJU^VG)J|Y+wn{s={jfU4z9sU zqzIoCj5}Ehd@ne)AAj1bksQe^%r~2lM>Itd`Q%`6YD#vi54;6--mIJU8Sk~<2c%qX zrgUx6iP~Onyl0*g0fS{dFS?b#-^i>(E*J8+O2!PZeFvZ%#-W;uMV9Q|U#?2Pg!oF^ z!A3>`L#KRd1Z149W7E8n)BS%G>@n|cm9Dj%%0PvwVCEwvze++rrj^9{zr@J54!SNH zN0kBhz`DYt-|u@cSF1JIwD2SB!S#3stvc4cH$CNwpi+6gib{T$XO5S9F64d0`F=qE zC-$p_N}rSab%^DDgN}6nCHDJ&GRprE0WPWA*sY5rd{5V;9dxIKq$$x^$db5Gk`xt- z?V==#DyXPLYFM)kUue|`wN?ClnYym%!(^2y^JbBNY^R!;=6I}WXwF#P!hprkBu2>z zK*|$@%8q+~-VaLCq#fGR+#8Q86C>GkZo2^ACQBhef)g0f5|g3m5lf0DYVWCABm4zT zc;JvzJ_d5nvU=S!-@JL&M;pQ^Q7~oAe-tSa1X46e>JuClOCvzZlqEu%G7Lk)Ny8Q) zhEK~9{heN0D2@t{|Eo@{ZEBFz4#Gq#)p;S9I=O3EZvMNESA|IuNrNck9?MRA)b;@_ zz|mM@YjzRMO6~BHMMHoHbsv?Q_lBNZ3Z){_b!>rCH+Vuap~@H+3l2?48nNeDLIcP^ zMF(7BPUQ25Oqtwm-GmkyJkdB^9VbvpU&q347^-`-crKIML42$c;{BB^hNO{0{kOO* zZHBbWb`tGS=!)#hPuZ9t{+8HW&=lBTe>_I>LDf8bITihn6ZK!uk-%=X&=uU88y3jJ zZa!bGcI*M4ljmk8oEI;=DBFB4EZwJ(uQ`OB>U<_|f4+}_N>Y1c*rQvza$;!tsW6Nq zrK@ei3@FrJ!PmpZ$8+FuV zIed$^k2?!^R0lE3-Ew!m<`wboGwDzj+6q#>P3K`?Q&YHfEi#_?*Tr*}VzLJ-}twp}J)RXF4nA-J&Y>067fv${?g>eV(^vSieb*3%PQ@Xz1`5 z3s+ym;;$?!C!OyVW{vL@jKqm{nk4-@2ky4g6R@-Qc=!$xgDJFKdOvs4WbLyVKYl^V6NctdlWrv*7sMb(VI)k#bqeTrc_wW zmv?7J3|Z9L)*CgRWg4Kl%v&Va8UtBnC8l zpm$`Pj;t>~5qzA1tHF}MWeXvfBy{)yx|dFsv{n)%!)xD)7Ru`ia<}D;GL5nJ#KTRK z+V@uJrTx6M5%HcF{B_1?)Y_z_dy229H1!ZqmByDQG_6XgS*#Lhc;*3oDr%KR`-Jqkr{S_-*V*-7yDAm?jI=U($VU-uFohS*z0$J%gi z8IW%7qRt4EByl%<5PKUsOp7%T7DZ2n$dm^%vwc_fR?OQ?-gC#un2Ipj|I^`1zjFcl z7;==tw`6v+78L}YTT5D5P5#0NR9&X5QscDFQ$c0W?wmGb>a>);Pp?us+H2Bfk7bKV zB2y~ePDHX=AzXY>Ya?W zpL(w-BC3}pi(@u1QJ|pNDH_m3>b`y-P-$mUqg6T8uL4;AJa2)#&#;K9)ZJm+c5!xY zn#pO^3{T}D5`WfP9NX$QJ9J-P?5DU@ABYWt(y)%JOrI9OAq9&kn$hN(o^3jEbez&9 z0%&rP(M3R8nPFHxK|5jobv)KSG~NwoBNYL*MOrmKf_p=+<1#kFEf>3L`!lnMcONy1 z7x}2{hA>;)L)H3yQ?B8ov-)H)>+cG0XW>AG^sjp*8a2f_ z^@JTsV3x&dwSQCyWk@p7I7S1|mbVe3(`U=68SAySt;6?S4c1lPfP4*E93aTrO-q1D#%;Hi@%T{`Ycz%HV#&u zMW-7;L0RN^TxtF8>%j+o&fHgZ2^V*l2X9L-)!?bAfn(O*jKL357&&shQDz4ZkXzCE z^lwnP!-?=b{DRS!yE^3@K#od`1!?V%tk#y=`qj5x6b$ju>GI|xs^KsMN-bs3C6eKu zG_YpNae)+-V@H~rViXTP+SX+|1X*hTLgQ@tXH=d)WU}bChg*=W7R9pR-bxySu^p(c z4V4nxUT@2~up-xan}S-D@$dc^;LEa~iS&2{rG~sNb0#X;mbYmAb>-QPJ<429&<-wu?Wa7#& zlx6sh?lyne3Vk7Rz`C0Z^uk>(*)5*0ms4YD9&KZNnmu7R1Wj90r?8Mz{@H7 z5y>ihHsY2j(gRBO+OeY^hlB6}s*@+**zPglCZTd&oJMh*6v_@;*tq=-YTSMSQX44I z&vvtw=-)6&`axX{ibnN;setcyu$vl7Pn6e>ytWh%I492lig=_eL9sQ<+WF&e{~oBB zr7FGHA}5BCO~@dsp>_@g&p*5_)jd_sWFL~woQ}R9{GRm1vLfW{daQ2K%YQGJcTK~} z5aAtV^G?QJ{4MTp@$G-wZedPv*fx!_=mMg3Vmfi2yJGMwTgB>!NYP6Q6b#rfm8HZr z?14(r3-W4@$2FTurZx3(mkCqJA++iQyhzQp8xbR;992R$0&!KWasYD`2eD@=*Tm=k zT|Udru(ysbP(fIK$QYR{L=vLE$K+IbACOngpUzI=PtUrphGCtumba-e2BU84-9Cm` zEJoq_Ybgl&0wNA$YOdo|ph(Poyv&Xc>+$+PMSD`Ea@6TvjkK=9o!_0QgI!b3U{A9T<9xL*M~#e(6J z^SJF#J*OmVBTep>ZG0hC=YynASw6K=drS#oE{Ff+mPLZl_MZEjxNCUYS2zEtyL&-{ z=gG8 zV@jX{JJ4TG(B6{J9uGxv_^iK+P8lA7Cs(8UH~effBX7EH^m4u>l1*#fvN4%9tM&KZ z0u|K7@YxoANhX84UhTC9#P`@{PM$%nS%tTcC|wH&%$p4S79aGmwgz;3o`=N&{14Fo zq&0)?#ub*o9ddp+008FylGZ5Nxhb018vlli9RD-7QB{yTU`O!1slgz~PWmIR4%J~; zDX9jvXn&A(#hkbB^@7Ur| zJ*;g6BpNV=00tU)hCEhWo+gMf_Ioj7j}hDsA=*2SfHICFK)E&nsw!FiSoBWBLY&kx zvR5&(7e~zO zbtT-DPP3DEuRQMgOH|K5ZOrnPoAkf^akP*;q-qrc_6eiSu*)UzySrJ8c<2G~+%|E3 zTDB#B%#PXb%|uKkZ@7Vq3ZP?paK&N+Y(saULD_V)PMCH&K{0$WPg$g`yU=5oRt8UCrt~!kE=u^=IkgDoTx(s1wal?0cgSJ*g5M( zuSXtsO5B`U@h43K$Gp99`<%fB&JHcP@#V;vvHJAy0eWJ|)WrM;vEKSl1lJ%SNL!)| z_vbfMm7x2UBXBQ(jJgjQf>TOKq!1O`M3XlZDdekKo70y-=<7^D^c)T(CGAE`aD#F8 z-^SJnO0?#KrDyPf>$u^Uaj2BLWJ7BuOm^d|G(@oiB;BYTIpnv~ zDy~!cFgcy!WeJWo?1AIVDbkjAAFNJa5rO+!W(f1ObStObmy7E!c*nw|Ovn1cV57TX z5L84tSH_Mk1Ko-x)kI&x)sbFy9J_?+!LjY(i)cdi_c&0~>)K_Wbl zPhMPW$$IsYnK3qke2j!~>%P8cZTdCopeaBV1K)BKHqe`&V$yzHE}Sc7ir zJ{}21D$qPiw-SlW^?dF8HWy8r`AnZRf-9oV*nf8Pzct-Lw2IIYFF{=vymUL*i(0qz zbj`ZjvjeTRoPpZS+TDxhgIJJ{D*siPR}-OB+xz8=rQwaWE>>kF+L2UF5iGUJRZU5} zB5}ta@1e6lF=jxOXJGJUxU~eK#KQv_^RK<>a-#PwLsL)A81& z5Zfp2*-NMDjqiA4aI{*V-~NwE`L|8ls_d`tfB9P}|Br$G|F=^9-z6lMRCVMw=uv!b zYFhWp^AePetYMffbO33vizw zjaK+D`T(`>K#KcN{sC$InWRjal`CT0ppUly^>P>|l7sH3+c&|n3cyGZlR^|NoXNuz zR3B7qEM%R-dL5(H3mS)k!w|Gy?GeErphsKz#oNwU6&^!2`T^Ghu+5Wv{iE{KGaS(~ zAa@f;$78~Vv(Azxvi4Q6rylwPEQx<-62_hA>n4cXaaz;P5Irzs7J~g18HIWzz%$Dy%1O&XVjco zm*zLwnDEzVnd`bzQrF6U_g!Gdv*PfQJo%0*@Du3|nHw^g+2~?Z%ws58naQwH^k@Q9 zMXU>|3ai$St~AgpZT{WDm?LhTB-4Kfv!~w(_MErTmsqo-`*ZZ`&1rxLG=k%sDImz; zjM^ahX>JbMb7|3<8Q^FO*Q)n>Bj$iR)IwM|LpI#Pb%X4^fy5~nvcIZZqyo+z%Kq>E zj(ykua7llV8F&7ov2FtnftuCge3N8w`{A(4GD6>CAB3*sMsv~|8y)&6e*82%Txb%W zFgm55&rSH{s+N8j33x$?}z4d{818Sc_TV+w^RG9G#(s z2X10w3ul>$2Z?NCcBKo6ZZ$R25Q8u9KN}1E@dV^%`@-OpXFqVYME!bA^=Yk0K1M}2 z<(~m%ZZfy~VJ$KR7eUn}+4k2qPm}ToL+3f?pI_j(7lg(n8IZWuXvR9UDn`#*T7YR8 zaK(2E3409>6F1qY^#@+j&Phcb%ErY>{OwS!>4gaB&WT(;!?NI28L|lDU9?Aep`uz= z@44{|2DNGiE25kEa2<_%(1o}K4u&HYoSpqFTU1VN&<0+0;PVC3>h+OC!P<+wegOXy zjo|2zNeEK_0AzOlS8Z}ZM>{JMM;SX?i{DcDe}p1OK2~nnYze1sUr^M1gNMSlYZ9F& z+w8m@jd*pYkGB!^T~4Mar;XzBm#u44SGEz$*M~hjp1WJ-35rGRPSrE%;j82*{(|}P zD*UL=-;G4y#Ip5u)Rr8GG87r+L59-Y;E3NyKCW!1G-Zm$V`SbxRQ~oTP*0SLE{qrGzSYQ2D_or);GVlH=TO571&cPFQYia>?nqUff ziqF|t1+XtNQ%8XcjGy^hSfl++xBpZJAWvH}Y}=kVMvUjqm7NpsiZDTLB=u&RPdh?x zATn}q?dWb#JWDU^>!Bg=VB+9`>D7fe#LN%XkAs(M6&0xr>}r%Q5=snUYcj6DQE;KM zO~MV(1JrK?nq#_T4I@N#lX?N0!ggFs19g-VT4SmK%d9yBP8LBXXR0AYy}~vY8hd>M zkke0;o8Lm_v9t4SFO@jAThf-PEn(>*8~0R8gJnPUkyU11C9#u+s@F9EsFJxxtN6o2 zSG8{DHsAkgo|bb|AJ<@ILqyD@?cDOTEQ;WxO%lDqS{om&MMDMc!8|h6B;9YC5cD2x zH*pW6+|?bs>LMTuz-&}dG-N_Px86{(Iwa9~&KfA9F(r8nk-BqvIkq95Ni)L-vr@DZd?k;okC1GZp)#RVohGg{%Ro#!L{zlpO}+cxR?<@4(GGn z9oo#2N^3vTk=&gWJz0~wpuK6XJbR7HneqBxS(bmYMyeN?PDEB+>#I!igi+TyL3e^% zHz@m>)+5uEx!L#M$)NhpLO~&)`utq(Tr1^6XU93jpz(r^H$@M#kp6d!qWZIr(0gPj zFdp!CL`ux<4aBsE54$oxt-;QhP1<(plrevq(knWL|Vg_nUZDJ66l!qvpQ^9A`=9~IVG zs{T$t=}MTU{gF}@nAOI9uejELrQ$R(8%bJuut{t|?{U&881nn`rO2t?8{%|ffGb~O@C;6-!JIT zzhT3<`A34)i>2RxdW({g9aume7=du3xgP40gso2clv7TKaOE9BIvZJo~>2eGI$_q7FWUK(1z&I=Yg+xI>tYBVZEHj8g zF$XM(teKn-*W^$g7*Ty}Pd z=zPy(N0vf(Fb9=cQNZ$KuW5&a1y^1?>Qq-^n%}~~CYm+L85w3GEpmVmD*iVdEMHJK z95<}j8SjJZAl5yMb^qe*z*3WPeWQ*_4K5n|!$&c4nKq{{M_SdO6(=f3UYYAiRFg8A zB)4Fu+zWEbQ9=;kyqFZk|9rWO5A+z0|MXr4p0}cp8X7FKW<^{3&uhOb?ogEVJ2hhk zb?yKO*R=M0LWL%Ug)?V0n$#vX3El=SBXG0EH{_ZubH60gdZOZMc*f8uOy)0#h z{1krodwfwMWQTR*OR3#x)IH-N*9bU>blCMGApFS+88UGvfGi0HXkw{Oe4$1gA#ZlV zaVbx7)ewbzcPT6e^*ZXz(^z{*u716oCe>^6Bx&!>QN@fVvn4t*WM*NGu5sw{qw&+o zziEAoXI$1YBs;U}JEmV$>rbKU+ZLv*pdWRU& z6!MpS!;p3{Gcsca2S$^ML2@-hw%)srip$DYM-^;&AMlK^>&47N@f^UfItpBMcR zd*}B3Xkz))WcBj%mhQ}t?mKCnx>Ked*{2)3wP@&V2> zt}Vs9&1h2Ep(r;$3@MR(&CgOgFm!w^^h%XE3>;wz9N!9bJPryn3Toy9E;MkWIe~Qv zf)Bkt6_f_Zvvn!Zffe?-C_E#e12-WhahP?*7psU}1zjRx=#isKUoos|`ru2ShUIC? zoQr^1mYLdM)nt5`@>95vE^?<3psCnk~P^?@^6GJd&C_OG_ZB%J&OlOfUxbucFUDtaeyuX`Uq zh7eiU2-B%b;?58SxMn}ai|Uo`a6Hxh_(rcbB6baoc(tOe+fWfP zFTY^%P&Q}t@)PQmJ4wTB~Xj-HgIx5dZ&>q1w?*klkx z>#00WhmpIGFQscX^IaP9smDSRN5bm1RRp>QuwyMubl7k8mNo|Fzu|Twa@=C3CkxGL zU9yadF3#BqXR4<_n>B&aGsSlPZKPy4MVZs(zT0-|FjS(M*YeI=?ON6vP&U)Dd#XS^ai^~bXCP?l4Mf0|CsfvLv{~LvG7h6N^{sX9qX}AsfP91E5cTq zVRa)$H-@&ANT}Jws%Vknj-6WV388sogO?8|UNhcPTB75^H{P7hevFIB)Bg4}_DfZJ z+#}-~96QwPN_V}`O9k9-6~Q|YJsykuf$zT01O4PweOr>`h zc!2}s6lgB+oOy3u^67160eMG4(yck6xz^tnL~LAiGSk4O$0%K=ne_O6HUIzwFRbx| zxyb#xJhmri7S)hvIHRA)N&ZlAd3G^}C|{&9CXtnXPMj&e0U|0G3MJ-w;N(DDEzYYX zkHa(4JXICx153#vyL8A*K}km^^pl|SU&?UZ*i$}ui$0j#Q3E%fhk%OSpRfnCVUMYbszdV#)3 z+MCoIu|J3$)DEkn)x2i~`0QFya-j6_q|@zZe}er$KD5sGG5nd%NvkpD{bW>N8` zj~CmZog=$gq+fH_D+vbjK+qP}nwr#s|>Ym%~ zt|w{n^?(WYM}OUNQrGw z)2YRb!WUkz6~Yl_*A7?3mI?~dbDQZ_vBE%p#5!u(X@@9@YOkGiV*wJztlY`85@d-j zR4GP6JwTI?P6G_IIbr#HhqUa&-|?{w2@c3!z!cfK!M> zIYswPoGZtT)z@Zt{50OU6ElI&a`7LEyUUKqrdi^E8Wm4Ep1@W9sJj2WQ}Lfd?Ja+D z@M~eVSU+pzf|1i4leucW`PJ_-`&HgVd)87Bo-0eGGEhS*9YHE12?`jvYLP1fWj??L zNL<M`pj#9n7rg7lbW_^6pv@}t6m9Z*ia=sp}K)di7;?XUV*IAiZeLyTXy5(yEYSnaoaN-9MaTaRq5 zOTl-CaolBaTxxVxwq+H;i1!ZQPZMPi?jL5dOfU<`vBRC%F$A%b=Hi+b{QGJvEqx`y;?r&QuBk!&OxC(t6xtbv_i>DAA z=ZKHAb1eZ`HQ)5L8bC-w6ke*g6JJMqaraAbFskogTKd1eJ)KBAYEKITe2w5a>iPnZ zJp6d*QJsJvCVneDdm)Ty$n_U`+8?Fh0Uwu4Iw6`}`ZTFe8{PSc@ZoYjCSV*jghqq%LyT1;%x#isPV> zqLsG(9XU4n%4-+8)e zA@({9X(jCx?TmdS?A@(ZC-tNUBen<>J`_tZ_02)we&c`#vHRlRd0A&3e?D1j+=YGG z-A~lp%zoSIMj5HQhgAWlh!KFVgX>TFzjkIo@UKp#CnbI_!QVyQl4&@Gq+2Vs&7r>< z8}nI&pTUbl#sC~>Wuf2KYek!e=%QhOqG+{WRQ@%lw_8(TX48oN6J z!y$99FxKvwFkjUE0TSsFnKkkDMDt4b0i((o)y=2NUAdk~8;LY)|i zae5+cnHoxZ72tn$&b4oMU|XhBjdWj6CYv^3Y5J~5qj-Mh!RX30soj=CTG3)T>-oeZ z#WeBIqktnM%?foNlT}iTx>GjmA7kK;&{v5VOa?IF128_=MoY2ZBL;j)5BT9p_6VT0 zSG7&Fe|q+>(jk&C*u<$ghOT{TFgBj^qw-l7*91EGKrgNq^-F;|69pJ)DWHO{39d6n zihsp7(XDG)PDA%vGR;DhkW^8pG%#dyw(8O#eoyI034x#wMTaP|+wDSQy4BtzJrsY2 zpP_}PyABHP$jd+I6J~&(7V>iOv46V;bffX+Qiz%6L$ILC8t^F?n_llT#+;5Y(J&i= zBJ{j7y#k6Dig$llK~)KMU{n-lz>VfyX_f_|Am=nHm~R&}o1M_dZ=f`3RJ=f9%+hP& zS*i9Nh-v_LYu`V%0ekJT-CB5e+-cWrjOd@c4-YeL#E3d1og4nfHBaLa7 ze^j(jmOe>8yBBe9shAQW`|hVAb+f0?H0NKdkL}BnMjHM zITe%jSdkGRaJ<_7JncHoD%P!kbEv+mb2u0FTdla`3(s!R>;C)TjM^oRf8j%h&lxFj z7uxQu=a;TM8MPm}&UfnBF<4>NP46N_4Nu7LFP1V?xT8c2LXV-0GI$|~6Sh{%zqVhX zbWZ%a7PJUwK-2%Ly~hDHDXF#OuqrvE0D2!=9$n7oOAYlg7mJmY znwBKPiHJ@_dBGI58{*ZK@zWjuF6gnRb5m7W``N{f_DVHnODwFJks&4v5lhjCUV}3v zX~uZhp=}8q@e+?4g5tk`^|5YdUahl;hkCxsS-h1^u)Pcn$r}FcBvB6L-#-e=$h)|6F|1nW(YNnhKzJnQFRg!k7&h% z-@rHQ)%wwkZzL#uvj13ksOI|uTJ(gO{%950CmmdNGBffTXIx@Ed{<6eb3-Q_W&pN< zrqwB{Y&|s@SQrt=;8WcV#HW;MQN>7ufSM-X)a}kLhYqF|LsGW>$WS(q(>ZwLm9Q8g zwnJ#Q&(v8|p5^s!kyboN#Biy?)#(X7z3J;%9T-@N84=_Rte4i|U?X{FQW3p3 z=Tl+TI~MrKuG>B=&VgdXH8v!F3^98gj{P;b_#Ym-d@A~#lmQ8orhR? z>^Xs@p{#GevR{8O3?U{q_Z3i6UXynZ$h0RTu7R3`mCLfimOcW}F3Z(sZSKmdVB~5d z-egKSJB8*TRvt{SFu>pPrDoEw@kA4K0{fTMO1nhnn4ibC`uk=+3e)IWJA>3z3q@+8)XGtKO_F@Dy^X+Z;a9D=% ziH7pnDJZouf)Sr~x%D4YDHIv24NZzo(zWQ+ZR);^y{bEeNIB@6BlP3g0h zXKW88ED!h}iIc`7`#Z3NvOiwOnHI;5 z+%cH4axnptD%d=_qx+AmcZ$M)c(pQ}0GgZ*A;7}V9nPs1VryK9UIfyrpLg167=v2P~PW;3sm<{;7960XXUxg-)(3j zY6atoFNEdaP=#ZmUWc}~7Uny_$L=tZVBkt60s1kxMI2m77l&p&W&I3xJ*R+N^aiA< zd#FB!pR(5lufU|FYx62yovUu*Qt!J(>ADdOq4&rA7RldV@}gAgUH+ z`yk|Oc+Qq!yg1|?=eo1>#>A;a!{DuF{sr{n)=qQL0#)zS6 z;=dl_g*VC5ivsfgs17%&fzyn3fZTZT!mvRKgr!om_0y!=6*_@USbhJxe+u0AQ)K)D z8!scp^24fN=W~_loTd^xIAoY8(0jyd(|hmKu7waZ8y8Wv}Hnd z%hlPX53}pbZiw-IID|jcdajRcLa*?`wf0kxb(moOIvD7n?&MkgTgIgfXL5Aa-cu9n zdsD5O!)EFYL1r{SYvE7>j~Fz#j!#FkPKm?KIB7biJifLm=yF_SWt1ZU$TMwzqen%- z{b&iRvlv(tQsoo)sa6}nkcas3GzaRDAz1YSwC14cFdvBA7Sf|>8j%^!KjFMT=BENi zHfUf_Csl`5JyKQox1wo>m5LcCr=$|03iE9tKjv>8BhpZQ826ZwFUsHZ_j!^g*`EsE zU@ocr^`#fIepk4~#Mnya3wR`$1TlnNkGvqlxxQF<7OZR(o1QoH2ocp@2L+$rE~FX! z+}C2u9$KQc_oLO*2p?{t*z5ZDCQi9@2Uab3UM;SY!@d0tv*}Cmh3#XbQgz`Dhu(_j zoy5i%!1B~IoLFq$N?~2@37$=K>OY{xv)KN2;^OZE)(PH7J#=c@)~SAJ)hVChT_LJH z_JG}E`sam)_VMx>Q*W^UI=0N8=h+BcZ~Wn$iG6F@4Q)!8WdZzi zCDbk_)NU*1c%{vGj>$A8Fkjs9XJvr{I#HQuNVrJmaokXmww$Q#XPM-_`6qRY0qR?9 z?gBs`PhmYte!uDZpUcM^K0cgST;$Gk&Iu}F9}_|{4xk$P&OYBrQ@!DkZA1hv9P1m* zrxBRY7aEP1h$?P4qjGCwe3;FtauGcQo`%FAeKIqD<-A4}?4!5i-^s|`&Ap0*D9iD< zvN>bpaiU@rR&Zyd<;o>x@hfMAO86{@VJ7A3wa42|W=%a3MT;)w>Jq7E9Kci!sN@1(wXc zPED0WyzsiY8$t!I%Yq3A%K22!4^d;UgbZ1$Bqe?j+KURQwhWu&0UE9`b%NIKMG}4& zIAtHXrsA(o3dY(?vTb-Rw~cLKclUtZ9DeqXZ1wEgv#Buuep^SjL}`hH5}x^ceLs$F z{%F9W7k99kM?D$2lMk@mb5SGRyn#hy%HxCi?~EGEVb_+;AD3kE=x{uNy~hFDj@rZK zCJh5CSdTtg$(s?DXIW*4>|ME;ynFnNYz{@F{OrIU(dk+Nz1kr6!e;<{J0tfN3wXT< z(a8qKas{-IY9P<{|9n6{?F`qj^laMU`xLnfWrlD}ZV-s45G&0XxaG~f(mm%2o)3k2V9SEjpzt)YqVBWuF-o@E)ActBjInsr#5==`G3+Un?rBc2?Z9 zFQ}|0cAh3}{cZP;+kwlFU^&3s`Tx0f#nJNS=KW0k#~OR=yhDib$`bmj(d|#DC_QhH zU*oGuPlkB-{dV@T_k9FBYlvj1)^1sckD~(@BL{~+@2P`Y-|?b9wajGHjLVIab-H43 zYRwdPzA|SQ4S=wt4&%XCF^)?0>`rSt@1?dy9fvukU*n@Zd6V_f@^S6A@<%)GfZfN_ zWX`G{HjfQqmFUVa?h&xisNIS{ml#C;23r3Z*{g6I?8Us4pp=e26`$uN0oIEZ5&T+w zBhSB*WDuv_3c<>dV^@elQ>4sQos8e06H@i6f39aBZ^dv?%N^jVntAjdCo`AT7?XCjvr~hI2mCRP5tDv*Cet!!* z#!SAjb8Y51+1nzp%4=&vAjw}EdPfjcAtI$%p;RG?3YpBn^QhEa>f8VXDHUQKnRuMI z>K;^YyO2Ad2Yn9_b&on00&4(jYK~JLz-t)D3vfBo1#HtI)!T@x z#z3e=BzSXXqG$3?7yig?rH83AgE&|R(1pmkG2$uwgNYi`H=Me95nUq(+{Q=Tq}z40 zTOl2mV`2|Vd_cQATg*!{f@$EG@7^}d z>U+pW`y5Lr_`r2!-Hz487_*o>QpNgj=)mw+!>A3{ivu^l!~6hAZ-d?kaRq3*0mv}M zIWbzuVFlGVoN+=Pbk3Ss55C$V2&|Pmf<<1d4^j)~+febPc9 z^$EyeVg+#M1@K*GZl+2fQ7cxAUO(Uap9gzOZ=T#Q4MGK@F=Y#>4I}z#TL)%Jy2j6ifNmdt_O^XoU@40mRQF;7P zUh@g3F-6~=SnD5xGE-_v_74(e5zu*LQM*0$@mU>S)kr9N#iB}E6Y_xH-CKZ8yol(Z zV?GfGixkM-rlsmn`$1~xOPCXd?Lcg4%_fj2S1(H1vJpIvPZd|E^ma`v7ckcz9S@g@;0`nN%NAi^D<6nqCzZJh4kyL!lzlCD%E!=d$$E zHG+XH&9e=f=~Z{Nl5>m!*M&$d_;|W0a@l!x#Zf0XNHEC^;{p84b4M}c%kAOwUvz`) zK{_0Q_P^wH!bU#^X6FH%VpE2DBm~jg|15AQ(qP4&_C7*3sAovhcZogbn#rO^!_e>K zMVA2(Y+`!YjJB%}sMwopC8rz_n$?m_xNrlS1TAmIX~YmTXdqn#xVvJ5GRwung*qiA zvrzd7ojX;Ek&nvEj;4$3AHu7*h-%Dawea((sLFAx@hMzJfmdKg8)^^G#HZ9Emq@-& z9zYAx7hjK)^o)i2Rw{b9_5~9YvH>vLBvH)qZKQb^yW0*@B{ z=GVo(#aqpYja*N+{52IVaIzDJAAQp%<;mG_o++}EX! zTxou=^_Z0o72x2nO<`Mrs{$5 z1y#efJ*@J+GsH;}r7QTRjj98&vytxp%p#OSozBX;`OQPA$0rJQR1MYzO)wrg5rS`4 z#Tf;k>$IpRt#A`HlrUU?gnmPr&0F^|>qCKb>PQYQe|qj)*M6SCx7iN_F7ti&xDCU{Vwsi6QQ`MpU50zUO^%-r2sJp=u47UQTS>?#h&)Ep zx-(Ak^fi{Mu^-0 z@{qKM005}}cjXc1UrajPf1uUOt&HXM9UTAvr4fS3dMV?67-?rWXaGQv)8F^n|MRCw z!`cyx1L?OAe|oELDW2K4w*G~rTaAW|<7g@bbz7|Hs!72>#Gj!*D1jAf%l6}~Y5OpY zFsf~rWXQDO(f*;(BO&|Z4xK%~6JbVa4ig@rp>S#~9>`bhmFFTX-+H8ug*@a2C1C5Z z0r)8ndYcG@UVH3skr-McS?gYWt8RekM-5tJnNEobBu&9`TX1LP#`F(VihNLdU8K}| zWKj~h3^k$lU>}(pA$*Wwp)#s;pSMP_DB+Nr$~7ebt4eVq?jjd z+e5Q(h(%*=2A?G6% zB|Y4wiVc#;Tv#gmh%+_@9xiYORlwbV8?c)^0#Lyt@0(SZs))6wV{6L>9m&vfbg2G9 z`*NG8uHbC9s6o@0uNX23>Fr%^7^I%t7pPuffRItl5xIWB)9iWdk`JpMS=_C?6W|Pp ze;|>qe0NGSY&!)smLp!UgDq&U5q72P%+4cEU>n}l^#3G!$Q< z0!?!u*mq+jB{)2AVO5tq`b@uNWKj-6a#7JfNfRt}Fkl!Vs+AUrlPWJX@lqIkP}9j3 zSbb`W+D#-l4;&HIt9%1^g)Cw#L#rafjGgDBUUcY4?mFtrzVySJ4+Dmg6eLa5@bbFB zcbfwl)f!q8J+{3-BO{wopM2x#ZZ=1OW`((>kXKX#`Xf99EQhJmuY|r84^iZc2hk^z zgNX7rob-};YH_;K+&&{??o0NXJf!x?IPi{KS*CHPmhPm_iQ+^EFq8QGH@!BN^YUq5 zS3I~Ms2VhE&o9(y8gwSP;=ThiLPaffj}}=m&B@%k!|uS-kA}S=NvC+*P$q?5x}bJx z-N><00D%NVNwdSx&)R>+q|F+Oer$emv5ZdxVC#C~=Gl0+HakC05mEW+{e#MmR#IDs z@Di>)NPquKkQs9`lI$zytMBVppB-Cz7q;<-#NN<2bYJjmx%-`y-T@QjMh7t(9T8Bpzo?o^R9M;C|)5^6{+QR z=`6ZY2dU;Ndeb0&+tusR=os=1EV>#YjVX}L+HGSnC1cnBoS3qLd6cl!r0_2x?2`|d zl%P+~bX)~+=4Cny4iZxr#zY$oNu6*rk9&(oQXqB=m61k6nr8cU{|FeF&)Vns3IG@_ zf`02rV&vJ>k!emFR4}bh;DyB5LPt}ESDkHdyJ_3zCffWXihCL>YW%`DO+8ZsG$!-Q zg-FcAK_;4?l`r7OqMwSQXHGSY<`vX5|Cse}ZRlpAv~t>#4~fPIP%DpVV)%+A)zvwQ zA&m4E5xJw^0hV{_s+&2^BtwY_ly0XNy91RV?y6rcVmyH0kdFmQaRaT}i!Zk%JfpFp zhUG+G?Y;sUkiiIUhti;t#>MY9|foNz~2 zI^%?%TqO&h-Q|~3!`Ws*KS&U|)sR_}3qsk@#*79AcG$nLX+NjGN*5Ql6nA{QUjYgt zI>w}#HZd->sISMpJBJ0|BHAAKRTOyiyvg-rVCw;koKCw$Il6LE(S+;rwKJaR?*6(L z(jV~?kt=)h;jwclCeQ-C9X`|!l{#95?w`ve-9p^wG;W#PHEfac>L8%bfp2JJUAMnU z1WIT(dd!THh{|?kxkrunmAETM>z=OlIOy#cT51N`r}KyE%^iT!P;%+UjO{TRG6 z5ZET2{84>ItFKWi`GC9pCQfCPGMO?fog)^8$GL6psQ9&K{lhFK z-PwA^!MIAXrRgEMX3`WlWoF6tiT%Vm^|owwAL?-3B($XQt{kXTzgDMJ3W+Syx@I)+ z08_mQ_>zO835&X5>Ggq0VD73PHKGiD^4_rvU2DGhx#lASFDkqf$tx3+ z#QCag71r&8o!Z&m)OZ^#V){j1N0(qJNE z&R{Bm(#fDp=L^IsV!kZsSbw~4RfO6k7LRRm#>H!8>V}J)7NBs2-04{$U+t;~p8>bL zQe;uXc&9+Z%;Y0-4P-c6koJHK`G72^W?8m_r!TP^+8`*g90d!p+K)Hw5^k<>Z}}g} zwdQff4L4kRhtl5Xb#;{Y%S}S?5!QQ}=q@|3z&&fxc0oJuZ)tmH+|4=q5pm$znY!K0 zxlW_L6Np%Ta`Z~+n9*8G?mek&z0JS=3+m|E*JLX2w-YRh002Pw-;Q-kP7cQU*8fF> z^S>GDmQ=0(0~7JfxwM}k>#!@VJpGp^>c<`y1u1pa?|@|_q%ezQT$dygRaogE^!;%y zPN|ZNg4z5k1jFtpeBXAG?YYO{wG^OgM}eeck?kL+=KyyQPV!2ZoMxW7PoLzR#zz!_P{h%8CA$pI+*o zIJ;S^aiB?MZU$T3lZ_M4^H1O2rD%h(2KQ z%II@<_~{m`4I!|X2fMtsNXCJSE5+t>H|<$Bloj!}3C^_dhm8vRS0`Qk8WSszk}KjB zi}o#um_<~G`V#e^>JW#E6Mat6F`+zpP~NEk^A@YlgS2|ykWkj-1gwP_&Yxn_mBG4e zX`lmPrd@(#k_WLx+I+Z8qw!>TUD30QM8<11`1g zVpc!{YpN`=Zs_K8zH!uA{`r2owv;Rc`S@q40!B-^aJO`H|FE{?#HsETU9dxXJDrXD z1Xl=7>bD0ea!#b=Z}CY#juY6v$~d5lXP*&JE_-|I$&ks{n&^F_F(?pmqNM`Vq4@GV zow&Iwda)jVA#(LAC{iRm$ zB|B$RBz^_DdBdzDW(%?9b8!aiaV_{9%n4>y0UUBP(?K*a^4_Nx*kKD_pnAetINh~! zi9qPigBMq>ogaNFJ~+B30Z5VEgNbWXpR>7L~0j zpJMF<^lP)XE^U}o6~mL^3n@Hwftr>xu&WXG<-5Tmdng?n>*-NwEL-_x4iWLhNd|V> z__IJ$W2pVSUCF19XESA`;v^-qQ|Q{ld)EFimTd|!Bo1BFbC@NpD{<+LWal1e+8pWR zc|}PXtC}%+mGr)Idh_`k?0;<=I@80lDF512>VA)}^#5(n{T^Tcvt=k?q;L1DRr&wQ zhX0$i+tT<=J9ebsv|~6WYZE_mqHAHV%ou8SH{Wv361r?AhYRtKGs}pgJ{PB~ww?Rw zW+HwvkQifWd48$|i|<{wd!1yeDP(e64=ABWM3iHR=@X}?f-8s=c{L?PHyJ<)kxDCJ zU=l1wh%`y@?rqWXljaZywJc*d4>PhzY#oF3NoJ4gO%8uxk&A)@G|AL_TBvw?y#zud z+(UrALfgB>)>kPUtqcr~Qf{f`|4SVWYCVO7K;cvC!jGcMh#KJ(_F2ezJkX7hPE?IP>yi z2&k}R??}&%IXS4H= z{f%`jN<5$gANPwvVCpdZvyo~?YwSQZX?FrR7EDyMs^ZF>O5m4NJUvC~z_I7bjrmPh zW{D?(%17-&_8LqT?^AY2qiSnRN$-l-zgtEFO;?|Dm-pj_(lAND1&tOKh|vH!#PCkT z!fPgVhaPZgx+q}bqu-{U=U6S$*q<1*(l$Sgw1Ew?TUm-l%X~tWW=QGVd$~d{TTr_) zagQCT5{eWoFbnpm39m=**4&cvHzX~%w^3iM&1d}wY@1ld4_$xDZfL6a@O)`3L+&j@yg>d+M!bM$Y9Z2OfTKWG9K4n~<%`R8szKm5&KLfcBbR4;}(X`|!oA z7}-m5JQnwQd`&r6jzPDUo8fwkqR?Fs^yr^J)jdQXPHNy5HEe0CLW%MT&Opk@vc^`+ z8g#B><)nbG*$#pHR8$pNHkLRw+iI4L)|4&;^`C!+LF?1-kIR4%I{xl5`;2{I*B zR|o%@7(gjpY7`=c!1t&J@-qmVZ_5)No4@+E6Z~sAMektCd$=d6pSUH_$Yqz|z?e4* z&GEwFg%N{?N2fij)4Xz~v~fsk=BSGSuR5MPh9mO69D^+_OS}!gw4&3$u8H8Q`3H_nMq^fL?Ul6K+h>22mMK?D?9Nwb;@aZq$jyTx!*y)twrwBw4s*Y~ z`*vR!knJF**KF(FE%F4NZl~+rBs8Vq%}kM?v8SuRa?|(fYtM^f*myFAaa^dxRtIgp zUiD35!Yr^Tjju$AXo0J#9cB8eZ{Vzdj81!gEN|U4g;Z_Be?E)8sa}fWpjoWr)Ny@< zm;5D(EyrG#qH@*N2PNb4Ajer=v?-J5ovTf+qoFGPH4&^o09(H_frMr7r`vj=})s36Wu{hT|?SR z+f!Qey}y?5>}Z@)tIhqFbuQ~MpFkNHsOhBom+@|$5(qyS98T)>gLu;H{<8OhuO1QKSJOR zbnG4F1jwsRyjYq2R3tdhfHce1SpnD>ZNbmHAAi?Y2g|A0-0AJr#a$DbEzK|qCKayz z@Jg6Rl6%stATU2X0nQ+>K$`zD_OsL!Hg&YC8$e3V3H2hzpY+!9+q@{2meH#3Ws;z* zZPmuID)Txft?M#&yjob@_#Zs8u|EbYe+Lh2h4x|6@vkJ6mH4x_G;Lwux*Jjm{^GD6 z)uU$rzhV(tOyF4vndR$LD{V`DDiOw%NnN{_p`49eshGw$ojmGdwb-6wyP({c`>7ms zQ%pq=`8{;X5yAV>eN!bv0@_LX-p6|&pw-F3b6DKf%Z?z(2)V~6Xl_`k`?=Qe*wrXa zAr%I3&?_o!r`azV6XlJMeQ>u?Ra3@-u0X5ub~`AI(1&>S2i@d9#Te^RY5odGRYsu~ zM5P@!3eP|I=m0PsT}7H=HdphfDM|Oy%Y^qo)QP%$cRwY$KJeVUqz2fX zxQ`#fKYiMU3<7Jw7KQAxyV&ieK|%4Bx4Oq`?xVbo=(XN0C|ru9O=uXdVgzT~Hbk}4 zTtQghE?$={vzaa3_?23!8W%D7PLvcc4=Kajd`=fztqP@o9(iVbk$x|Zge=wOpyp=g zp!}m3DVz&HmyCyO(w%0T>swZQGh6Uq`ZVp6y7Bij&13YgN6;K!`mR2s^;|jOzx|Dv ziSprr*)e%#Jp&SDpN?D+lD-FgSX@hS_z*5BR)in4OlGtMcnWMw+P=tw7@j$s^bl7* zwsEpPx&GJbML&89&*}G+CJX=H0BQf{^8G(&mfs;p(b&+|!RY@uyez58IQ|~hx-L{N zPlekU&z#bwP)y6&pM{k~##5!ac?A#S1*HM00m4Jg{1^f&4;d_fvpP!=QD7*QaZw4&_F3}hhA(on_%2~e(z2ceN9 z_mg*1!d4xz5Y|fnv608P0uBs?^Qnt8QC*3>$N96@?vj6s%4P{|w5%mWvIJZRG87-U zF-r|jYo@P+tdPo(WEZEfX;9+U%t}DzCt2gZr1psCZl9v4#efKOP>p2p1-UPJSygw*z`*|k%@C8HMlfh9wOkm#ztsk zjJ!;n05Mu;12(BF_-db7PuQ2u+wG_saL3B8UIzya9-Q8Rs$w<3RRm_5oR+@s>-WKsNh2^LxH5NXE-eeq?YcH_uSG*PW0aSmuLtSyntY`hrla4 zpw8Y*;M#EdaIXpCy0EcvMwqT0Qrj6+akRfcolb-94gOxZ-D}i4{gju zkiJP79!6!mAwvVYLG`SIj`5HAN4;aD*HnF?E2 zuRPdZ%;ZR02QM!GzlLJntAkxLjnFdINS7Sf8r)ssz#d9-`sRKg)9?+*FMNd)O_STe zLBMd*;AUiOAuhGtGgWxauYyad)A;*m+)lDP9^8B~=wq|38ZRq^(Z zzX?oxY**E#;b4y$UC2UGu4+slqw8H)sa9guB+`hoqO?Ljvab%0Dcr!jwAda(Wo?xx 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 diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..3a5356e64 --- /dev/null +++ b/pom.xml @@ -0,0 +1,166 @@ + + 4.0.0 + ch.eitchnet + ch.eitchnet.utils + jar + 1.0-SNAPSHOT + ch.eitchnet.utils + https://github.com/eitch/ch.eitchnet.utils + + + UTF-8 + + + + + 2011 + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl.html + repo + + + + eitchnet.ch + http://blog.eitchnet.ch + + + + eitch + Robert von Vurg + eitch@eitchnet.ch + http://blog.eitchnet.ch + eitchnet.ch + http://blog.eitchnet.ch + + architect + developer + + +1 + + http://localhost + + + + + + Github Issues + https://github.com/eitch/ch.eitchnet.utils/issues + + + + + + scm:git:https://github.com/eitch/ch.eitchnet.utils.git + scm:git:git@github.com:eitch/ch.eitchnet.utils.git + https://github.com/eitch/ch.eitchnet.utils + + + + + + + junit + junit + 4.10 + test + + + log4j + log4j + 1.2.17 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 2.3 + + UTF-8 + + + + + + diff --git a/src/ch/eitchnet/rmi/RMIFileClient.java b/src/main/java/ch/eitchnet/rmi/RMIFileClient.java similarity index 100% rename from src/ch/eitchnet/rmi/RMIFileClient.java rename to src/main/java/ch/eitchnet/rmi/RMIFileClient.java diff --git a/src/ch/eitchnet/rmi/RmiFileDeletion.java b/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java similarity index 100% rename from src/ch/eitchnet/rmi/RmiFileDeletion.java rename to src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java diff --git a/src/ch/eitchnet/rmi/RmiFileHandler.java b/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java similarity index 100% rename from src/ch/eitchnet/rmi/RmiFileHandler.java rename to src/main/java/ch/eitchnet/rmi/RmiFileHandler.java diff --git a/src/ch/eitchnet/rmi/RmiFilePart.java b/src/main/java/ch/eitchnet/rmi/RmiFilePart.java similarity index 100% rename from src/ch/eitchnet/rmi/RmiFilePart.java rename to src/main/java/ch/eitchnet/rmi/RmiFilePart.java diff --git a/src/ch/eitchnet/rmi/RmiHelper.java b/src/main/java/ch/eitchnet/rmi/RmiHelper.java similarity index 100% rename from src/ch/eitchnet/rmi/RmiHelper.java rename to src/main/java/ch/eitchnet/rmi/RmiHelper.java diff --git a/src/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java similarity index 100% rename from src/ch/eitchnet/utils/helper/FileHelper.java rename to src/main/java/ch/eitchnet/utils/helper/FileHelper.java diff --git a/src/ch/eitchnet/utils/helper/Log4jConfigurator.java b/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java similarity index 100% rename from src/ch/eitchnet/utils/helper/Log4jConfigurator.java rename to src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java diff --git a/src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java b/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java similarity index 100% rename from src/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java rename to src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java diff --git a/src/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java similarity index 100% rename from src/ch/eitchnet/utils/helper/StringHelper.java rename to src/main/java/ch/eitchnet/utils/helper/StringHelper.java diff --git a/src/ch/eitchnet/utils/helper/SystemHelper.java b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java similarity index 100% rename from src/ch/eitchnet/utils/helper/SystemHelper.java rename to src/main/java/ch/eitchnet/utils/helper/SystemHelper.java diff --git a/src/ch/eitchnet/utils/objectfilter/ITransactionObject.java b/src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java similarity index 100% rename from src/ch/eitchnet/utils/objectfilter/ITransactionObject.java rename to src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java diff --git a/src/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java similarity index 100% rename from src/ch/eitchnet/utils/objectfilter/ObjectCache.java rename to src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java diff --git a/src/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java similarity index 100% rename from src/ch/eitchnet/utils/objectfilter/ObjectFilter.java rename to src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java diff --git a/src/ch/eitchnet/utils/objectfilter/Operation.java b/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java similarity index 100% rename from src/ch/eitchnet/utils/objectfilter/Operation.java rename to src/main/java/ch/eitchnet/utils/objectfilter/Operation.java From 10b8cb407115c3f0a2744530aac66e4565e52ff9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 15:37:24 +0200 Subject: [PATCH 078/457] [Major] rebuilt project to use Maven. - Now a "mvn compile" will build the project downloading dependencies as needed - Note: as ch.eitchnet.utils is a dependency, it must be installed first by using "mvn install" --- .gitignore | 12 +- config/log4j.properties | 12 -- pom.xml | 171 ++++++++++++++++++ .../java}/ch/eitchnet/xmlpers/XmlDao.java | 0 .../ch/eitchnet/xmlpers/XmlDaoFactory.java | 0 .../ch/eitchnet/xmlpers/XmlFilePersister.java | 0 .../xmlpers/XmlPersistenceExecption.java | 0 .../xmlpers/XmlPersistenceHandler.java | 0 .../xmlpers/XmlPersistencePathBuilder.java | 0 .../xmlpers/XmlPersistenceTransaction.java | 0 src/main/resources/log4j.properties | 12 ++ .../xmlpers}/test/XmlPersistenceTest.java | 7 +- .../eitchnet/xmlpers}/test/impl/MyClass.java | 2 +- .../xmlpers}/test/impl/MyClassDao.java | 2 +- .../xmlpers}/test/impl/MyDaoFactory.java | 2 +- 15 files changed, 197 insertions(+), 23 deletions(-) delete mode 100644 config/log4j.properties create mode 100644 pom.xml rename src/{ => main/java}/ch/eitchnet/xmlpers/XmlDao.java (100%) rename src/{ => main/java}/ch/eitchnet/xmlpers/XmlDaoFactory.java (100%) rename src/{ => main/java}/ch/eitchnet/xmlpers/XmlFilePersister.java (100%) rename src/{ => main/java}/ch/eitchnet/xmlpers/XmlPersistenceExecption.java (100%) rename src/{ => main/java}/ch/eitchnet/xmlpers/XmlPersistenceHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java (100%) rename src/{ => main/java}/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java (100%) create mode 100644 src/main/resources/log4j.properties rename {test/ch/eitchnet/xmlpers/java => src/test/java/ch/eitchnet/xmlpers}/test/XmlPersistenceTest.java (97%) rename {test/ch/eitchnet/xmlpers/java => src/test/java/ch/eitchnet/xmlpers}/test/impl/MyClass.java (98%) rename {test/ch/eitchnet/xmlpers/java => src/test/java/ch/eitchnet/xmlpers}/test/impl/MyClassDao.java (98%) rename {test/ch/eitchnet/xmlpers/java => src/test/java/ch/eitchnet/xmlpers}/test/impl/MyDaoFactory.java (96%) diff --git a/.gitignore b/.gitignore index c77d8a98c..8cbe22473 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ *.class -# Package Files # -*.jar -*.war -*.ear +# Maven target +target/ # Project files -tmp +tmp/ +logs/ + +# External libraries +lib/ diff --git a/config/log4j.properties b/config/log4j.properties deleted file mode 100644 index a745f967b..000000000 --- a/config/log4j.properties +++ /dev/null @@ -1,12 +0,0 @@ -log4j.rootLogger = info, stdout, file - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] %C{1} %M - %m%n - -log4j.appender.file=org.apache.log4j.RollingFileAppender -log4j.appender.file.File=${user.dir}/logs/app.log -log4j.appender.file.MaxFileSize=10000KB -log4j.appender.file.MaxBackupIndex=0 -log4j.appender.file.layout=org.apache.log4j.PatternLayout -log4j.appender.file.layout.ConversionPattern=%d %5p [%t] %C{1} %M - %m%n \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..2dd69cbbb --- /dev/null +++ b/pom.xml @@ -0,0 +1,171 @@ + + 4.0.0 + ch.eitchnet + ch.eitchnet.xmlpers + jar + 1.0-SNAPSHOT + ch.eitchnet.xmlpers + https://github.com/eitch/ch.eitchnet.xmlpers + + + UTF-8 + + + + + 2011 + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl.html + repo + + + + eitchnet.ch + http://blog.eitchnet.ch + + + + eitch + Robert von Vurg + eitch@eitchnet.ch + http://blog.eitchnet.ch + eitchnet.ch + http://blog.eitchnet.ch + + architect + developer + + +1 + + http://localhost + + + + + + Github Issues + https://github.com/eitch/ch.eitchnet.xmlpers/issues + + + + + + scm:git:https://github.com/eitch/ch.eitchnet.xmlpers.git + scm:git:git@github.com:eitch/ch.eitchnet.xmlpers.git + https://github.com/eitch/ch.eitchnet.xmlpers + + + + + + + junit + junit + 4.10 + test + + + log4j + log4j + 1.2.17 + + + ch.eitchnet + ch.eitchnet.utils + 1.0-SNAPSHOT + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 2.3 + + UTF-8 + + + + + + diff --git a/src/ch/eitchnet/xmlpers/XmlDao.java b/src/main/java/ch/eitchnet/xmlpers/XmlDao.java similarity index 100% rename from src/ch/eitchnet/xmlpers/XmlDao.java rename to src/main/java/ch/eitchnet/xmlpers/XmlDao.java diff --git a/src/ch/eitchnet/xmlpers/XmlDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java similarity index 100% rename from src/ch/eitchnet/xmlpers/XmlDaoFactory.java rename to src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java diff --git a/src/ch/eitchnet/xmlpers/XmlFilePersister.java b/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java similarity index 100% rename from src/ch/eitchnet/xmlpers/XmlFilePersister.java rename to src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java similarity index 100% rename from src/ch/eitchnet/xmlpers/XmlPersistenceExecption.java rename to src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java similarity index 100% rename from src/ch/eitchnet/xmlpers/XmlPersistenceHandler.java rename to src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java diff --git a/src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java similarity index 100% rename from src/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java rename to src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java diff --git a/src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java similarity index 100% rename from src/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java rename to src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 000000000..6aa19bb24 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,12 @@ +log4j.rootLogger = info, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] %C{1} %M - %m%n + +#log4j.appender.file=org.apache.log4j.RollingFileAppender +#log4j.appender.file.File=${user.dir}/logs/app.log +#log4j.appender.file.MaxFileSize=10000KB +#log4j.appender.file.MaxBackupIndex=0 +#log4j.appender.file.layout=org.apache.log4j.PatternLayout +#log4j.appender.file.layout.ConversionPattern=%d %5p [%t] %C{1} %M - %m%n diff --git a/test/ch/eitchnet/xmlpers/java/test/XmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java similarity index 97% rename from test/ch/eitchnet/xmlpers/java/test/XmlPersistenceTest.java rename to src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java index a380e56a5..9296e4750 100644 --- a/test/ch/eitchnet/xmlpers/java/test/XmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java @@ -22,7 +22,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.java.xmlpers.test; +package ch.eitchnet.xmlpers.test; import java.io.File; import java.util.List; @@ -33,13 +33,14 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.featherlite.plugin.xmlpers.test.impl.MyClass; -import ch.eitchnet.featherlite.plugin.xmlpers.test.impl.MyDaoFactory; import ch.eitchnet.utils.helper.Log4jConfigurator; import ch.eitchnet.utils.objectfilter.ITransactionObject; import ch.eitchnet.xmlpers.XmlPersistenceExecption; import ch.eitchnet.xmlpers.XmlPersistenceHandler; import ch.eitchnet.xmlpers.XmlPersistenceTransaction; +import ch.eitchnet.xmlpers.test.impl.MyClass; +import ch.eitchnet.xmlpers.test.impl.MyClassDao; +import ch.eitchnet.xmlpers.test.impl.MyDaoFactory; /** * @author Robert von Burg diff --git a/test/ch/eitchnet/xmlpers/java/test/impl/MyClass.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java similarity index 98% rename from test/ch/eitchnet/xmlpers/java/test/impl/MyClass.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java index 903f947c9..ec2692970 100644 --- a/test/ch/eitchnet/xmlpers/java/test/impl/MyClass.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java @@ -22,7 +22,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.java.xmlpers.test.impl; +package ch.eitchnet.xmlpers.test.impl; import ch.eitchnet.utils.objectfilter.ITransactionObject; diff --git a/test/ch/eitchnet/xmlpers/java/test/impl/MyClassDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java similarity index 98% rename from test/ch/eitchnet/xmlpers/java/test/impl/MyClassDao.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java index 6468b2710..771f8e115 100644 --- a/test/ch/eitchnet/xmlpers/java/test/impl/MyClassDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java @@ -22,7 +22,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.java.xmlpers.test.impl; +package ch.eitchnet.xmlpers.test.impl; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; diff --git a/test/ch/eitchnet/xmlpers/java/test/impl/MyDaoFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java similarity index 96% rename from test/ch/eitchnet/xmlpers/java/test/impl/MyDaoFactory.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java index 9114f1ce4..c0ed22409 100644 --- a/test/ch/eitchnet/xmlpers/java/test/impl/MyDaoFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java @@ -22,7 +22,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.java.xmlpers.test.impl; +package ch.eitchnet.xmlpers.test.impl; import ch.eitchnet.xmlpers.XmlDao; import ch.eitchnet.xmlpers.XmlDaoFactory; From 0e026bf7130716dd4862f64d6755b6bfdfeb577a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 15:40:01 +0200 Subject: [PATCH 079/457] [Major] rebuilt project to use Maven. - Now a "mvn compile" will build the project - Note, that since ch.eitchnet.utils is a dependency, it must be installed first using "mvn install" to be able to compile Privilege --- .classpath | 10 - .gitignore | 4 + .project | 17 -- .settings/org.eclipse.jdt.core.prefs | 87 --------- lib/dom4j-1.6.1-src.zip | Bin 440064 -> 0 bytes lib/dom4j-1.6.1.jar | Bin 313898 -> 0 bytes lib/log4j-1.2.15.jar | Bin 391834 -> 0 bytes pom.xml | 176 ++++++++++++++++++ .../handler/DefaultEncryptionHandler.java | 0 .../handler/DefaultPrivilegeHandler.java | 0 .../privilege/handler/EncryptionHandler.java | 0 .../privilege/handler/PersistenceHandler.java | 0 .../privilege/handler/PrivilegeHandler.java | 0 .../handler/XmlPersistenceHandler.java | 0 .../helper/BootstrapConfigurationHelper.java | 0 .../privilege/helper/ClassHelper.java | 0 .../eitchnet/privilege/helper/HashHelper.java | 0 .../helper/InitializationHelper.java | 0 .../privilege/helper/PasswordCreaterUI.java | 0 .../privilege/helper/PasswordCreator.java | 0 .../privilege/helper/XmlConstants.java | 0 .../eitchnet/privilege/helper/XmlHelper.java | 0 .../privilege/i18n/AccessDeniedException.java | 0 .../privilege/i18n/PrivilegeException.java | 0 .../eitchnet/privilege/model/Certificate.java | 0 .../privilege/model/PrivilegeRep.java | 0 .../privilege/model/Restrictable.java | 0 .../ch/eitchnet/privilege/model/RoleRep.java | 0 .../ch/eitchnet/privilege/model/UserRep.java | 0 .../eitchnet/privilege/model/UserState.java | 0 .../privilege/model/internal/Privilege.java | 0 .../privilege/model/internal/Role.java | 0 .../privilege/model/internal/Session.java | 0 .../privilege/model/internal/User.java | 0 .../privilege/policy/DefaultPrivilege.java | 0 .../privilege/policy/PrivilegePolicy.java | 0 .../privilege/test/PrivilegeTest.java | 0 .../privilege/test/TestRestrictable.java | 0 38 files changed, 180 insertions(+), 114 deletions(-) delete mode 100644 .classpath delete mode 100644 .project delete mode 100644 .settings/org.eclipse.jdt.core.prefs delete mode 100644 lib/dom4j-1.6.1-src.zip delete mode 100644 lib/dom4j-1.6.1.jar delete mode 100644 lib/log4j-1.2.15.jar create mode 100644 pom.xml rename src/{ => main/java}/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/EncryptionHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/PersistenceHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/PrivilegeHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/ClassHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/HashHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/InitializationHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/PasswordCreaterUI.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/PasswordCreator.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/XmlConstants.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/XmlHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/i18n/AccessDeniedException.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/i18n/PrivilegeException.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/Certificate.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/PrivilegeRep.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/Restrictable.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/RoleRep.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/UserRep.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/UserState.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/internal/Privilege.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/internal/Role.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/internal/Session.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/internal/User.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/policy/DefaultPrivilege.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/policy/PrivilegePolicy.java (100%) rename {test => src/test/java}/ch/eitchnet/privilege/test/PrivilegeTest.java (100%) rename {test => src/test/java}/ch/eitchnet/privilege/test/TestRestrictable.java (100%) diff --git a/.classpath b/.classpath deleted file mode 100644 index f52b649dc..000000000 --- a/.classpath +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/.gitignore b/.gitignore index e493dd6ee..7cfc64f07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ bin dist + +# Maven target +target/ + diff --git a/.project b/.project deleted file mode 100644 index 2bbc43fc5..000000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - Privilege - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index c436f312a..000000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,87 +0,0 @@ -#Sat Nov 06 15:16:05 CET 2010 -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.doc.comment.support=enabled -org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.autoboxing=warning -org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning -org.eclipse.jdt.core.compiler.problem.deadCode=warning -org.eclipse.jdt.core.compiler.problem.deprecation=warning -org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled -org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled -org.eclipse.jdt.core.compiler.problem.discouragedReference=warning -org.eclipse.jdt.core.compiler.problem.emptyStatement=warning -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning -org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled -org.eclipse.jdt.core.compiler.problem.fieldHiding=warning -org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning -org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning -org.eclipse.jdt.core.compiler.problem.forbiddenReference=error -org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning -org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning -org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning -org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning -org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning -org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public -org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore -org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning -org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public -org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags -org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning -org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning -org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning -org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning -org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning -org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning -org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning -org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning -org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning -org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning -org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=enabled -org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning -org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning -org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning -org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning -org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning -org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning -org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning -org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning -org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=warning -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled -org.eclipse.jdt.core.compiler.problem.unusedImport=warning -org.eclipse.jdt.core.compiler.problem.unusedLabel=warning -org.eclipse.jdt.core.compiler.problem.unusedLocal=warning -org.eclipse.jdt.core.compiler.problem.unusedParameter=warning -org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled -org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning -org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning -org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/lib/dom4j-1.6.1-src.zip b/lib/dom4j-1.6.1-src.zip deleted file mode 100644 index 39b4137d350e6bad0405b9b3aca2ea6143ea79de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 440064 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%0ArZNyr#a_uiccET<7nN;-(B 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 Z!Q0dci2%c+Q1#&NE-;_kWe5G|{{hteC$azl diff --git a/lib/dom4j-1.6.1.jar b/lib/dom4j-1.6.1.jar deleted file mode 100644 index c8c4dbb92d6c23a7fbb2813eb721eb4cce91750c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 313898 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

    ^<7W@(9yr9mUm2tK?C|&v@%Rm35X`o{%fo+w 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%0A?GZzYp7)bLPx}z&<;hZaX^DZ#RA|ACLXD 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%0Aok&dtGbv;D|BgjT@f(sv0`S4#(FudZ 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 diff --git a/lib/log4j-1.2.15.jar b/lib/log4j-1.2.15.jar deleted file mode 100644 index c930a6ab4d4b73c1a6feb9e929091205664bb340..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 391834 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 z≫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

    sl*CtvD7j$_;?KD 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

    {0o23%x0zG^lL-+)0hE&z$21eha>paa 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 LL+IIXJH`J1EdB>a diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..53f666142 --- /dev/null +++ b/pom.xml @@ -0,0 +1,176 @@ + + 4.0.0 + ch.eitchnet + ch.eitchnet.privilege + jar + 1.0-SNAPSHOT + ch.eitchnet.privilege + https://github.com/eitch/ch.eitchnet.privilege + + + UTF-8 + + + + + 2011 + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl.html + repo + + + + eitchnet.ch + http://blog.eitchnet.ch + + + + eitch + Robert von Vurg + eitch@eitchnet.ch + http://blog.eitchnet.ch + eitchnet.ch + http://blog.eitchnet.ch + + architect + developer + + +1 + + http://localhost + + + + + + Github Issues + https://github.com/eitch/ch.eitchnet.privilege/issues + + + + + + scm:git:https://github.com/eitch/ch.eitchnet.privilege.git + scm:git:git@github.com:eitch/ch.eitchnet.privilege.git + https://github.com/eitch/ch.eitchnet.privilege + + + + + + + junit + junit + 4.10 + test + + + log4j + log4j + 1.2.17 + + + maven + dom4j + 1.7-20060614 + + + ch.eitchnet + ch.eitchnet.utils + 1.0-SNAPSHOT + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 2.3 + + UTF-8 + + + + + + diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/EncryptionHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/PersistenceHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/PrivilegeHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/ClassHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java diff --git a/src/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/HashHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/HashHelper.java diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/InitializationHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java rename to src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/PasswordCreator.java rename to src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/XmlConstants.java rename to src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/XmlHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java similarity index 100% rename from src/ch/eitchnet/privilege/i18n/AccessDeniedException.java rename to src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java diff --git a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java similarity index 100% rename from src/ch/eitchnet/privilege/i18n/PrivilegeException.java rename to src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java similarity index 100% rename from src/ch/eitchnet/privilege/model/Certificate.java rename to src/main/java/ch/eitchnet/privilege/model/Certificate.java diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java similarity index 100% rename from src/ch/eitchnet/privilege/model/PrivilegeRep.java rename to src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java similarity index 100% rename from src/ch/eitchnet/privilege/model/Restrictable.java rename to src/main/java/ch/eitchnet/privilege/model/Restrictable.java diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java similarity index 100% rename from src/ch/eitchnet/privilege/model/RoleRep.java rename to src/main/java/ch/eitchnet/privilege/model/RoleRep.java diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java similarity index 100% rename from src/ch/eitchnet/privilege/model/UserRep.java rename to src/main/java/ch/eitchnet/privilege/model/UserRep.java diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java similarity index 100% rename from src/ch/eitchnet/privilege/model/UserState.java rename to src/main/java/ch/eitchnet/privilege/model/UserState.java diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java similarity index 100% rename from src/ch/eitchnet/privilege/model/internal/Privilege.java rename to src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java similarity index 100% rename from src/ch/eitchnet/privilege/model/internal/Role.java rename to src/main/java/ch/eitchnet/privilege/model/internal/Role.java diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java similarity index 100% rename from src/ch/eitchnet/privilege/model/internal/Session.java rename to src/main/java/ch/eitchnet/privilege/model/internal/Session.java diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java similarity index 100% rename from src/ch/eitchnet/privilege/model/internal/User.java rename to src/main/java/ch/eitchnet/privilege/model/internal/User.java diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java similarity index 100% rename from src/ch/eitchnet/privilege/policy/DefaultPrivilege.java rename to src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java similarity index 100% rename from src/ch/eitchnet/privilege/policy/PrivilegePolicy.java rename to src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java similarity index 100% rename from test/ch/eitchnet/privilege/test/PrivilegeTest.java rename to src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java similarity index 100% rename from test/ch/eitchnet/privilege/test/TestRestrictable.java rename to src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java From c65fe9c247f5d6e6b847cb745557f743a2c184e5 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 15:44:47 +0200 Subject: [PATCH 080/457] [Minor] added eclipse settings path to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 2595e2a6b..76c4111c6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ tmp/ # Maven target target/ +# Eclipse settings +.classpath +.project +.settings/ From 50f2cba067b7b271de510f064a56a327e6a8b30a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 15:46:29 +0200 Subject: [PATCH 081/457] [Minor] Added eclipse settings file to .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 8cbe22473..3487fb9f8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,10 @@ target/ tmp/ logs/ +# Eclipse settings +.classpath +.project +.settings/ + # External libraries lib/ From 7e6987b35823a51134cec91e6bfe20c262c3be29 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 15:47:26 +0200 Subject: [PATCH 082/457] [Minor] Added eclipse settings to .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 7cfc64f07..8d6b8f627 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ dist # Maven target target/ +# Eclipse settings +.classpath +.project +.settings/ + From 7dbd5af6a71ceb84cf8b0b13f5a8599565efede5 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 22:50:52 +0200 Subject: [PATCH 083/457] [Bugfix] fixed a resource leak where a BufferedReader was not close In FileHelper.readFileToString() the BufferedReader was not closed. This has been corrected --- .../java/ch/eitchnet/utils/helper/FileHelper.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 4f310ec8a..66fdc5369 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -60,9 +60,11 @@ public class FileHelper { * @return the contents of a file as a string */ public static final String readFileToString(File file) { + + BufferedReader bufferedReader = null; try { - BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); + bufferedReader = new BufferedReader(new FileReader(file)); StringBuilder sb = new StringBuilder(); String line; @@ -77,6 +79,14 @@ public class FileHelper { throw new RuntimeException("Filed does not exist " + file.getAbsolutePath()); } catch (IOException e) { throw new RuntimeException("Could not read file " + file.getAbsolutePath()); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + logger.error("Failed to close BufferedReader: " + e.getLocalizedMessage()); + } + } } } From ffc9fa77cdf7f9d03b33406104091fec7754095e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 23:16:16 +0200 Subject: [PATCH 084/457] [Minor] change mvn eclipse configuration to load sources as well --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 3a5356e64..7ad55e0a2 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,16 @@ + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + org.apache.maven.plugins From 61f6e6cdc406830a7a1a3503b9a6a0c05e1eaeec Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 23:17:07 +0200 Subject: [PATCH 085/457] [Minor] change mvn eclipse configuration to load sources as well --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 2dd69cbbb..d889c4f99 100644 --- a/pom.xml +++ b/pom.xml @@ -131,6 +131,16 @@ + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + org.apache.maven.plugins From f471be1ea73e0c6cc218e502bc59c684a3ad13e3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 23:17:45 +0200 Subject: [PATCH 086/457] [Minor] change mvn eclipse configuration to load sources as well --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 53f666142..61b5d186c 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,16 @@ + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + org.apache.maven.plugins From b78e9bb064c348dd3887ae997298b244dcbff7a5 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 29 Jul 2012 18:23:31 +0200 Subject: [PATCH 087/457] [New] added a new PrivilegeHandler.queryUser(UserRep) to query users - The querying is done by passing a UserRep with all the fields set which need to be equal to select the user. Null fields are understood as not required - Performed further clean up in that all password variables have been changed to type byte[]. - Cleaned up copyright notices in files - fixed version of Privilege 0.1.0-SNAPSHOT in pom.xml --- MANIFEST.MF | 7 - README | 20 +- build.xml | 77 ----- pom.xml | 17 +- .../handler/DefaultEncryptionHandler.java | 24 +- .../handler/DefaultPrivilegeHandler.java | 280 ++++++++++++++---- .../privilege/handler/EncryptionHandler.java | 17 +- .../privilege/handler/PersistenceHandler.java | 8 +- .../privilege/handler/PrivilegeHandler.java | 28 +- .../handler/XmlPersistenceHandler.java | 8 +- .../helper/BootstrapConfigurationHelper.java | 8 +- .../privilege/helper/ClassHelper.java | 8 +- .../eitchnet/privilege/helper/HashHelper.java | 8 +- .../helper/InitializationHelper.java | 14 +- .../privilege/helper/PasswordCreaterUI.java | 8 +- .../privilege/helper/PasswordCreator.java | 8 +- .../privilege/helper/XmlConstants.java | 8 +- .../eitchnet/privilege/helper/XmlHelper.java | 8 +- .../privilege/i18n/AccessDeniedException.java | 8 +- .../privilege/i18n/PrivilegeException.java | 8 +- .../eitchnet/privilege/model/Certificate.java | 8 +- .../privilege/model/PrivilegeRep.java | 8 +- .../privilege/model/Restrictable.java | 8 +- .../ch/eitchnet/privilege/model/RoleRep.java | 8 +- .../ch/eitchnet/privilege/model/UserRep.java | 8 +- .../eitchnet/privilege/model/UserState.java | 8 +- .../privilege/model/internal/Privilege.java | 8 +- .../privilege/model/internal/Role.java | 8 +- .../privilege/model/internal/Session.java | 8 +- .../privilege/model/internal/User.java | 8 +- .../privilege/policy/DefaultPrivilege.java | 8 +- .../privilege/policy/PrivilegePolicy.java | 8 +- .../privilege/test/PrivilegeTest.java | 68 ++--- .../privilege/test/TestRestrictable.java | 8 +- 34 files changed, 346 insertions(+), 398 deletions(-) delete mode 100644 MANIFEST.MF delete mode 100644 build.xml diff --git a/MANIFEST.MF b/MANIFEST.MF deleted file mode 100644 index 8a69d98b1..000000000 --- a/MANIFEST.MF +++ /dev/null @@ -1,7 +0,0 @@ -Manifest-Version: 1.0 -Implementation-Vendor: eitchnet.ch -Implementation-Title: eitchnet Java Privilege implementation -Implementation-Version: 0.0.3 -Specification-Vendor: eitchnet.ch -Specification-Title: eitchnet Java Privilege implementation -Specification-Version: 0.1 diff --git a/README b/README index c4c3ef642..73ec1b402 100644 --- a/README +++ b/README @@ -17,7 +17,7 @@ a LDAP repository with only the Privilege JAR. Privilege is distributed under the GNU Lesser General Public License on Github.com and can be downloaded at - https://github.com/eitch/Privilege + https://github.com/eitch/ch.eitchnet.privilege The main developer is Robert von Burg who also maintains the Github repository. He is available for all questions regarding Privilege @@ -72,20 +72,10 @@ directory of the Repository Compiling ################################ -Since Privilege is a Java library, it is built using Apache Ant. The build.xml -file is configured to build Privilege directly from the root directory by simply -calling ant at the command line: +Privilege is a Maven3 project and can be built by simply performing the +following command: -$ ant -Buildfile: /data/src/apixxo_WS/Privilege/build.xml - -dist: - [mkdir] Created dir: /data/src/apixxo_WS/Privilege/dist - [copy] Copying 28 files to /data/src/apixxo_WS/Privilege/bin - [jar] Building jar: /data/src/apixxo_WS/Privilege/dist/Privilege.jar - -BUILD SUCCESSFUL -Total time: 0 seconds +$ mvn compile Using ################################ @@ -96,7 +86,7 @@ which contains a few test cases including showing how to load Privilege. This documentation is still in need of more work, but for any questions please don't hesitate to write an e-mail to the developer and we'll find a solution. - Switzerland, the 7. August 2011 + Switzerland, the 29. July 2012 Robert von Burg diff --git a/build.xml b/build.xml deleted file mode 100644 index 34f371db8..000000000 --- a/build.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 61b5d186c..5a7c18a40 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ch.eitchnet ch.eitchnet.privilege jar - 1.0-SNAPSHOT + 0.1.0-SNAPSHOT ch.eitchnet.privilege https://github.com/eitch/ch.eitchnet.privilege @@ -156,6 +156,21 @@ 1.6 + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + org.apache.maven.plugins diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 10d02c49e..98a5609ba 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; import java.io.UnsupportedEncodingException; @@ -84,6 +78,22 @@ public class DefaultEncryptionHandler implements EncryptionHandler { } } + /** + * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) + */ + @Override + public String convertToHash(byte[] bytes) { + try { + + return HashHelper.stringToHash(this.hashAlgorithm, bytes); + + } catch (NoSuchAlgorithmException e) { + throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); + } catch (UnsupportedEncodingException e) { + throw new PrivilegeException("Charset ASCII is not supported!", e); + } + } + /** * @see ch.eitchnet.privilege.handler.EncryptionHandler#nextToken() */ diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 28442e470..748619a16 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,12 +17,13 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -112,7 +108,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public RoleRep getRole(String roleName) { Role role = this.persistenceHandler.getRole(roleName); if (role == null) - throw new PrivilegeException("Role " + roleName + " does not exist!"); + return null; return role.asRoleRep(); } @@ -123,7 +119,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep getUser(String username) { User user = this.persistenceHandler.getUser(username); if (user == null) - throw new PrivilegeException("User " + username + " does not exist!"); + return null; return user.asUserRep(); } @@ -162,6 +158,135 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return policy; } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#queryUsers(ch.eitchnet.privilege.model.UserRep) + */ + @Override + public List queryUsers(UserRep selectorRep) { + + String selUserId = selectorRep.getUserId(); + String selUsername = selectorRep.getUsername(); + String selFirstname = selectorRep.getFirstname(); + String selSurname = selectorRep.getSurname(); + UserState selUserState = selectorRep.getUserState(); + Locale selLocale = selectorRep.getLocale(); + Set selRoles = selectorRep.getRoles(); + Map selPropertyMap = selectorRep.getProperties(); + + List result = new ArrayList(); + List allUsers = this.persistenceHandler.getAllUsers(); + for (User user : allUsers) { + + // selections + boolean userIdSelected; + boolean usernameSelected; + boolean firstnameSelected; + boolean surnameSelected; + boolean userStateSelected; + boolean localeSelected; + boolean roleSelected; + boolean propertySelected; + + // userId + if (selUserId == null) + userIdSelected = true; + else if (selUserId.equals(user.getUserId())) + userIdSelected = true; + else + userIdSelected = false; + + // username + if (selUsername == null) + usernameSelected = true; + else if (selUsername.equals(user.getUsername())) + usernameSelected = true; + else + usernameSelected = false; + + // firstname + if (selFirstname == null) + firstnameSelected = true; + else if (selFirstname.equals(user.getFirstname())) + firstnameSelected = true; + else + firstnameSelected = false; + + // surname + if (selSurname == null) + surnameSelected = true; + else if (selSurname.equals(user.getSurname())) + surnameSelected = true; + else + surnameSelected = false; + + // user state + if (selUserState == null) + userStateSelected = true; + else if (selUserState.equals(user.getUserState())) + userStateSelected = true; + else + userStateSelected = false; + + // locale + if (selLocale == null) + localeSelected = true; + else if (selLocale.equals(user.getLocale())) + localeSelected = true; + else + localeSelected = false; + + // roles + roleSelected = isSelectedByRole(selRoles, user.getRoles()); + + // properties + propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties()); + + boolean selected = userIdSelected && usernameSelected && firstnameSelected && surnameSelected + && userStateSelected && localeSelected && roleSelected && propertySelected; + + if (selected) + result.add(user.asUserRep()); + } + + return result; + } + + /** + * @param selPropertyMap + * @param properties + * @return + */ + private boolean isSelectedByProperty(Map selPropertyMap, Map properties) { + + if (selPropertyMap == null) + return true; + + if (selPropertyMap.isEmpty() && properties.isEmpty()) + return true; + + for (String selKey : selPropertyMap.keySet()) { + + String value = properties.get(selKey); + if (value == null || !value.equals(selPropertyMap.get(selKey))) + return false; + } + + return true; + } + + /** + * @param selRoles + * @param roles + * @return + */ + private boolean isSelectedByRole(Set selRoles, Set roles) { + + if (selRoles == null) + return true; + + return roles.containsAll(selRoles); + } + /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.RoleRep) @@ -187,29 +312,34 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * ch.eitchnet.privilege.model.UserRep, java.lang.String) */ @Override - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) { + public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) { + try { - // validate who is doing this - validateIsPrivilegeAdmin(certificate); + // validate who is doing this + validateIsPrivilegeAdmin(certificate); - String passwordHash = null; - if (password != null) { + String passwordHash = null; + if (password != null) { - // validate password meets basic requirements - validatePassword(password); + // validate password meets basic requirements + validatePassword(password); - // hash password - passwordHash = this.encryptionHandler.convertToHash(password); + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } + + // create new user + // XXX should the collections be recreated and the getRoles() and getProperties() methods be removed? + User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), + userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), + userRep.getProperties()); + + // delegate to persistence handler + this.persistenceHandler.addOrReplaceUser(user); + + } finally { + clearPassword(password); } - - // create new user - // XXX should the collections be recreated and the getRoles() and getProperties() methods be removed? - User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), - userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), - userRep.getProperties()); - - // delegate to persistence handler - this.persistenceHandler.addOrReplaceUser(user); } /** @@ -450,42 +580,47 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * java.lang.String, java.lang.String) */ @Override - public void setUserPassword(Certificate certificate, String username, String password) { + public void setUserPassword(Certificate certificate, String username, byte[] password) { + try { - // check if certificate is for same user, in which case user is changing their own password - if (certificate.getUsername().equals(username)) { + // check if certificate is for same user, in which case user is changing their own password + if (certificate.getUsername().equals(username)) { - // validate the certificate - isCertificateValid(certificate); + // validate the certificate + isCertificateValid(certificate); - } else { + } else { - // otherwise validate the the certificate is for a privilege admin - validateIsPrivilegeAdmin(certificate); + // otherwise validate the the certificate is for a privilege admin + validateIsPrivilegeAdmin(certificate); + } + + // get User + User user = this.persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + String passwordHash = null; + if (password != null) { + + // validate password meets basic requirements + validatePassword(password); + + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } + + // create new user + User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), + user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); + + // delegate user replacement to persistence handler + this.persistenceHandler.addOrReplaceUser(newUser); + + } finally { + clearPassword(password); } - - // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - String passwordHash = null; - if (password != null) { - - // validate password meets basic requirements - validatePassword(password); - - // hash password - passwordHash = this.encryptionHandler.convertToHash(password); - } - - // create new user - User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); - - // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -519,16 +654,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * if the user credentials are not valid */ @Override - public Certificate authenticate(String username, String password) { + public Certificate authenticate(String username, byte[] password) { // create certificate Certificate certificate; try { - // both username and password must at least have 3 characters in length + // username must be at least 3 characters in length if (username == null || username.length() < 3) throw new PrivilegeException("The given username is shorter than 3 characters"); - else if (password == null || password.length() < 3) - throw new PrivilegeException("The given password is shorter than 3 characters"); + + // and validate the password + validatePassword(password); // we only work with hashed passwords String passwordHash = this.encryptionHandler.convertToHash(password); @@ -578,6 +714,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } catch (RuntimeException e) { logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); throw e; + } finally { + clearPassword(password); } // return the certificate @@ -801,11 +939,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(java.lang.String) */ @Override - public void validatePassword(String password) throws PrivilegeException { + public void validatePassword(byte[] password) throws PrivilegeException { - if (password == null || password.isEmpty()) { + if (password == null || password.length == 0) { throw new PrivilegeException("A password may not be empty!"); } + + if (password.length < 3) { + throw new PrivilegeException("The given password is shorter than 3 characters"); + } } /** @@ -909,4 +1051,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } + /** + * @param password + */ + private void clearPassword(byte[] password) { + if (password != null) { + for (int i = 0; i < password.length; i++) { + password[i] = 0; + } + } + } } diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java index 50d758173..b25dbd82a 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; import java.util.Map; @@ -51,6 +45,15 @@ public interface EncryptionHandler { * @return the hash of the string after converting */ public String convertToHash(String string); + + /** + * Converts a given byte array, e.g. a password to a hash which is defined by the concrete implementation + * + * @param bytes + * the bytes to convert + * @return the hash of the string after converting + */ + public String convertToHash(byte[] bytes); /** * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration the diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index 29ad14fac..bba8f20c0 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; import java.util.List; diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 03bda0c84..c6577ea64 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,9 +17,9 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; +import java.util.List; import java.util.Locale; import ch.eitchnet.privilege.i18n.AccessDeniedException; @@ -75,6 +70,17 @@ public interface PrivilegeHandler { */ public RoleRep getRole(String roleName); + /** + * Method to query {@link UserRep} which meet the criteria set in the given {@link UserRep}. Null fields mean the + * fields are not relevant. + * + * @param selectorRep + * the {@link UserRep} to use as criteria selection + * + * @return a list of {@link UserRep}s which fit the given criteria + */ + public List queryUsers(UserRep selectorRep); + /** * Removes the user with the given username * @@ -171,7 +177,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) + public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, PrivilegeException; /** @@ -251,7 +257,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserPassword(Certificate certificate, String username, String password) + public void setUserPassword(Certificate certificate, String username, byte[] password) throws AccessDeniedException, PrivilegeException; /** @@ -326,7 +332,7 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user credentials are not valid */ - public Certificate authenticate(String username, String password) throws AccessDeniedException; + public Certificate authenticate(String username, byte[] password) throws AccessDeniedException; /** * Invalidates the {@link Session} for the given {@link Certificate}, effectively logging out the user who was @@ -416,7 +422,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if the password does not implement the requirement of the concrete implementation */ - public void validatePassword(String password) throws PrivilegeException; + public void validatePassword(byte[] password) throws PrivilegeException; /** * Persists any changes to the privilege data model. Changes are thus not persisted immediately, but must be diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 6910ad735..9e2608437 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; import java.io.File; diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 386ce8476..42677a198 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.File; diff --git a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java index 7a741681f..41b10b775 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import ch.eitchnet.privilege.i18n.PrivilegeException; diff --git a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java index 628ac518a..b0da71fee 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.UnsupportedEncodingException; diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java index 9f2dda7ba..fad40de25 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.File; @@ -40,6 +34,8 @@ import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.helper.SystemHelper; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the @@ -163,6 +159,10 @@ public class InitializationHelper { for (Element parameter : elements) { String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); String value = parameter.attributeValue(XmlConstants.XML_ATTR_VALUE); + + // replace any defined system properties + value = StringHelper.replaceSystemPropertiesIn(value); + parameterMap.put(name, value); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index ad6aa9546..f2c4231c8 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.awt.Dimension; diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java index f57a5615e..228cd529c 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.BufferedReader; diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index c554473a9..3a9a16bc3 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; /** diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index 50433c212..9cb3786b7 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.File; diff --git a/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java index 5e9029717..818335103 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.i18n; /** diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java index db281372a..0f9fe5111 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.i18n; /** diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 762ff0606..87d30a3da 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import java.io.Serializable; diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index 6d39f685e..e13d6a1f4 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import java.io.Serializable; diff --git a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java index d3d401a61..95ef2e3c4 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import ch.eitchnet.privilege.model.internal.Privilege; diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index 4af09ca17..f7a37a0f5 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import java.io.Serializable; diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 38e3562e3..66b27284c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import java.io.Serializable; diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index d98b9df14..4b451b66f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import ch.eitchnet.privilege.model.internal.User; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java index 2ee6121f5..c62456fbd 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model.internal; import java.util.Collections; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index aa76e6bd6..ab929ffde 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model.internal; import java.util.Collections; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java index 2210c8fc3..7e3bb8614 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model.internal; import ch.eitchnet.privilege.handler.PrivilegeHandler; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index bdfc7a438..9e12026ac 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model.internal; import java.util.Collections; diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index bd67adf0e..ebb061656 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.i18n.AccessDeniedException; diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 9c0d14bb0..50fc79837 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.i18n.AccessDeniedException; diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 859ff4415..2bd5c0a77 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.test; import java.io.File; @@ -58,15 +52,15 @@ import ch.eitchnet.privilege.model.UserState; public class PrivilegeTest { private static final String ADMIN = "admin"; - private static final String PASS_ADMIN = "admin"; + private static final byte[] PASS_ADMIN = "admin".getBytes(); private static final String BOB = "bob"; private static final String TED = "ted"; - private static final String PASS_BOB = "admin1"; + private static final byte[] PASS_BOB = "admin1".getBytes(); private static final String ROLE_FEATHERLITE_USER = "FeatherliteUser"; private static final String ROLE_USER = "user"; - private static final String PASS_DEF = "def"; - private static final String PASS_BAD = "123"; - private static final String PASS_TED = "12345"; + private static final byte[] PASS_DEF = "def".getBytes(); + private static final byte[] PASS_BAD = "123".getBytes(); + private static final byte[] PASS_TED = "12345".getBytes(); private static final Logger logger = Logger.getLogger(PrivilegeTest.class); @@ -103,11 +97,17 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); privilegeHandler.invalidateSession(certificate); } + private byte[] copyBytes(byte[] bytes) { + byte[] copy = new byte[bytes.length]; + System.arraycopy(bytes, 0, copy, 0, bytes.length); + return copy; + } + /** * @throws Exception * if something goes wrong @@ -115,7 +115,7 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_BAD); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_BAD)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); privilegeHandler.invalidateSession(certificate); } @@ -139,7 +139,7 @@ public class PrivilegeTest { @Test public void testAddUserBobAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); // let's add a new user bob UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, @@ -148,7 +148,7 @@ public class PrivilegeTest { logger.info("Added user " + BOB); // set bob's password - privilegeHandler.setUserPassword(certificate, BOB, PASS_BOB); + privilegeHandler.setUserPassword(certificate, BOB, copyBytes(PASS_BOB)); logger.info("Set Bob's password"); privilegeHandler.invalidateSession(certificate); } @@ -161,7 +161,7 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); privilegeHandler.invalidateSession(certificate); } @@ -171,7 +171,7 @@ public class PrivilegeTest { */ @Test public void testEnableUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); privilegeHandler.invalidateSession(certificate); } @@ -185,7 +185,7 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); privilegeHandler.invalidateSession(certificate); } @@ -196,7 +196,7 @@ public class PrivilegeTest { */ @Test public void testAddRole() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); @@ -211,7 +211,7 @@ public class PrivilegeTest { */ @Test public void testAddRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); privilegeHandler.invalidateSession(certificate); } @@ -222,7 +222,7 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); privilegeHandler.invalidateSession(certificate); } @@ -236,7 +236,7 @@ public class PrivilegeTest { public void testFailAddUserTedAsBob() throws Exception { // auth as Bog - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted @@ -254,7 +254,7 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); privilegeHandler.invalidateSession(certificate); @@ -267,7 +267,7 @@ public class PrivilegeTest { @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted @@ -288,11 +288,11 @@ public class PrivilegeTest { @Test public void testSetTedPwdAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set ted's password to default - privilegeHandler.setUserPassword(certificate, TED, PASS_DEF); + privilegeHandler.setUserPassword(certificate, TED, copyBytes(PASS_DEF)); privilegeHandler.invalidateSession(certificate); } @@ -303,8 +303,8 @@ public class PrivilegeTest { */ @Test public void testTedChangesOwnPwd() throws Exception { - Certificate certificate = privilegeHandler.authenticate(TED, PASS_DEF); - privilegeHandler.setUserPassword(certificate, TED, PASS_TED); + Certificate certificate = privilegeHandler.authenticate(TED, copyBytes(PASS_DEF)); + privilegeHandler.setUserPassword(certificate, TED, copyBytes(PASS_TED)); privilegeHandler.invalidateSession(certificate); } @@ -314,7 +314,7 @@ public class PrivilegeTest { */ @Test public void testAuthAsTed() throws Exception { - Certificate certificate = privilegeHandler.authenticate(TED, PASS_TED); + Certificate certificate = privilegeHandler.authenticate(TED, copyBytes(PASS_TED)); privilegeHandler.invalidateSession(certificate); } @@ -325,7 +325,7 @@ public class PrivilegeTest { @Test public void testPerformRestrictableAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable @@ -342,7 +342,7 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailPerformRestrictableAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable @@ -361,7 +361,7 @@ public class PrivilegeTest { @Test public void testAddFeatherliteRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_FEATHERLITE_USER); logger.info("Added " + ROLE_FEATHERLITE_USER + " to " + BOB); privilegeHandler.invalidateSession(certificate); @@ -375,7 +375,7 @@ public class PrivilegeTest { */ @Test public void testPerformRestrictableAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable diff --git a/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java index e384e89df..117103ee6 100644 --- a/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.test; import ch.eitchnet.privilege.model.Restrictable; From 2afba1876c6c60305029197b2689f9e8c5a6a53b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 29 Jul 2012 18:27:15 +0200 Subject: [PATCH 088/457] [Major] added methods to SystemHelper and new class ProcessHelper SystemHelper now has a number of methods to query OS information ProcessHelper is a helper to execute system calls --- build.xml | 73 ------- pom.xml | 17 +- .../java/ch/eitchnet/rmi/RMIFileClient.java | 11 +- .../java/ch/eitchnet/rmi/RmiFileDeletion.java | 11 +- .../java/ch/eitchnet/rmi/RmiFileHandler.java | 11 +- .../java/ch/eitchnet/rmi/RmiFilePart.java | 11 +- src/main/java/ch/eitchnet/rmi/RmiHelper.java | 11 +- .../ch/eitchnet/utils/helper/FileHelper.java | 11 +- .../utils/helper/Log4jConfigurator.java | 158 ++++++++++++-- .../utils/helper/Log4jPropertyWatchDog.java | 11 +- .../eitchnet/utils/helper/ProcessHelper.java | 200 ++++++++++++++++++ .../eitchnet/utils/helper/StringHelper.java | 11 +- .../eitchnet/utils/helper/SystemHelper.java | 100 ++++++++- .../objectfilter/ITransactionObject.java | 11 +- .../utils/objectfilter/ObjectCache.java | 11 +- .../utils/objectfilter/ObjectFilter.java | 11 +- .../utils/objectfilter/Operation.java | 11 +- 17 files changed, 481 insertions(+), 199 deletions(-) delete mode 100644 build.xml create mode 100644 src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java diff --git a/build.xml b/build.xml deleted file mode 100644 index ee31a36fb..000000000 --- a/build.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cleaning build folder ${buildFolder} - - - - - - - Cleaning dist folder ${distFolder} - - - - - - - diff --git a/pom.xml b/pom.xml index 7ad55e0a2..ffcf01466 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ch.eitchnet ch.eitchnet.utils jar - 1.0-SNAPSHOT + 0.1.0-SNAPSHOT ch.eitchnet.utils https://github.com/eitch/ch.eitchnet.utils @@ -146,6 +146,21 @@ 1.6 + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + org.apache.maven.plugins diff --git a/src/main/java/ch/eitchnet/rmi/RMIFileClient.java b/src/main/java/ch/eitchnet/rmi/RMIFileClient.java index d5ad7c2cd..9ddbab772 100644 --- a/src/main/java/ch/eitchnet/rmi/RMIFileClient.java +++ b/src/main/java/ch/eitchnet/rmi/RMIFileClient.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.rmi; diff --git a/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java b/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java index 0c68caba3..a8fc6d385 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java +++ b/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.rmi; diff --git a/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java b/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java index 47aba008b..f21355aae 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java +++ b/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.rmi; diff --git a/src/main/java/ch/eitchnet/rmi/RmiFilePart.java b/src/main/java/ch/eitchnet/rmi/RmiFilePart.java index 8bddfed1c..750b993a6 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFilePart.java +++ b/src/main/java/ch/eitchnet/rmi/RmiFilePart.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.rmi; diff --git a/src/main/java/ch/eitchnet/rmi/RmiHelper.java b/src/main/java/ch/eitchnet/rmi/RmiHelper.java index 96aa27879..a8530ad0c 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiHelper.java +++ b/src/main/java/ch/eitchnet/rmi/RmiHelper.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.rmi; diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 66fdc5369..9cba18dc3 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.helper; diff --git a/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java b/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java index e70672640..327ab5446 100644 --- a/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java +++ b/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.helper; @@ -28,6 +23,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Date; import java.util.Properties; @@ -96,37 +92,80 @@ public class Log4jConfigurator { *

    * Any properties in the properties are substituted using * {@link StringHelper#replaceProperties(Properties, Properties)} and then the configuration file is written to a - * new file /tmp/{@link Log4jConfigurator#FILE_LOG4J_TEMP} and then finally + * new file /tmp/ {@link Log4jConfigurator#FILE_LOG4J_TEMP} and then finally * {@link PropertyConfigurator#configureAndWatch(String)} is called so that the configuration is loaded and log4j * watches the temporary file for configuration changes *

    */ public static synchronized void loadLog4jConfiguration() { - // first clean up any old watch dog in case of a programmatic re-load of hte configuration - cleanupOldWatchdog(); - - // get a configured log4j properties file, or use default RSPConfigConstants.FILE_LOG4J + // get a configured log4j properties file, or use default + // RSPConfigConstants.FILE_LOG4J String fileLog4j = SystemHelper.getProperty(Log4jConfigurator.class.getName(), Log4jConfigurator.PROP_FILE_LOG4J, Log4jConfigurator.FILE_LOG4J); // get the root directory String userDir = System.getProperty("user.dir"); String configDir = userDir + "/config/"; - String tmpDir = userDir + "/tmp/"; - // load the log4j.properties file String pathNameToLog4j = configDir + fileLog4j; File log4JPath = new File(pathNameToLog4j); - if (!log4JPath.exists()) - throw new RuntimeException("The log4j configuration file does not exist at " + log4JPath.getAbsolutePath()); + + try { + + // load the log4j.properties file + if (!log4JPath.exists()) + throw new RuntimeException("The log4j configuration file does not exist at " + + log4JPath.getAbsolutePath()); + + // now perform the loading + loadLog4jConfiguration(log4JPath); + + } catch (Exception e) { + + Log4jConfigurator.configure(); + logger.error(e, e); + logger.error("Log4j COULD NOT BE INITIALIZED. Please check the log4j configuration file exists at " + + log4JPath.getAbsolutePath()); + + } + } + + /** + *

    + * Loads the given log4j configuration + *

    + * + *

    + * Any properties in the properties are substituted using + * {@link StringHelper#replaceProperties(Properties, Properties)} and then the configuration file is written to a + * new file /tmp/ {@link Log4jConfigurator#FILE_LOG4J_TEMP} and then finally + * {@link PropertyConfigurator#configureAndWatch(String)} is called so that the configuration is loaded and log4j + * watches the temporary file for configuration changes + *

    + * + * @param log4jConfigPath + */ + public static synchronized void loadLog4jConfiguration(File log4jConfigPath) { + + if (log4jConfigPath == null) + throw new RuntimeException("log4jConfigPath may not be null!"); + + // first clean up any old watch dog in case of a programmatic re-load of the configuration + cleanupOldWatchdog(); + + // get the root directory + String userDir = System.getProperty("user.dir"); + String tmpDir = userDir + "/tmp/"; String pathNameToLog4jTemp = tmpDir + Log4jConfigurator.FILE_LOG4J_TEMP; Properties log4jProperties = new Properties(); FileInputStream fin = null; FileOutputStream fout = null; + try { - fin = new FileInputStream(pathNameToLog4j); + + fin = new FileInputStream(log4jConfigPath); log4jProperties.load(fin); fin.close(); @@ -142,7 +181,8 @@ public class Log4jConfigurator { log4jProperties.store(fout, "Running instance log4j configuration " + new Date()); fout.close(); - // XXX if the server is in a web context, then we may not use the FileWatchDog + // XXX if the server is in a web context, then we may not use the + // FileWatchDog BasicConfigurator.resetConfiguration(); watchDog = new Log4jPropertyWatchDog(pathNameToLog4jTemp); watchDog.start(); @@ -150,10 +190,12 @@ public class Log4jConfigurator { logger.info("Log4j is configured to use and watch file " + pathNameToLog4jTemp); } catch (Exception e) { + Log4jConfigurator.configure(); logger.error(e, e); - logger.error("Log4j COULD NOT BE INITIALIZED. Please check the " + fileLog4j + " file at " - + pathNameToLog4j); + logger.error("Log4j COULD NOT BE INITIALIZED. Please check the log4j configuration file at " + + log4jConfigPath); + } finally { if (fin != null) { try { @@ -172,6 +214,80 @@ public class Log4jConfigurator { } } + /** + *

    + * Loads the log4j configuration file as a class resource by calling {@link Class#getResourceAsStream(String)} for + * the given class + *

    + * + * @param clazz + */ + public static synchronized void loadLog4jConfigurationAsResource(Class clazz) { + + try { + + if (clazz == null) + throw new RuntimeException("clazz may not be null!"); + + InputStream resourceAsStream = clazz.getResourceAsStream("/" + FILE_LOG4J); + if (resourceAsStream == null) { + throw new RuntimeException("The resource '" + FILE_LOG4J + "' could not be found for class " + + clazz.getName()); + } + + // load the properties from the input stream + Properties log4jProperties = new Properties(); + log4jProperties.load(resourceAsStream); + + // and then + loadLog4jConfiguration(log4jProperties); + + } catch (Exception e) { + Log4jConfigurator.configure(); + logger.error(e, e); + logger.error("Log4j COULD NOT BE INITIALIZED. Please check that the log4j configuration file '" + + FILE_LOG4J + "' exists as a resource for class " + clazz.getName() + + " and is a valid properties configuration"); + } + } + + /** + *

    + * Loads the given log4j configuration. Log4j is configured with the given properties. The only change is that + * {@link StringHelper#replaceProperties(Properties, Properties)} is used to replace any properties + *

    + * + *

    + * No property watch dog is loaded + *

    + * + * @param log4jProperties + * the properties to use for the log4j configuration + */ + public static synchronized void loadLog4jConfiguration(Properties log4jProperties) { + + try { + + if (log4jProperties == null) + throw new RuntimeException("log4jProperties may not be null!"); + + // first clean up any old watch dog in case of a programmatic re-load of the configuration + cleanupOldWatchdog(); + + // replace any variables + StringHelper.replaceProperties(log4jProperties, System.getProperties()); + + // now configure log4j + PropertyConfigurator.configure(log4jProperties); + logger.info("Log4j is configured using the given properties."); + + } catch (Exception e) { + Log4jConfigurator.configure(); + logger.error(e, e); + logger.error("Log4j COULD NOT BE INITIALIZED. The given log4jProperties seem not to be valid!"); + } + } + /** * Cleanup a running watch dog */ diff --git a/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java b/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java index a41f8dc2a..a4f2aecda 100644 --- a/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java +++ b/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.helper; diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java new file mode 100644 index 000000000..30a7a17bd --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.utils + * + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.utils. If not, see . + * + */ +package ch.eitchnet.utils.helper; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.apache.log4j.Logger; + +/** + * @author Robert von Burg + */ +public class ProcessHelper { + + private static final Logger logger = Logger.getLogger(ProcessHelper.class); + + public static ProcessResult runCommand(String command) { + final StringBuffer sb = new StringBuffer(); + sb.append("=====================================\n"); + try { + + final Process process = Runtime.getRuntime().exec(command); + + final BufferedReader errorStream = new BufferedReader( + new InputStreamReader(process.getErrorStream())); + Thread errorIn = new Thread("errorIn") { + @Override + public void run() { + readStream(sb, "[ERROR] ", errorStream); + } + }; + errorIn.start(); + + final BufferedReader inputStream = new BufferedReader( + new InputStreamReader(process.getInputStream())); + Thread infoIn = new Thread("infoIn") { + @Override + public void run() { + readStream(sb, "[INFO] ", inputStream); + } + }; + infoIn.start(); + + int returnValue = process.waitFor(); + + errorIn.join(100l); + infoIn.join(100l); + sb.append("=====================================\n"); + + return new ProcessResult(returnValue, sb.toString(), null); + + } catch (IOException e) { + throw new RuntimeException("Failed to perform command: " + + e.getLocalizedMessage(), e); + } catch (InterruptedException e) { + logger.error("Interrupted!"); + sb.append("[FATAL] Interrupted"); + return new ProcessResult(-1, sb.toString(), e); + } + } + + public static ProcessResult runCommand(File workingDirectory, + String... commandAndArgs) { + + if (!workingDirectory.exists()) + throw new RuntimeException("Working directory does not exist at " + + workingDirectory.getAbsolutePath()); + if (commandAndArgs == null || commandAndArgs.length == 0) + throw new RuntimeException("No command passed!"); + + final StringBuffer sb = new StringBuffer(); + sb.append("=====================================\n"); + try { + + ProcessBuilder processBuilder = new ProcessBuilder(commandAndArgs); + processBuilder.environment(); + processBuilder.directory(workingDirectory); + + final Process process = processBuilder.start(); + + final BufferedReader errorStream = new BufferedReader( + new InputStreamReader(process.getErrorStream())); + Thread errorIn = new Thread("errorIn") { + @Override + public void run() { + readStream(sb, "[ERROR] ", errorStream); + } + }; + errorIn.start(); + + final BufferedReader inputStream = new BufferedReader( + new InputStreamReader(process.getInputStream())); + Thread infoIn = new Thread("infoIn") { + @Override + public void run() { + readStream(sb, "[INFO] ", inputStream); + } + }; + infoIn.start(); + + int returnValue = process.waitFor(); + + errorIn.join(100l); + infoIn.join(100l); + sb.append("=====================================\n"); + + return new ProcessResult(returnValue, sb.toString(), null); + + } catch (IOException e) { + throw new RuntimeException("Failed to perform command: " + + e.getLocalizedMessage(), e); + } catch (InterruptedException e) { + logger.error("Interrupted!"); + sb.append("[FATAL] Interrupted"); + return new ProcessResult(-1, sb.toString(), e); + } + } + + public static class ProcessResult { + public final int returnValue; + public final String processOutput; + public final Throwable t; + + public ProcessResult(int returnValue, String processOutput, Throwable t) { + this.returnValue = returnValue; + this.processOutput = processOutput; + this.t = t; + } + } + + private static void readStream(StringBuffer sb, String prefix, + BufferedReader bufferedReader) { + String line; + try { + while ((line = bufferedReader.readLine()) != null) { + sb.append(prefix + line + "\n"); + } + } catch (IOException e) { + String msg = "Faild to read from " + prefix + " stream: " + + e.getLocalizedMessage(); + sb.append("[FATAL] " + msg + "\n"); + } + } + + public static void openFile(File pdfPath) { + + ProcessResult processResult; + if (SystemHelper.isLinux()) { + processResult = runCommand("xdg-open " + pdfPath.getAbsolutePath()); + } else if (SystemHelper.isMacOS()) { + processResult = runCommand("open " + pdfPath.getAbsolutePath()); + } 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); + } else { + throw new UnsupportedOperationException("Unexpected OS: " + + SystemHelper.osName); + } + + logProcessResult(processResult); + } + + public static void logProcessResult(ProcessResult processResult) { + if (processResult.returnValue == 0) { + logger.info("Process executed successfully"); + } else if (processResult.returnValue == -1) { + logger.error("Process execution failed:\n" + + processResult.processOutput); + logger.error(processResult.t, processResult.t); + } else { + logger.info("Process execution was not successful with return value:" + + processResult.returnValue + + "\n" + + processResult.processOutput); + } + } +} diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index c5807b2c1..c69de70d0 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.helper; diff --git a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java index 2449d9038..eeb3aa284 100644 --- a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java @@ -1,29 +1,25 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.helper; + /** * A helper class for {@link System} methods * @@ -31,6 +27,94 @@ package ch.eitchnet.utils.helper; */ public class SystemHelper { + private static final SystemHelper instance; + + static { + instance = new SystemHelper(); + } + + public static SystemHelper getInstance() { + return instance; + } + + public static final String osName = System.getProperty("os.name"); + public static final String osArch = System.getProperty("os.arch"); + public static final String osVersion = System.getProperty("os.version"); + public static final String javaVendor = System.getProperty("java.vendor"); + public static final String javaVersion = System.getProperty("java.version"); + + /** + * private constructor + */ + private SystemHelper() { + // + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return asString(); + } + + /** + * @see java.lang.Object#toString() + */ + public static String asString() { + StringBuilder sb = new StringBuilder(); + sb.append(osName); + sb.append(" "); + sb.append(osArch); + sb.append(" "); + sb.append(osVersion); + sb.append(" "); + sb.append("on Java " + javaVendor); + sb.append(" version "); + sb.append(javaVersion); + return sb.toString(); + } + + public static String getUserDir() { + return System.getProperty("user.dir"); + } + + public static boolean isMacOS() { + return osName.startsWith("Mac"); + } + + public static boolean isWindows() { + return osName.startsWith("Win"); + } + + public static boolean isLinux() { + return osName.startsWith("Lin"); + } + + public static boolean is32bit() { + return osArch.equals("x86") || osArch.equals("i386") || osArch.equals("i686"); + } + + public static boolean is64bit() { + return osArch.equals("x86_64") || osArch.equals("amd64"); + } + + 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() { + return "Memory available " + getMaxMemory() + " / Used: " + getUsedMemory() + " / Free:" + getFreeMemory(); + } + /** * 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 diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java b/src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java index a243ed9ba..c9bfc3efe 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Michael Gatto - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.objectfilter; diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java index b329c4f0b..e2d0b5e09 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Michael Gatto - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.objectfilter; diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index 4a29eec99..a79970c11 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Michael Gatto - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.objectfilter; diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java b/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java index 8d23723ba..1dce6362a 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Michael Gatto - * - */ - -/* * This file is part of ch.eitchnet.java.utils * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.utils. If not, see . * */ package ch.eitchnet.utils.objectfilter; From 925623bc9394861642b80a5727b224e6e8ca235b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 29 Jul 2012 18:28:22 +0200 Subject: [PATCH 089/457] [Minor] cleaned up copyright notices in all classes --- build.xml | 73 ------------------- pom.xml | 17 ++++- src/main/java/ch/eitchnet/xmlpers/XmlDao.java | 11 +-- .../ch/eitchnet/xmlpers/XmlDaoFactory.java | 11 +-- .../ch/eitchnet/xmlpers/XmlFilePersister.java | 11 +-- .../xmlpers/XmlPersistenceExecption.java | 11 +-- .../xmlpers/XmlPersistenceHandler.java | 11 +-- .../xmlpers/XmlPersistencePathBuilder.java | 11 +-- .../xmlpers/XmlPersistenceTransaction.java | 11 +-- .../xmlpers/test/XmlPersistenceTest.java | 12 +-- .../eitchnet/xmlpers/test/impl/MyClass.java | 11 +-- .../xmlpers/test/impl/MyClassDao.java | 11 +-- .../xmlpers/test/impl/MyDaoFactory.java | 11 +-- 13 files changed, 49 insertions(+), 163 deletions(-) delete mode 100644 build.xml diff --git a/build.xml b/build.xml deleted file mode 100644 index 2fd58d18c..000000000 --- a/build.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cleaning build folder ${buildFolder} - - - - - - - Cleaning dist folder ${distFolder} - - - - - - - diff --git a/pom.xml b/pom.xml index d889c4f99..94901bab3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ch.eitchnet ch.eitchnet.xmlpers jar - 1.0-SNAPSHOT + 0.1.0-SNAPSHOT ch.eitchnet.xmlpers https://github.com/eitch/ch.eitchnet.xmlpers @@ -151,6 +151,21 @@ 1.6 + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + org.apache.maven.plugins diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlDao.java b/src/main/java/ch/eitchnet/xmlpers/XmlDao.java index 998f81f04..234c0a9ea 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlDao.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java index 338aa5e3f..90b469703 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java b/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java index f4f71f44b..f9d7bc4fd 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java index cdf49b88a..ff01fca32 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java index 81796a96e..7382d30c2 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java index 28df70a79..5a5fcf7d8 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java index de6bbc607..e844d3d4e 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java index 9296e4750..3c0220186 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers.test; @@ -39,7 +34,6 @@ import ch.eitchnet.xmlpers.XmlPersistenceExecption; import ch.eitchnet.xmlpers.XmlPersistenceHandler; import ch.eitchnet.xmlpers.XmlPersistenceTransaction; import ch.eitchnet.xmlpers.test.impl.MyClass; -import ch.eitchnet.xmlpers.test.impl.MyClassDao; import ch.eitchnet.xmlpers.test.impl.MyDaoFactory; /** diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java index ec2692970..a0c1d3e2c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java index 771f8e115..4686ba349 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java index c0ed22409..efa26d450 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java @@ -1,25 +1,20 @@ /* * Copyright (c) 2012 * - * Robert von Burg - * - */ - -/* * This file is part of ch.eitchnet.java.xmlpers * - * Privilege is free software: you can redistribute it and/or modify + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * Privilege is distributed in the hope that it will be useful, + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * along with ch.eitchnet.java.xmlpers. If not, see . * */ package ch.eitchnet.xmlpers.test.impl; From 45e5eac46df365f3d4476040c03018f5a8fb56d8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 4 Aug 2012 21:31:56 +0200 Subject: [PATCH 090/457] [Minor] change modifier of DefaultPrivilegeHandler.nextSessionId() set method DefaultPrivilegeHandler.nextSessionId() to protected from private so it can be used in sub classes --- .../ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 748619a16..afedc0189 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1018,7 +1018,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * @return a new session id */ - private synchronized String nextSessionId() { + protected synchronized String nextSessionId() { return Long.toString(++this.lastSessionId % Long.MAX_VALUE); } From 3831d29a12db520315c2d07160669bf746fb344a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 4 Aug 2012 21:36:49 +0200 Subject: [PATCH 091/457] [Minor] updated dependency to ch.eitchnet.utils to 0.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5a7c18a40..75a37097c 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0-SNAPSHOT + 0.1.0-SNAPSHOT From eec0b671352ad39670a0113fefa1374c0a740d75 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 4 Aug 2012 21:37:09 +0200 Subject: [PATCH 092/457] [Minor] updated dependency to ch.eitchnet.utils to 0.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94901bab3..65b28f284 100644 --- a/pom.xml +++ b/pom.xml @@ -125,7 +125,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0-SNAPSHOT + 0.1.0-SNAPSHOT From 8b780368d79440340f44fc0255fa2bf41babba2b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 5 Aug 2012 01:33:54 +0200 Subject: [PATCH 093/457] [Devel] implementing PrivilegeHandler.runAsSystem() Implementing the runAsSystem() so that special actions can be performed, where no user is logged in Refactored the test classes so that model specific classes are in the model sub package. --- config/PrivilegeModel.xml | 27 ++- .../handler/DefaultPrivilegeHandler.java | 166 ++++++++++++++++-- .../privilege/handler/PrivilegeHandler.java | 14 +- .../privilege/handler/SystemUserAction.java | 31 ++++ .../handler/XmlPersistenceHandler.java | 108 ++++++------ .../helper/CertificateThreadLocal.java | 46 +++++ .../eitchnet/privilege/model/UserState.java | 7 +- .../privilege/test/PrivilegeTest.java | 79 ++++++++- .../test/{ => model}/TestRestrictable.java | 4 +- .../test/model/TestSystemRestrictable.java | 46 +++++ .../test/model/TestSystemUserAction.java | 52 ++++++ .../test/model/TestSystemUserActionDeny.java | 50 ++++++ 12 files changed, 544 insertions(+), 86 deletions(-) create mode 100644 src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java create mode 100644 src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java rename src/test/java/ch/eitchnet/privilege/test/{ => model}/TestRestrictable.java (93%) create mode 100644 src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index e43216141..396878bc4 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -26,19 +26,30 @@ along with Privilege. If not, see . - Featherlite + Application Administrator ENABLED en_GB PrivilegeAdmin - FeatherliteUser + AppUser + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + @@ -46,11 +57,17 @@ along with Privilege. If not, see . - - + + true - + + + + + true + + true diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index afedc0189..d4444d9af 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -74,32 +74,37 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * last assigned id for the {@link Session}s */ - protected long lastSessionId; + private long lastSessionId; + + /** + * This map stores certificates for system users as a cache + */ + private Map systemUserCertificateMap; + + /** + * Map keeping a reference to all active sessions with their certificates + */ + private Map sessionMap; /** * Map of {@link PrivilegePolicy} classes */ private Map> policyMap; - /** - * Map keeping a reference to all active sessions with their certificates - */ - protected Map sessionMap; - /** * The persistence handler is used for getting objects and saving changes */ - protected PersistenceHandler persistenceHandler; + private PersistenceHandler persistenceHandler; /** * The encryption handler is used for generating hashes and tokens */ - protected EncryptionHandler encryptionHandler; + private EncryptionHandler encryptionHandler; /** * flag to define if already initialized */ - protected boolean initialized; + private boolean initialized; /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) @@ -138,7 +143,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @throws PrivilegeException * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated */ - protected PrivilegePolicy getPolicy(String policyName) { + private PrivilegePolicy getPolicy(String policyName) { // get the policies class Class policyClazz = this.policyMap.get(policyName); @@ -673,20 +678,20 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { User user = this.persistenceHandler.getUser(username); // no user means no authentication if (user == null) - throw new AccessDeniedException("There is no user defined with the credentials: " + username - + " / ***..."); + throw new AccessDeniedException("There is no user defined with the username " + username); // validate password String pwHash = user.getPassword(); if (pwHash == null) throw new AccessDeniedException("User has no password and may not login!"); if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + throw new AccessDeniedException("Password is incorrect for " + username); // validate if user is allowed to login + // this also capture the trying to login of SYSTEM user if (user.getUserState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " - + user.getUserState()); + throw new AccessDeniedException("User " + username + " does not have state " + UserState.ENABLED + + " and can not login!"); // validate user has at least one role if (user.getRoles().isEmpty()) { @@ -832,7 +837,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @param role * @param restrictable */ - protected boolean actionAllowed(Role role, Restrictable restrictable) { + private boolean actionAllowed(Role role, Restrictable restrictable) { // validate PrivilegeName for this restrictable String privilegeName = restrictable.getPrivilegeName(); @@ -979,7 +984,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @throws PrivilegeException * if the this method is called multiple times or an initialization exception occurs */ - public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, + public synchronized void initialize(Map parameterMap, EncryptionHandler encryptionHandler, PersistenceHandler persistenceHandler, Map> policyMap) { if (this.initialized) @@ -996,6 +1001,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.lastSessionId = 0l; this.sessionMap = Collections.synchronizedMap(new HashMap()); + this.systemUserCertificateMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } @@ -1018,7 +1024,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * @return a new session id */ - protected synchronized String nextSessionId() { + private synchronized String nextSessionId() { return Long.toString(++this.lastSessionId % Long.MAX_VALUE); } @@ -1061,4 +1067,128 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#runAsSystem(java.lang.String, + * ch.eitchnet.privilege.handler.SystemUserAction) + */ + @Override + public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { + + if (systemUsername == null) + throw new PrivilegeException("systemUsername may not be null!"); + if (action == null) + throw new PrivilegeException("action may not be null!"); + + // get the system user + User systemUser = this.persistenceHandler.getUser(systemUsername); + if (systemUser == null) + throw new PrivilegeException("System user " + systemUsername + " does not exist!"); + + // validate this is a system user + if (systemUser.getUserState() != UserState.SYSTEM) + throw new PrivilegeException("User " + systemUsername + " is not a System user!"); + + // validate this system user may perform the given action + String actionClassname = action.getClass().getName(); + checkPrivilege(actionClassname, systemUser); + + // get certificate for this system user + Certificate systemUserCertificate = getSystemUserCertificate(systemUsername); + + // perform the action + action.execute(systemUserCertificate); + } + + /** + * Checks if the given user has the given privilege + * + * @param privilegeName + * the name of the privilege to check on the user + * @param user + * the user to check for the given privilege + * + * @throws PrivilegeException + * if the user does not have the privilege + */ + private void checkPrivilege(String privilegeName, User user) throws PrivilegeException { + + // check each role if it has the privilege + for (String roleName : user.getRoles()) { + + Role role = this.persistenceHandler.getRole(roleName); + + // on the first occurrence of our privilege, stop + if (role.hasPrivilege(privilegeName)) + return; + } + + // default throw exception, as the user does not have the privilege + throw new PrivilegeException("User " + user.getUsername() + " does not have Privilege " + privilegeName); + } + + /** + * Returns the {@link Certificate} for the given system username. If it does not yet exist, then it is created by + * authenticating the system user + * + * @param systemUsername + * the name of the system user + * + * @return the {@link Certificate} for this system user + */ + private Certificate getSystemUserCertificate(String systemUsername) { + + // see if a certificate has already been created for this system user + Certificate systemUserCertificate = systemUserCertificateMap.get(systemUsername); + if (systemUserCertificate != null) + return systemUserCertificate; + + // otherwise log this system user in, by performing a slightly different authentication + + // we only work with hashed passwords + String passwordHash = this.encryptionHandler.convertToHash(systemUsername.getBytes()); + + // get user object + User user = this.persistenceHandler.getUser(systemUsername); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("The system user with username " + systemUsername + " does not exist!"); + + // validate password + String pwHash = user.getPassword(); + if (pwHash == null) + throw new AccessDeniedException("System user " + systemUsername + " has no password and may not login!"); + if (!pwHash.equals(passwordHash)) + throw new AccessDeniedException("System user " + systemUsername + " has an incorrect password defined!"); + + // validate user state is system + if (user.getUserState() != UserState.SYSTEM) + throw new PrivilegeException("The system " + systemUsername + " user does not have expected user state " + + UserState.SYSTEM); + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + throw new PrivilegeException("The system user " + systemUsername + " does not have any roles defined!"); + } + + // get 2 auth tokens + String authToken = this.encryptionHandler.nextToken(); + String authPassword = this.encryptionHandler.nextToken(); + + // get next session id + String sessionId = nextSessionId(); + + // create a new certificate, with details of the user + systemUserCertificate = new Certificate(sessionId, systemUsername, authToken, authPassword, user.getLocale(), + new HashMap(user.getProperties())); + + // create and save a new session + Session session = new Session(sessionId, systemUsername, authToken, authPassword, System.currentTimeMillis()); + this.sessionMap.put(sessionId, new CertificateSessionPair(session, systemUserCertificate)); + + // log + logger.info("The system user " + systemUsername + " is logged in with session " + session); + + return systemUserCertificate; + } } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index c6577ea64..4f2c54b2a 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -46,7 +46,7 @@ import ch.eitchnet.privilege.model.internal.User; public interface PrivilegeHandler { /** - * value = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects + * PRIVILEGE_ADMIN_ROLE = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects */ public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; @@ -437,4 +437,16 @@ public interface PrivilegeHandler { * if the users of the given certificate does not have the privilege to perform this action */ public boolean persist(Certificate certificate) throws AccessDeniedException; + + /** + * Special method to perform work as a System user, meaning the given systemUsername corresponds to an account which + * has the state {@link UserState#SYSTEM} and this user must have privilege to perform the concrete implementation + * of the given {@link SystemUserAction} instance + * + * @param systemUsername + * the username of the system user to perform the action as + * @param action + * the action to be performed as the system user + */ + public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException; } diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java new file mode 100644 index 000000000..0aac16d1d --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.privilege.handler; + +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + * + */ +public interface SystemUserAction { + + public void execute(Certificate certificate); +} diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 9e2608437..e1323f7ff 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -290,7 +290,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { * * @return the map of converted {@link User} objects */ - protected static Map readUsers(Element usersRootElement) { + protected Map readUsers(Element usersRootElement) { Map userMap = new HashMap(); @@ -321,6 +321,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { String roleName = roleElement.getTextTrim(); if (roleName.isEmpty()) { logger.error("User " + username + " has a role defined with no name, Skipped."); + } else if (!this.roleMap.containsKey(roleName)) { + logger.error("User " + username + " has a inexistant role " + roleName + ", Skipped."); } else { roles.add(roleName); } @@ -332,10 +334,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { // create user User user = new User(userId, username, password, firstname, surname, userState, roles, locale, propertyMap); - logger.info("Added user " + user); // put user in map userMap.put(username, user); + logger.info("Loaded user " + user); } return userMap; @@ -349,7 +351,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { * * @return the map of converted {@link Role} objects */ - protected static Map readRoles(Element rolesRootElement) { + protected Map readRoles(Element rolesRootElement) { Map roleMap = new HashMap(); @@ -368,6 +370,56 @@ public class XmlPersistenceHandler implements PersistenceHandler { return roleMap; } + /** + * Parses {@link Privilege} objects from their XML representation to their objects + * + * @param roleParentElement + * the parent on which the Privilege XML elements are + * + * @return the map of {@link Privilege} objects + */ + protected Map readPrivileges(Element roleParentElement) { + + Map privilegeMap = new HashMap(); + + @SuppressWarnings("unchecked") + List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); + + Element allAllowedE = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED); + boolean allAllowed = false; + if (allAllowedE != null) { + allAllowed = Boolean.valueOf(allAllowedE.getTextTrim()).booleanValue(); + } + + @SuppressWarnings("unchecked") + List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); + Set denyList = new HashSet(denyElements.size()); + for (Element denyElement : denyElements) { + String denyValue = denyElement.getTextTrim(); + if (!denyValue.isEmpty()) + denyList.add(denyValue); + } + + @SuppressWarnings("unchecked") + List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); + Set allowList = new HashSet(allowElements.size()); + for (Element allowElement : allowElements) { + String allowValue = allowElement.getTextTrim(); + if (!allowValue.isEmpty()) + allowList.add(allowValue); + } + + Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); + privilegeMap.put(privilegeName, privilege); + } + + return privilegeMap; + } + /** * Converts {@link User} objects to their XML representations * @@ -466,56 +518,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { return rolesAsElements; } - /** - * Parses {@link Privilege} objects from their XML representation to their objects - * - * @param roleParentElement - * the parent on which the Privilege XML elements are - * - * @return the map of {@link Privilege} objects - */ - protected static Map readPrivileges(Element roleParentElement) { - - Map privilegeMap = new HashMap(); - - @SuppressWarnings("unchecked") - List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); - for (Element privilegeElement : privilegeElements) { - - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - - Element allAllowedE = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED); - boolean allAllowed = false; - if (allAllowedE != null) { - allAllowed = Boolean.valueOf(allAllowedE.getTextTrim()).booleanValue(); - } - - @SuppressWarnings("unchecked") - List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - Set denyList = new HashSet(denyElements.size()); - for (Element denyElement : denyElements) { - String denyValue = denyElement.getTextTrim(); - if (!denyValue.isEmpty()) - denyList.add(denyValue); - } - - @SuppressWarnings("unchecked") - List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - Set allowList = new HashSet(allowElements.size()); - for (Element allowElement : allowElements) { - String allowValue = allowElement.getTextTrim(); - if (!allowValue.isEmpty()) - allowList.add(allowValue); - } - - Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); - } - - return privilegeMap; - } - /** * Converts {@link Privilege} objects to their XML representation * diff --git a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java new file mode 100644 index 000000000..9830541e3 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.privilege.helper; + +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + * + */ +public class CertificateThreadLocal extends ThreadLocal { + + private static final CertificateThreadLocal instance; + static { + instance = new CertificateThreadLocal(); + } + + public static CertificateThreadLocal getInstance() { + return instance; + } + + public static Certificate getCert() { + return instance.get(); + } + + public static void setCert(Certificate certificate) { + instance.set(certificate); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index 4b451b66f..c34782e1b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -51,5 +51,10 @@ public enum UserState { /** * the user has automatically expired through a predefined time */ - EXPIRED; + EXPIRED, + + /** + * This is the System user state which is special and thus exempted from normal uses + */ + SYSTEM; } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 2bd5c0a77..cd1ada34d 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -33,6 +33,7 @@ import org.junit.BeforeClass; import org.junit.Test; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.CertificateThreadLocal; import ch.eitchnet.privilege.helper.InitializationHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -42,6 +43,9 @@ import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.test.model.TestRestrictable; +import ch.eitchnet.privilege.test.model.TestSystemUserAction; +import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more @@ -55,8 +59,9 @@ public class PrivilegeTest { private static final byte[] PASS_ADMIN = "admin".getBytes(); private static final String BOB = "bob"; private static final String TED = "ted"; + private static final String SYSTEM_USER_ADMIN = "system_admin"; private static final byte[] PASS_BOB = "admin1".getBytes(); - private static final String ROLE_FEATHERLITE_USER = "FeatherliteUser"; + private static final String ROLE_APP_USER = "AppUser"; private static final String ROLE_USER = "user"; private static final byte[] PASS_DEF = "def".getBytes(); private static final byte[] PASS_BAD = "123".getBytes(); @@ -335,7 +340,7 @@ public class PrivilegeTest { } /** - * Tests if the user bob, who does not have FeatherliteUser role can perform restrictable + * Tests if the user bob, who does not have AppUser role can perform restrictable * * @throws Exception * if something goes wrong @@ -359,16 +364,16 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testAddFeatherliteRoleToBob() throws Exception { + public void testAddAppRoleToBob() throws Exception { Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.addRoleToUser(certificate, BOB, ROLE_FEATHERLITE_USER); - logger.info("Added " + ROLE_FEATHERLITE_USER + " to " + BOB); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); + logger.info("Added " + ROLE_APP_USER + " to " + BOB); privilegeHandler.invalidateSession(certificate); } /** - * Tests if the user bob, who now has FeatherliteUser role can perform restrictable + * Tests if the user bob, who now has AppUser role can perform restrictable * * @throws Exception * if something goes wrong @@ -386,4 +391,66 @@ public class PrivilegeTest { privilegeHandler.invalidateSession(certificate); } } + + /** + * Tests if an action can be performed as a system user + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testPerformSystemRestrictable() throws Exception { + + // create the action to be performed as a system user + TestSystemUserAction action = new TestSystemUserAction(privilegeHandler); + + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + } + + /** + * Checks that the system user can not perform a valid action, but illegal privilege + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = PrivilegeException.class) + public void testPerformSystemRestrictableFailPrivilege() throws Exception { + + // create the action to be performed as a system user + TestSystemUserActionDeny action = new TestSystemUserActionDeny(privilegeHandler); + + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + } + + /** + * System user may not login + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = AccessDeniedException.class) + public void testLoginSystemUser() throws Exception { + + privilegeHandler.authenticate(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); + } + + @Test + public void testCertificateThreadLocal() { + + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // set certificate into thread local + CertificateThreadLocal.getInstance().set(certificate); + + // see if bob can perform restrictable by returning certificate from CertificateThreadLocal + Restrictable restrictable = new TestRestrictable(); + try { + privilegeHandler.actionAllowed(CertificateThreadLocal.getInstance().get(), restrictable); + } finally { + privilegeHandler.invalidateSession(certificate); + } + } } diff --git a/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java similarity index 93% rename from src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java rename to src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java index 117103ee6..7b677373c 100644 --- a/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java @@ -17,7 +17,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.privilege.test; +package ch.eitchnet.privilege.test.model; import ch.eitchnet.privilege.model.Restrictable; @@ -32,7 +32,7 @@ public class TestRestrictable implements Restrictable { */ @Override public String getPrivilegeName() { - return "com.rsp.core.base.service.Service"; + return TestRestrictable.class.getName(); } /** diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java new file mode 100644 index 000000000..d0e6145ba --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010 - 2012 + * + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.model.Restrictable; + +/** + * @author Robert von Burg + * + */ +public class TestSystemRestrictable implements Restrictable { + + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + */ + @Override + public String getPrivilegeName() { + return TestSystemRestrictable.class.getName(); + } + + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + */ + @Override + public Object getPrivilegeValue() { + return TestSystemRestrictable.class.getName(); + } + +} diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java new file mode 100644 index 000000000..2b7cfc786 --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.handler.SystemUserAction; +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + * + */ +public class TestSystemUserAction implements SystemUserAction { + + private PrivilegeHandler handler; + + /** + * + */ + public TestSystemUserAction(PrivilegeHandler handler) { + this.handler = handler; + } + + /** + * @see ch.eitchnet.privilege.handler.SystemUserAction#execute(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public void execute(Certificate certificate) { + + TestSystemRestrictable restrictable = new TestSystemRestrictable(); + + handler.actionAllowed(certificate, restrictable); + } + +} diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java new file mode 100644 index 000000000..1220d008b --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.handler.SystemUserAction; +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + * + */ +public class TestSystemUserActionDeny implements SystemUserAction { + + private PrivilegeHandler handler; + + /** + * + */ + public TestSystemUserActionDeny(PrivilegeHandler handler) { + this.handler = handler; + } + + /** + * @see ch.eitchnet.privilege.handler.SystemUserAction#execute(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public void execute(Certificate certificate) { + + TestRestrictable restrictable = new TestRestrictable(); + handler.actionAllowed(certificate, restrictable); + } +} From 0d392b1c602acc10b97d2f863b8d1f7e0f415c31 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Nov 2012 09:58:52 +0100 Subject: [PATCH 094/457] [New] added new formatting functions in StringHelper - formatNanoDuration(long:nanos):String - formatMillisecondsDuration(millis:long):String --- pom.xml | 1 + .../eitchnet/utils/helper/StringHelper.java | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/pom.xml b/pom.xml index ffcf01466..20471eb4a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,7 @@ 4.0.0 + ch.eitchnet ch.eitchnet.utils jar diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index c69de70d0..5911a4693 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -413,4 +413,40 @@ public class StringHelper { return sb.toString(); } + + /** + * Formats the given number of milliseconds to a time like 0.000s/ms + * + * @param millis + * the number of milliseconds + * + * @return format the given number of milliseconds to a time like 0.000s/ms + */ + public static String formatMillisecondsDuration(final long millis) { + if (millis > 1000) { + return String.format("%.3fs", (((double) millis) / 1000)); //$NON-NLS-1$ + } + + return millis + "ms"; //$NON-NLS-1$ + } + + /** + * Formats the given number of nanoseconds to a time like 0.000s/ms/us/ns + * + * @param nanos + * the number of nanoseconds + * + * @return format the given number of nanoseconds to a time like 0.000s/ms/us/ns + */ + public static String formatNanoDuration(final long nanos) { + if (nanos > 1000000000) { + return String.format("%.3fs", (((double) nanos) / 1000000000)); //$NON-NLS-1$ + } else if (nanos > 1000000) { + return String.format("%.3fms", (((double) nanos) / 1000000)); //$NON-NLS-1$ + } else if (nanos > 1000) { + return String.format("%.3fus", (((double) nanos) / 1000)); //$NON-NLS-1$ + } else { + return nanos + "ns"; //$NON-NLS-1$ + } + } } From bb7042524f08541309562a37078902cdde53486f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Nov 2012 19:42:58 +0100 Subject: [PATCH 095/457] [New] added new utility method StringHelper.isEmpty() which also checks for null --- .../java/ch/eitchnet/utils/helper/StringHelper.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 5911a4693..c44a2ac5e 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -449,4 +449,16 @@ public class StringHelper { return nanos + "ns"; //$NON-NLS-1$ } } + + /** + * 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(); + } } From 2fc807fe11a9a29c9d1c4954fefb20341086f649 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 19 Nov 2012 22:44:07 +0100 Subject: [PATCH 096/457] [Minor] cleaned up warnings in code and cleaned up pom.xml --- pom.xml | 315 ++++++++---------- .../java/ch/eitchnet/rmi/RmiFileDeletion.java | 4 +- .../java/ch/eitchnet/rmi/RmiFileHandler.java | 14 +- .../java/ch/eitchnet/rmi/RmiFilePart.java | 16 +- src/main/java/ch/eitchnet/rmi/RmiHelper.java | 51 +-- .../ch/eitchnet/utils/helper/FileHelper.java | 40 +-- .../utils/helper/Log4jConfigurator.java | 60 ++-- .../utils/helper/Log4jPropertyWatchDog.java | 32 +- .../eitchnet/utils/helper/ProcessHelper.java | 28 +- .../eitchnet/utils/helper/StringHelper.java | 61 ++-- .../eitchnet/utils/helper/SystemHelper.java | 32 +- .../utils/objectfilter/ObjectCache.java | 20 +- .../utils/objectfilter/ObjectFilter.java | 70 ++-- 13 files changed, 353 insertions(+), 390 deletions(-) diff --git a/pom.xml b/pom.xml index 20471eb4a..22a55833e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,192 +1,151 @@ - 4.0.0 - - ch.eitchnet - ch.eitchnet.utils - jar - 0.1.0-SNAPSHOT - ch.eitchnet.utils - https://github.com/eitch/ch.eitchnet.utils + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 - - UTF-8 - + ch.eitchnet + ch.eitchnet.utils + jar + 0.1.0-SNAPSHOT + ch.eitchnet.utils + https://github.com/eitch/ch.eitchnet.utils - + + UTF-8 + - 2011 - - - GNU Lesser General Public License - http://www.gnu.org/licenses/lgpl.html - repo - - - - eitchnet.ch - http://blog.eitchnet.ch - - - - eitch - Robert von Vurg - eitch@eitchnet.ch - http://blog.eitchnet.ch - eitchnet.ch - http://blog.eitchnet.ch - - architect - developer - - +1 - - http://localhost - - - + - - Github Issues - https://github.com/eitch/ch.eitchnet.utils/issues - + 2011 + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl.html + repo + + + + eitchnet.ch + http://blog.eitchnet.ch + + + + eitch + Robert von Vurg + eitch@eitchnet.ch + http://blog.eitchnet.ch + eitchnet.ch + http://blog.eitchnet.ch + + architect + developer + + +1 + + http://localhost + + + - + - - scm:git:https://github.com/eitch/ch.eitchnet.utils.git - scm:git:git@github.com:eitch/ch.eitchnet.utils.git - https://github.com/eitch/ch.eitchnet.utils - + + scm:git:https://github.com/eitch/ch.eitchnet.utils.git + scm:git:git@github.com:eitch/ch.eitchnet.utils.git + https://github.com/eitch/ch.eitchnet.utils + - + - - - junit - junit - 4.10 - test - - - log4j - log4j - 1.2.17 - - + + + junit + junit + 4.10 + test + + + log4j + log4j + 1.2.17 + + - - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - true - true - - + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.0 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar-no-fork - - - - + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + + + - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - true - true - - - - - - - - org.apache.maven.plugins - maven-site-plugin - 2.3 - - UTF-8 - - - - - + + org.apache.maven.plugins + maven-site-plugin + 2.3 + + UTF-8 + + + + diff --git a/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java b/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java index a8fc6d385..3071092f0 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java +++ b/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java @@ -47,13 +47,13 @@ public class RmiFileDeletion implements Serializable { * @return the fileType */ public String getFileType() { - return fileType; + return this.fileType; } /** * @return the fileName */ public String getFileName() { - return fileName; + return this.fileName; } } diff --git a/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java b/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java index f21355aae..7d3ff04b4 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java +++ b/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java @@ -80,7 +80,7 @@ public class RmiFileHandler { validateFileType(fileType); // evaluate the path where the file should reside - File file = new File(basePath + "/" + fileType, filePart.getFileName()); + File file = new File(this.basePath + "/" + fileType, filePart.getFileName()); // now evaluate the file exists if (!file.canRead()) { @@ -102,9 +102,9 @@ public class RmiFileHandler { // variables defining the part of the file we're going to return long requestOffset = filePart.getPartOffset(); int requestSize = filePart.getPartLength(); - if (requestSize > MAX_PART_SIZE) { + if (requestSize > RmiFileHandler.MAX_PART_SIZE) { throw new RuntimeException("The requested part size " + requestSize + " is greater than the allowed " - + MAX_PART_SIZE); + + RmiFileHandler.MAX_PART_SIZE); } // validate lengths and offsets @@ -125,7 +125,7 @@ public class RmiFileHandler { long l = Math.min(requestSize, remaining); // this is a fail safe - if (l > MAX_PART_SIZE) + if (l > RmiFileHandler.MAX_PART_SIZE) throw new RuntimeException("Something went wrong. Min of requestSize and remaining is > MAX_PART_SIZE!"); // this is the size of the array we want to return @@ -163,7 +163,7 @@ public class RmiFileHandler { try { fin.close(); } catch (IOException e) { - logger.error("Error while closing FileInputStream: " + e.getLocalizedMessage()); + RmiFileHandler.logger.error("Error while closing FileInputStream: " + e.getLocalizedMessage()); } } } @@ -190,7 +190,7 @@ public class RmiFileHandler { validateFileType(fileType); // evaluate the path where the file should reside - File dstFile = new File(basePath + "/" + fileType, filePart.getFileName()); + File dstFile = new File(this.basePath + "/" + fileType, filePart.getFileName()); // if the file already exists, then this may not be a start part if (filePart.getPartOffset() == 0 && dstFile.exists()) { @@ -231,7 +231,7 @@ public class RmiFileHandler { validateFileType(fileType); // evaluate the path where the file should reside - File fileToDelete = new File(basePath + "/" + fileType, fileDeletion.getFileName()); + File fileToDelete = new File(this.basePath + "/" + fileType, fileDeletion.getFileName()); // delete the file return FileHelper.deleteFiles(new File[] { fileToDelete }, true); diff --git a/src/main/java/ch/eitchnet/rmi/RmiFilePart.java b/src/main/java/ch/eitchnet/rmi/RmiFilePart.java index 750b993a6..627fcc373 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFilePart.java +++ b/src/main/java/ch/eitchnet/rmi/RmiFilePart.java @@ -64,7 +64,7 @@ public class RmiFilePart implements Serializable { * @return the fileLength */ public long getFileLength() { - return fileLength; + return this.fileLength; } /** @@ -79,7 +79,7 @@ public class RmiFilePart implements Serializable { * @return the fileHash */ public String getFileHash() { - return fileHash; + return this.fileHash; } /** @@ -94,14 +94,14 @@ public class RmiFilePart implements Serializable { * @return the fileType */ public String getFileType() { - return fileType; + return this.fileType; } /** * @return the partOffset */ public long getPartOffset() { - return partOffset; + return this.partOffset; } /** @@ -116,7 +116,7 @@ public class RmiFilePart implements Serializable { * @return the partLength */ public int getPartLength() { - return partLength; + return this.partLength; } /** @@ -131,7 +131,7 @@ public class RmiFilePart implements Serializable { * @return the partBytes */ public byte[] getPartBytes() { - return partBytes; + return this.partBytes; } /** @@ -146,7 +146,7 @@ public class RmiFilePart implements Serializable { * @return the lastPart */ public boolean isLastPart() { - return lastPart; + return this.lastPart; } /** @@ -161,6 +161,6 @@ public class RmiFilePart implements Serializable { * @return the fileName */ public String getFileName() { - return fileName; + return this.fileName; } } diff --git a/src/main/java/ch/eitchnet/rmi/RmiHelper.java b/src/main/java/ch/eitchnet/rmi/RmiHelper.java index a8530ad0c..6cf6dae2a 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiHelper.java +++ b/src/main/java/ch/eitchnet/rmi/RmiHelper.java @@ -40,10 +40,10 @@ public class RmiHelper { /** * @param rmiFileClient - * @param filePart + * @param origFilePart * @param dstFile */ - public static void downloadFile(RMIFileClient rmiFileClient, RmiFilePart filePart, File dstFile) { + public static void downloadFile(RMIFileClient rmiFileClient, RmiFilePart origFilePart, File dstFile) { // here we don't overwrite, the caller must make sure the destination file does not exist if (dstFile.exists()) @@ -51,56 +51,59 @@ public class RmiHelper { + " already exists. Delete it first, if you want to overwrite it!"); try { + + RmiFilePart tmpPart = origFilePart; + int loops = 0; - int startLength = filePart.getPartLength(); + int startLength = tmpPart.getPartLength(); while (true) { loops += 1; // get the next part - filePart = rmiFileClient.requestFile(filePart); + tmpPart = rmiFileClient.requestFile(tmpPart); // validate length of data - if (filePart.getPartLength() != filePart.getPartBytes().length) - throw new RuntimeException("Invalid FilePart. Part length is not as long as the bytes passed " - + filePart.getPartLength() + " / " + filePart.getPartBytes().length); + if (tmpPart.getPartLength() != tmpPart.getPartBytes().length) + throw new RuntimeException("Invalid tmpPart. Part length is not as long as the bytes passed " + + tmpPart.getPartLength() + " / " + tmpPart.getPartBytes().length); // validate offset is size of file - if (filePart.getPartOffset() != dstFile.length()) { + if (tmpPart.getPartOffset() != dstFile.length()) { throw new RuntimeException("The part offset $offset is not at the end of the file " - + filePart.getPartOffset() + " / " + dstFile.length()); + + tmpPart.getPartOffset() + " / " + dstFile.length()); } // append the part - FileHelper.appendFilePart(dstFile, filePart.getPartBytes()); + FileHelper.appendFilePart(dstFile, tmpPart.getPartBytes()); // update the offset - filePart.setPartOffset(filePart.getPartOffset() + filePart.getPartBytes().length); + tmpPart.setPartOffset(tmpPart.getPartOffset() + tmpPart.getPartBytes().length); // break if the offset is past the length of the file - if (filePart.getPartOffset() >= filePart.getFileLength()) + if (tmpPart.getPartOffset() >= tmpPart.getFileLength()) break; } - logger.info(filePart.getFileType() + ": " + filePart.getFileName() + ": Requested " + loops - + " parts. StartSize: " + startLength + " EndSize: " + filePart.getPartLength()); + RmiHelper.logger.info(tmpPart.getFileType() + ": " + tmpPart.getFileName() + ": Requested " + loops + + " parts. StartSize: " + startLength + " EndSize: " + tmpPart.getPartLength()); // validate that the offset is at the end of the file - if (filePart.getPartOffset() != filePart.getFileLength()) { - throw new RuntimeException("Offset " + filePart.getPartOffset() + " is not at file length " - + filePart.getFileLength() + " after reading all the file parts!"); + if (tmpPart.getPartOffset() != origFilePart.getFileLength()) { + throw new RuntimeException("Offset " + tmpPart.getPartOffset() + " is not at file length " + + origFilePart.getFileLength() + " after reading all the file parts!"); } // now validate hashes String dstFileHash = StringHelper.getHexString(FileHelper.hashFileSha256(dstFile)); - if (!dstFileHash.equals(filePart.getFileHash())) { - throw new RuntimeException("Downloading the file " + filePart.getFileName() - + " failed because the hashes don't match. Expected: " + filePart.getFileHash() + " / Actual: " - + dstFileHash); + if (!dstFileHash.equals(origFilePart.getFileHash())) { + throw new RuntimeException("Downloading the file " + origFilePart.getFileName() + + " failed because the hashes don't match. Expected: " + origFilePart.getFileHash() + + " / Actual: " + dstFileHash); } } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException) e; - throw new RuntimeException("Downloading the file " + filePart.getFileName() + throw new RuntimeException("Downloading the file " + origFilePart.getFileName() + " failed because of an underlying exception " + e.getLocalizedMessage()); } } @@ -194,7 +197,7 @@ public class RmiHelper { offset = nextOffset; } - logger.info(filePart.getFileType() + ": " + filePart.getFileName() + ": Sent " + loops + RmiHelper.logger.info(filePart.getFileType() + ": " + filePart.getFileName() + ": Sent " + loops + " parts. StartSize: " + startLength + " EndSize: " + filePart.getPartLength()); } catch (Exception e) { @@ -207,7 +210,7 @@ public class RmiHelper { try { inputStream.close(); } catch (IOException e) { - logger.error("Exception while closing FileInputStream " + e.getLocalizedMessage()); + RmiHelper.logger.error("Exception while closing FileInputStream " + e.getLocalizedMessage()); } } } diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 9cba18dc3..aac499b67 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -79,7 +79,7 @@ public class FileHelper { try { bufferedReader.close(); } catch (IOException e) { - logger.error("Failed to close BufferedReader: " + e.getLocalizedMessage()); + FileHelper.logger.error("Failed to close BufferedReader: " + e.getLocalizedMessage()); } } } @@ -115,7 +115,7 @@ public class FileHelper { * @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 deleteFiles(new File[] { file }, log); + return FileHelper.deleteFiles(new File[] { file }, log); } /** @@ -132,28 +132,28 @@ public class FileHelper { for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.isDirectory()) { - boolean done = deleteFiles(file.listFiles(), log); + boolean done = FileHelper.deleteFiles(file.listFiles(), log); if (!done) { worked = false; - logger.warn("Could not empty the directory: " + file.getAbsolutePath()); + FileHelper.logger.warn("Could not empty the directory: " + file.getAbsolutePath()); } else { done = file.delete(); if (done) { if (log) - logger.info("Deleted DIR " + file.getAbsolutePath()); + FileHelper.logger.info("Deleted DIR " + file.getAbsolutePath()); } else { worked = false; - logger.warn("Could not delete the directory: " + file.getAbsolutePath()); + FileHelper.logger.warn("Could not delete the directory: " + file.getAbsolutePath()); } } } else { boolean done = file.delete(); if (done) { if (log) - logger.info("Deleted FILE " + file.getAbsolutePath()); + FileHelper.logger.info("Deleted FILE " + file.getAbsolutePath()); } else { worked = false; - logger.warn(("Could not delete the file: " + file.getAbsolutePath())); + FileHelper.logger.warn(("Could not delete the file: " + file.getAbsolutePath())); } } } @@ -195,7 +195,7 @@ public class FileHelper { String fromFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(fromFile)); String toFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(toFile)); if (!fromFileMD5.equals(toFileMD5)) { - logger.error("Copying failed, as MD5 sums are not equal: " + fromFileMD5 + " / " + toFileMD5); + FileHelper.logger.error("Copying failed, as MD5 sums are not equal: " + fromFileMD5 + " / " + toFileMD5); toFile.delete(); return false; @@ -204,7 +204,7 @@ public class FileHelper { // cleanup if files are not the same length if (fromFile.length() != toFile.length()) { - logger.error("Copying failed, as new files are not the same length: " + fromFile.length() + " / " + FileHelper.logger.error("Copying failed, as new files are not the same length: " + fromFile.length() + " / " + toFile.length()); toFile.delete(); @@ -213,7 +213,7 @@ public class FileHelper { } catch (Exception e) { - logger.error(e, e); + FileHelper.logger.error(e, e); return false; } finally { @@ -222,7 +222,7 @@ public class FileHelper { try { inBuffer.close(); } catch (IOException e) { - logger.error("Error closing BufferedInputStream" + e); + FileHelper.logger.error("Error closing BufferedInputStream" + e); } } @@ -230,7 +230,7 @@ public class FileHelper { try { outBuffer.close(); } catch (IOException e) { - logger.error("Error closing BufferedOutputStream" + e); + FileHelper.logger.error("Error closing BufferedOutputStream" + e); } } } @@ -254,11 +254,11 @@ public class FileHelper { return true; } - logger.warn("Simple File.renameTo failed, trying copy/delete..."); + FileHelper.logger.warn("Simple File.renameTo failed, trying copy/delete..."); // delete if copy was successful, otherwise move will fail if (FileHelper.copy(fromFile, toFile, true)) { - logger.info("Deleting fromFile: " + fromFile.getAbsolutePath()); + FileHelper.logger.info("Deleting fromFile: " + fromFile.getAbsolutePath()); return fromFile.delete(); } @@ -372,7 +372,7 @@ public class FileHelper { * @return the humanized form of the files size */ public final static String humanizeFileSize(File file) { - return humanizeFileSize(file.length()); + return FileHelper.humanizeFileSize(file.length()); } /** @@ -407,7 +407,7 @@ public class FileHelper { * @return the hash as a byte array */ public static byte[] hashFileMd5(File file) { - return hashFile(file, "MD5"); + return FileHelper.hashFile(file, "MD5"); } /** @@ -420,7 +420,7 @@ public class FileHelper { * @return the hash as a byte array */ public static byte[] hashFileSha1(File file) { - return hashFile(file, "SHA-1"); + return FileHelper.hashFile(file, "SHA-1"); } /** @@ -433,7 +433,7 @@ public class FileHelper { * @return the hash as a byte array */ public static byte[] hashFileSha256(File file) { - return hashFile(file, "SHA-256"); + return FileHelper.hashFile(file, "SHA-256"); } /** @@ -492,7 +492,7 @@ public class FileHelper { try { outputStream.close(); } catch (IOException e) { - logger.error("Exception while closing FileOutputStream " + e.getLocalizedMessage()); + FileHelper.logger.error("Exception while closing FileOutputStream " + e.getLocalizedMessage()); } } } diff --git a/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java b/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java index 327ab5446..678ff78f3 100644 --- a/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java +++ b/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java @@ -63,9 +63,9 @@ public class Log4jConfigurator { * Configures log4j with the default {@link ConsoleAppender} */ public static synchronized void configure() { - cleanupOldWatchdog(); + Log4jConfigurator.cleanupOldWatchdog(); BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(getDefaulLayout())); + BasicConfigurator.configure(new ConsoleAppender(Log4jConfigurator.getDefaulLayout())); Logger.getRootLogger().setLevel(Level.INFO); } @@ -119,13 +119,13 @@ public class Log4jConfigurator { + log4JPath.getAbsolutePath()); // now perform the loading - loadLog4jConfiguration(log4JPath); + Log4jConfigurator.loadLog4jConfiguration(log4JPath); } catch (Exception e) { Log4jConfigurator.configure(); - logger.error(e, e); - logger.error("Log4j COULD NOT BE INITIALIZED. Please check the log4j configuration file exists at " + Log4jConfigurator.logger.error(e, e); + Log4jConfigurator.logger.error("Log4j COULD NOT BE INITIALIZED. Please check the log4j configuration file exists at " + log4JPath.getAbsolutePath()); } @@ -152,7 +152,7 @@ public class Log4jConfigurator { throw new RuntimeException("log4jConfigPath may not be null!"); // first clean up any old watch dog in case of a programmatic re-load of the configuration - cleanupOldWatchdog(); + Log4jConfigurator.cleanupOldWatchdog(); // get the root directory String userDir = System.getProperty("user.dir"); @@ -184,16 +184,16 @@ public class Log4jConfigurator { // XXX if the server is in a web context, then we may not use the // FileWatchDog BasicConfigurator.resetConfiguration(); - watchDog = new Log4jPropertyWatchDog(pathNameToLog4jTemp); - watchDog.start(); + Log4jConfigurator.watchDog = new Log4jPropertyWatchDog(pathNameToLog4jTemp); + Log4jConfigurator.watchDog.start(); - logger.info("Log4j is configured to use and watch file " + pathNameToLog4jTemp); + Log4jConfigurator.logger.info("Log4j is configured to use and watch file " + pathNameToLog4jTemp); } catch (Exception e) { Log4jConfigurator.configure(); - logger.error(e, e); - logger.error("Log4j COULD NOT BE INITIALIZED. Please check the log4j configuration file at " + Log4jConfigurator.logger.error(e, e); + Log4jConfigurator.logger.error("Log4j COULD NOT BE INITIALIZED. Please check the log4j configuration file at " + log4jConfigPath); } finally { @@ -201,14 +201,14 @@ public class Log4jConfigurator { try { fin.close(); } catch (IOException e) { - logger.error("Exception closing input file: " + e, e); + Log4jConfigurator.logger.error("Exception closing input file: " + e, e); } } if (fout != null) { try { fout.close(); } catch (IOException e) { - logger.error("Exception closing output file: " + e, e); + Log4jConfigurator.logger.error("Exception closing output file: " + e, e); } } } @@ -229,9 +229,9 @@ public class Log4jConfigurator { if (clazz == null) throw new RuntimeException("clazz may not be null!"); - InputStream resourceAsStream = clazz.getResourceAsStream("/" + FILE_LOG4J); + InputStream resourceAsStream = clazz.getResourceAsStream("/" + Log4jConfigurator.FILE_LOG4J); if (resourceAsStream == null) { - throw new RuntimeException("The resource '" + FILE_LOG4J + "' could not be found for class " + throw new RuntimeException("The resource '" + Log4jConfigurator.FILE_LOG4J + "' could not be found for class " + clazz.getName()); } @@ -240,13 +240,13 @@ public class Log4jConfigurator { log4jProperties.load(resourceAsStream); // and then - loadLog4jConfiguration(log4jProperties); + Log4jConfigurator.loadLog4jConfiguration(log4jProperties); } catch (Exception e) { Log4jConfigurator.configure(); - logger.error(e, e); - logger.error("Log4j COULD NOT BE INITIALIZED. Please check that the log4j configuration file '" - + FILE_LOG4J + "' exists as a resource for class " + clazz.getName() + Log4jConfigurator.logger.error(e, e); + Log4jConfigurator.logger.error("Log4j COULD NOT BE INITIALIZED. Please check that the log4j configuration file '" + + Log4jConfigurator.FILE_LOG4J + "' exists as a resource for class " + clazz.getName() + " and is a valid properties configuration"); } } @@ -272,19 +272,19 @@ public class Log4jConfigurator { throw new RuntimeException("log4jProperties may not be null!"); // first clean up any old watch dog in case of a programmatic re-load of the configuration - cleanupOldWatchdog(); + Log4jConfigurator.cleanupOldWatchdog(); // replace any variables StringHelper.replaceProperties(log4jProperties, System.getProperties()); // now configure log4j PropertyConfigurator.configure(log4jProperties); - logger.info("Log4j is configured using the given properties."); + Log4jConfigurator.logger.info("Log4j is configured using the given properties."); } catch (Exception e) { Log4jConfigurator.configure(); - logger.error(e, e); - logger.error("Log4j COULD NOT BE INITIALIZED. The given log4jProperties seem not to be valid!"); + Log4jConfigurator.logger.error(e, e); + Log4jConfigurator.logger.error("Log4j COULD NOT BE INITIALIZED. The given log4jProperties seem not to be valid!"); } } @@ -293,17 +293,17 @@ public class Log4jConfigurator { */ public static synchronized void cleanupOldWatchdog() { // clean up an old watch dog - if (watchDog != null) { - logger.info("Stopping old Log4j watchdog."); - watchDog.interrupt(); + if (Log4jConfigurator.watchDog != null) { + Log4jConfigurator.logger.info("Stopping old Log4j watchdog."); + Log4jConfigurator.watchDog.interrupt(); try { - watchDog.join(1000l); + Log4jConfigurator.watchDog.join(1000l); } catch (InterruptedException e) { - logger.error("Oops. Could not terminate an old WatchDog."); + Log4jConfigurator.logger.error("Oops. Could not terminate an old WatchDog."); } finally { - watchDog = null; + Log4jConfigurator.watchDog = null; } - logger.info("Done."); + Log4jConfigurator.logger.info("Done."); } } } diff --git a/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java b/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java index a4f2aecda..e13794550 100644 --- a/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java +++ b/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java @@ -44,7 +44,7 @@ public class Log4jPropertyWatchDog extends Thread { /** * The delay to observe between every check. By default set {@link #DEFAULT_DELAY}. */ - protected long delay = DEFAULT_DELAY; + protected long delay = Log4jPropertyWatchDog.DEFAULT_DELAY; protected File file; protected long lastModif = 0; @@ -57,7 +57,7 @@ public class Log4jPropertyWatchDog extends Thread { protected Log4jPropertyWatchDog(String filename) { super("FileWatchdog"); this.filename = filename; - file = new File(filename); + this.file = new File(filename); setDaemon(true); checkAndConfigure(); } @@ -75,24 +75,24 @@ public class Log4jPropertyWatchDog extends Thread { protected void checkAndConfigure() { boolean fileExists; try { - fileExists = file.exists(); + fileExists = this.file.exists(); } catch (SecurityException e) { - LogLog.warn("Was not allowed to read check file existance, file:[" + filename + "]."); - interrupted = true; // there is no point in continuing + LogLog.warn("Was not allowed to read check file existance, file:[" + this.filename + "]."); + this.interrupted = true; // there is no point in continuing return; } if (fileExists) { - long l = file.lastModified(); // this can also throw a SecurityException - if (l > lastModif) { // however, if we reached this point this - lastModif = l; // is very unlikely. + long l = this.file.lastModified(); // this can also throw a SecurityException + if (l > this.lastModif) { // however, if we reached this point this + this.lastModif = l; // is very unlikely. doOnChange(); - warnedAlready = false; + this.warnedAlready = false; } } else { - if (!warnedAlready) { - LogLog.debug("[" + filename + "] does not exist."); - warnedAlready = true; + if (!this.warnedAlready) { + LogLog.debug("[" + this.filename + "] does not exist."); + this.warnedAlready = true; } } } @@ -102,7 +102,7 @@ public class Log4jPropertyWatchDog extends Thread { */ public void doOnChange() { PropertyConfigurator propertyConfigurator = new PropertyConfigurator(); - propertyConfigurator.doConfigure(filename, LogManager.getLoggerRepository()); + propertyConfigurator.doConfigure(this.filename, LogManager.getLoggerRepository()); } /** @@ -110,12 +110,12 @@ public class Log4jPropertyWatchDog extends Thread { */ @Override public void run() { - while (!interrupted) { + while (!this.interrupted) { try { - Thread.sleep(delay); + Thread.sleep(this.delay); } catch (InterruptedException e) { // no interruption expected - interrupted = true; + this.interrupted = true; } checkAndConfigure(); } diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index 30a7a17bd..c30366426 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -45,7 +45,7 @@ public class ProcessHelper { Thread errorIn = new Thread("errorIn") { @Override public void run() { - readStream(sb, "[ERROR] ", errorStream); + ProcessHelper.readStream(sb, "[ERROR] ", errorStream); } }; errorIn.start(); @@ -55,7 +55,7 @@ public class ProcessHelper { Thread infoIn = new Thread("infoIn") { @Override public void run() { - readStream(sb, "[INFO] ", inputStream); + ProcessHelper.readStream(sb, "[INFO] ", inputStream); } }; infoIn.start(); @@ -72,7 +72,7 @@ public class ProcessHelper { throw new RuntimeException("Failed to perform command: " + e.getLocalizedMessage(), e); } catch (InterruptedException e) { - logger.error("Interrupted!"); + ProcessHelper.logger.error("Interrupted!"); sb.append("[FATAL] Interrupted"); return new ProcessResult(-1, sb.toString(), e); } @@ -102,7 +102,7 @@ public class ProcessHelper { Thread errorIn = new Thread("errorIn") { @Override public void run() { - readStream(sb, "[ERROR] ", errorStream); + ProcessHelper.readStream(sb, "[ERROR] ", errorStream); } }; errorIn.start(); @@ -112,7 +112,7 @@ public class ProcessHelper { Thread infoIn = new Thread("infoIn") { @Override public void run() { - readStream(sb, "[INFO] ", inputStream); + ProcessHelper.readStream(sb, "[INFO] ", inputStream); } }; infoIn.start(); @@ -129,7 +129,7 @@ public class ProcessHelper { throw new RuntimeException("Failed to perform command: " + e.getLocalizedMessage(), e); } catch (InterruptedException e) { - logger.error("Interrupted!"); + ProcessHelper.logger.error("Interrupted!"); sb.append("[FATAL] Interrupted"); return new ProcessResult(-1, sb.toString(), e); } @@ -165,33 +165,33 @@ public class ProcessHelper { ProcessResult processResult; if (SystemHelper.isLinux()) { - processResult = runCommand("xdg-open " + pdfPath.getAbsolutePath()); + processResult = ProcessHelper.runCommand("xdg-open " + pdfPath.getAbsolutePath()); } else if (SystemHelper.isMacOS()) { - processResult = runCommand("open " + pdfPath.getAbsolutePath()); + processResult = ProcessHelper.runCommand("open " + pdfPath.getAbsolutePath()); } 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 " + processResult = ProcessHelper.runCommand("rundll32 url.dll,FileProtocolHandler " + pdfFile); } else { throw new UnsupportedOperationException("Unexpected OS: " + SystemHelper.osName); } - logProcessResult(processResult); + ProcessHelper.logProcessResult(processResult); } public static void logProcessResult(ProcessResult processResult) { if (processResult.returnValue == 0) { - logger.info("Process executed successfully"); + ProcessHelper.logger.info("Process executed successfully"); } else if (processResult.returnValue == -1) { - logger.error("Process execution failed:\n" + ProcessHelper.logger.error("Process execution failed:\n" + processResult.processOutput); - logger.error(processResult.t, processResult.t); + ProcessHelper.logger.error(processResult.t, processResult.t); } else { - logger.info("Process execution was not successful with return value:" + ProcessHelper.logger.info("Process execution was not successful with return value:" + processResult.returnValue + "\n" + processResult.processOutput); diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index c44a2ac5e..9ba4b56fb 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -59,8 +59,8 @@ public class StringHelper { for (byte b : raw) { int v = b & 0xFF; - hex[index++] = HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = HEX_CHAR_TABLE[v & 0xF]; + hex[index++] = StringHelper.HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = StringHelper.HEX_CHAR_TABLE[v & 0xF]; } return new String(hex, "ASCII"); @@ -103,7 +103,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashMd5(String string) { - return hashMd5(string.getBytes()); + return StringHelper.hashMd5(string.getBytes()); } /** @@ -116,7 +116,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashMd5(byte[] bytes) { - return hash("MD5", bytes); + return StringHelper.hash("MD5", bytes); } /** @@ -129,7 +129,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashSha1(String string) { - return hashSha1(string.getBytes()); + return StringHelper.hashSha1(string.getBytes()); } /** @@ -142,7 +142,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashSha1(byte[] bytes) { - return hash("SHA-1", bytes); + return StringHelper.hash("SHA-1", bytes); } /** @@ -155,7 +155,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashSha256(String string) { - return hashSha256(string.getBytes()); + return StringHelper.hashSha256(string.getBytes()); } /** @@ -168,7 +168,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashSha256(byte[] bytes) { - return hash("SHA-256", bytes); + return StringHelper.hash("SHA-256", bytes); } /** @@ -209,7 +209,7 @@ public class StringHelper { * @return the new string */ public static String normalizeLength(String value, int length, boolean beginning, char c) { - return normalizeLength(value, length, beginning, false, c); + return StringHelper.normalizeLength(value, length, beginning, false, c); } /** @@ -235,20 +235,21 @@ public class StringHelper { if (value.length() < length) { - while (value.length() != length) { + String tmp = value; + while (tmp.length() != length) { if (beginning) { - value = c + value; + tmp = c + tmp; } else { - value = value + c; + tmp = tmp + c; } } - return value; + return tmp; } else if (shorten) { - logger.warn("Shortening length of value: " + value); - logger.warn("Length is: " + value.length() + " max: " + length); + StringHelper.logger.warn("Shortening length of value: " + value); + StringHelper.logger.warn("Length is: " + value.length() + " max: " + length); return value.substring(0, length); } @@ -263,7 +264,7 @@ public class StringHelper { * returned */ public static String replaceSystemPropertiesIn(String value) { - return replacePropertiesIn(System.getProperties(), value); + return StringHelper.replacePropertiesIn(System.getProperties(), value); } /** @@ -278,41 +279,41 @@ public class StringHelper { * * @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) { + public static String replacePropertiesIn(Properties properties, String alue) { - // keep copy of original value - String origValue = value; + // get a copy of the value + String tmpValue = alue; // get first occurrence of $ character int pos = -1; int stop = 0; // loop on $ character positions - while ((pos = value.indexOf('$', pos + 1)) != -1) { + while ((pos = tmpValue.indexOf('$', pos + 1)) != -1) { // if pos+1 is not { character then continue - if (value.charAt(pos + 1) != '{') { + if (tmpValue.charAt(pos + 1) != '{') { continue; } // find end of sequence with } character - stop = value.indexOf('}', pos + 1); + stop = tmpValue.indexOf('}', pos + 1); // if no stop found, then break as another sequence should be able to start if (stop == -1) { - logger.error("Sequence starts at offset " + pos + " but does not end!"); - value = origValue; + StringHelper.logger.error("Sequence starts at offset " + pos + " but does not end!"); + tmpValue = alue; break; } // get sequence enclosed by pos and stop - String sequence = value.substring(pos + 2, stop); + String sequence = tmpValue.substring(pos + 2, stop); // make sure sequence doesn't contain $ { } characters if (sequence.contains("$") || sequence.contains("{") || sequence.contains("}")) { - logger.error("Enclosed sequence in offsets " + pos + " - " + stop + StringHelper.logger.error("Enclosed sequence in offsets " + pos + " - " + stop + " contains one of the illegal chars: $ { }: " + sequence); - value = origValue; + tmpValue = alue; break; } @@ -326,10 +327,10 @@ public class StringHelper { } // property exists, so replace in value - value = value.replace("${" + sequence + "}", property); + tmpValue = tmpValue.replace("${" + sequence + "}", property); } - return value; + return tmpValue; } /** @@ -340,7 +341,7 @@ public class StringHelper { * 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); + StringHelper.replaceProperties(properties, null); } /** diff --git a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java index eeb3aa284..50fb91628 100644 --- a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java @@ -34,7 +34,7 @@ public class SystemHelper { } public static SystemHelper getInstance() { - return instance; + return SystemHelper.instance; } public static final String osName = System.getProperty("os.name"); @@ -55,7 +55,7 @@ public class SystemHelper { */ @Override public String toString() { - return asString(); + return SystemHelper.asString(); } /** @@ -63,15 +63,15 @@ public class SystemHelper { */ public static String asString() { StringBuilder sb = new StringBuilder(); - sb.append(osName); + sb.append(SystemHelper.osName); sb.append(" "); - sb.append(osArch); + sb.append(SystemHelper.osArch); sb.append(" "); - sb.append(osVersion); + sb.append(SystemHelper.osVersion); sb.append(" "); - sb.append("on Java " + javaVendor); + sb.append("on Java " + SystemHelper.javaVendor); sb.append(" version "); - sb.append(javaVersion); + sb.append(SystemHelper.javaVersion); return sb.toString(); } @@ -80,23 +80,23 @@ public class SystemHelper { } public static boolean isMacOS() { - return osName.startsWith("Mac"); + return SystemHelper.osName.startsWith("Mac"); } public static boolean isWindows() { - return osName.startsWith("Win"); + return SystemHelper.osName.startsWith("Win"); } public static boolean isLinux() { - return osName.startsWith("Lin"); + return SystemHelper.osName.startsWith("Lin"); } public static boolean is32bit() { - return osArch.equals("x86") || osArch.equals("i386") || osArch.equals("i686"); + return SystemHelper.osArch.equals("x86") || SystemHelper.osArch.equals("i386") || SystemHelper.osArch.equals("i686"); } public static boolean is64bit() { - return osArch.equals("x86_64") || osArch.equals("amd64"); + return SystemHelper.osArch.equals("x86_64") || SystemHelper.osArch.equals("amd64"); } public static String getMaxMemory() { @@ -112,7 +112,7 @@ public class SystemHelper { } public static String getMemorySummary() { - return "Memory available " + getMaxMemory() + " / Used: " + getUsedMemory() + " / Free:" + getFreeMemory(); + return "Memory available " + SystemHelper.getMaxMemory() + " / Used: " + SystemHelper.getUsedMemory() + " / Free:" + SystemHelper.getFreeMemory(); } /** @@ -150,7 +150,7 @@ public class SystemHelper { * 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())); + return Boolean.valueOf(SystemHelper.getProperty(context, key, def == null ? null : def.toString())); } /** @@ -163,7 +163,7 @@ public class SystemHelper { * 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())); + return Integer.valueOf(SystemHelper.getProperty(context, key, def == null ? null : def.toString())); } /** @@ -176,6 +176,6 @@ public class SystemHelper { * 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())); + return Double.valueOf(SystemHelper.getProperty(context, key, def == null ? null : def.toString())); } } diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java index e2d0b5e09..2c55d2705 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -71,8 +71,8 @@ public class ObjectCache { this.object = object; this.operation = operation; - if (logger.isDebugEnabled()) { - logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation + " / " + if (ObjectCache.logger.isDebugEnabled()) { + ObjectCache.logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation + " / " + object.toString()); } } @@ -83,8 +83,8 @@ public class ObjectCache { * @param object */ public void setObject(T object) { - if (logger.isDebugEnabled()) { - logger.debug("Updating ID " + this.id + " to value " + object.toString()); + if (ObjectCache.logger.isDebugEnabled()) { + ObjectCache.logger.debug("Updating ID " + this.id + " to value " + object.toString()); } this.object = object; } @@ -95,8 +95,8 @@ public class ObjectCache { * @param newOperation */ public void setOperation(Operation newOperation) { - if (logger.isDebugEnabled()) { - logger.debug("Updating Operation of ID " + this.id + " from " + this.operation + " to " + newOperation); + if (ObjectCache.logger.isDebugEnabled()) { + ObjectCache.logger.debug("Updating Operation of ID " + this.id + " from " + this.operation + " to " + newOperation); } this.operation = newOperation; } @@ -105,27 +105,27 @@ public class ObjectCache { * @return the id */ public long getId() { - return id; + return this.id; } /** * @return the key */ public String getKey() { - return key; + return this.key; } /** * @return the object */ public T getObject() { - return object; + return this.object; } /** * @return the operation */ public Operation getOperation() { - return operation; + return this.operation; } } diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index a79970c11..51733db77 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -117,11 +117,11 @@ public class ObjectFilter { */ public void add(String key, T objectToAdd) { - if (logger.isDebugEnabled()) - logger.debug("add object " + objectToAdd + " with key " + key); + if (ObjectFilter.logger.isDebugEnabled()) + ObjectFilter.logger.debug("add object " + objectToAdd + " with key " + key); // add the key to the set - keySet.add(key); + this.keySet.add(key); // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. long id = objectToAdd.getTransactionID(); @@ -131,14 +131,14 @@ public class ObjectFilter { id = dispenseID(); objectToAdd.setTransactionID(id); ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); - cache.put(id, cacheObj); + this.cache.put(id, cacheObj); } else { - ObjectCache cached = cache.get(Long.valueOf(objectToAdd.getTransactionID())); + ObjectCache cached = this.cache.get(Long.valueOf(objectToAdd.getTransactionID())); if (cached == null) { // The object got an ID during this run, but was not added to the cache. // Hence, we add it now, with the current operation. ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); - cache.put(id, cacheObj); + this.cache.put(id, cacheObj); } else { String existingKey = cached.getKey(); if (!existingKey.equals(key)) { @@ -197,11 +197,11 @@ public class ObjectFilter { */ public void update(String key, T objectToUpdate) { - if (logger.isDebugEnabled()) - logger.debug("update object " + objectToUpdate + " with key " + key); + if (ObjectFilter.logger.isDebugEnabled()) + ObjectFilter.logger.debug("update object " + objectToUpdate + " with key " + key); // add the key to the keyset - keySet.add(key); + this.keySet.add(key); // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. long id = objectToUpdate.getTransactionID(); @@ -209,14 +209,14 @@ public class ObjectFilter { id = dispenseID(); objectToUpdate.setTransactionID(id); ObjectCache cacheObj = new ObjectCache(key, objectToUpdate, Operation.MODIFY); - cache.put(id, cacheObj); + this.cache.put(id, cacheObj); } else { - ObjectCache cached = cache.get(Long.valueOf(objectToUpdate.getTransactionID())); + ObjectCache cached = this.cache.get(Long.valueOf(objectToUpdate.getTransactionID())); 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(key, objectToUpdate, Operation.MODIFY); - cache.put(id, cacheObj); + this.cache.put(id, cacheObj); } else { String existingKey = cached.getKey(); if (!existingKey.equals(key)) { @@ -275,25 +275,25 @@ public class ObjectFilter { */ public void remove(String key, T objectToRemove) { - if (logger.isDebugEnabled()) - logger.debug("remove object " + objectToRemove + " with key " + key); + if (ObjectFilter.logger.isDebugEnabled()) + ObjectFilter.logger.debug("remove object " + objectToRemove + " with key " + key); // add the key to the keyset - keySet.add(key); + this.keySet.add(key); // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. long id = objectToRemove.getTransactionID(); if (id == ITransactionObject.UNSET) { id = dispenseID(); objectToRemove.setTransactionID(id); ObjectCache cacheObj = new ObjectCache(key, objectToRemove, Operation.REMOVE); - cache.put(id, cacheObj); + this.cache.put(id, cacheObj); } else { - ObjectCache cached = cache.get(Long.valueOf(id)); + ObjectCache cached = this.cache.get(Long.valueOf(id)); 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(key, objectToRemove, Operation.REMOVE); - cache.put(id, cacheObj); + this.cache.put(id, cacheObj); } else { String existingKey = cached.getKey(); if (!existingKey.equals(key)) { @@ -315,7 +315,7 @@ public class ObjectFilter { 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. - cache.remove(Long.valueOf(id)); + this.cache.remove(Long.valueOf(id)); break; case MODIFY: cached.setObject(objectToRemove); @@ -448,7 +448,7 @@ public class ObjectFilter { */ public List getAdded(String key) { List addedObjects = new LinkedList(); - Collection> allObjs = cache.values(); + Collection> allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { addedObjects.add(objectCache.getObject()); @@ -468,7 +468,7 @@ public class ObjectFilter { */ public List getAdded(Class clazz, String key) { List addedObjects = new LinkedList(); - Collection> allObjs = cache.values(); + Collection> allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { if (objectCache.getObject().getClass() == clazz) { @@ -490,7 +490,7 @@ public class ObjectFilter { */ public List getUpdated(String key) { List updatedObjects = new LinkedList(); - Collection> allObjs = cache.values(); + Collection> allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { updatedObjects.add(objectCache.getObject()); @@ -508,7 +508,7 @@ public class ObjectFilter { */ public List getUpdated(Class clazz, String key) { List updatedObjects = new LinkedList(); - Collection> allObjs = cache.values(); + Collection> allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { if (objectCache.getObject().getClass() == clazz) { @@ -530,7 +530,7 @@ public class ObjectFilter { */ public List getRemoved(String key) { List removedObjects = new LinkedList(); - Collection> allObjs = cache.values(); + Collection> allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { removedObjects.add(objectCache.getObject()); @@ -548,7 +548,7 @@ public class ObjectFilter { */ public List getRemoved(Class clazz, String key) { List removedObjects = new LinkedList(); - Collection> allObjs = cache.values(); + Collection> allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { if (objectCache.getObject().getClass() == clazz) { @@ -571,7 +571,7 @@ public class ObjectFilter { */ public List getAll(String key) { List allObjects = new LinkedList(); - Collection> allObjs = cache.values(); + Collection> allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getKey().equals(key)) { allObjects.add(objectCache.getObject()); @@ -590,7 +590,7 @@ public class ObjectFilter { */ public List> getCache(String key) { List> allCache = new LinkedList>(); - Collection> allObjs = cache.values(); + Collection> allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getKey().equals(key)) { allCache.add(objectCache); @@ -605,26 +605,26 @@ public class ObjectFilter { * @return The set containing the keys of that have been added to the filter. */ public Set keySet() { - return keySet; + return this.keySet; } /** * Clear the cache. */ public void clearCache() { - cache.clear(); - keySet.clear(); + this.cache.clear(); + this.keySet.clear(); } /** * @return get a unique transaction ID */ public synchronized long dispenseID() { - id++; - if (id == Long.MAX_VALUE) { - logger.error("Rolling IDs of objectFilter back to 1. Hope this is fine."); - id = 1; + ObjectFilter.id++; + if (ObjectFilter.id == Long.MAX_VALUE) { + ObjectFilter.logger.error("Rolling IDs of objectFilter back to 1. Hope this is fine."); + ObjectFilter.id = 1; } - return id; + return ObjectFilter.id; } } From 59e25a8e757c6ba6cf446d2d0958b7b373a087ff Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 19 Nov 2012 22:50:32 +0100 Subject: [PATCH 097/457] [Minor] code cleanup --- pom.xml | 332 ++++++++---------- .../handler/DefaultEncryptionHandler.java | 2 +- .../handler/DefaultPrivilegeHandler.java | 22 +- .../handler/XmlPersistenceHandler.java | 24 +- .../helper/BootstrapConfigurationHelper.java | 22 +- .../helper/CertificateThreadLocal.java | 6 +- .../eitchnet/privilege/helper/HashHelper.java | 6 +- .../helper/InitializationHelper.java | 15 +- .../eitchnet/privilege/helper/XmlHelper.java | 12 +- .../privilege/test/PrivilegeTest.java | 150 ++++---- .../test/model/TestSystemUserAction.java | 2 +- .../test/model/TestSystemUserActionDeny.java | 2 +- 12 files changed, 278 insertions(+), 317 deletions(-) diff --git a/pom.xml b/pom.xml index 75a37097c..0a189f43f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,201 +1,163 @@ - 4.0.0 - ch.eitchnet - ch.eitchnet.privilege - jar - 0.1.0-SNAPSHOT - ch.eitchnet.privilege - https://github.com/eitch/ch.eitchnet.privilege + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 - - UTF-8 - + ch.eitchnet + ch.eitchnet.privilege + jar + 0.1.0-SNAPSHOT + ch.eitchnet.privilege + https://github.com/eitch/ch.eitchnet.privilege - + + UTF-8 + - 2011 - - - GNU Lesser General Public License - http://www.gnu.org/licenses/lgpl.html - repo - - - - eitchnet.ch - http://blog.eitchnet.ch - - - - eitch - Robert von Vurg - eitch@eitchnet.ch - http://blog.eitchnet.ch - eitchnet.ch - http://blog.eitchnet.ch - - architect - developer - - +1 - - http://localhost - - - + - - Github Issues - https://github.com/eitch/ch.eitchnet.privilege/issues - + 2011 + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl.html + repo + + + + eitchnet.ch + http://blog.eitchnet.ch + + + + eitch + Robert von Vurg + eitch@eitchnet.ch + http://blog.eitchnet.ch + eitchnet.ch + http://blog.eitchnet.ch + + architect + developer + + +1 + + http://localhost + + + - + - - scm:git:https://github.com/eitch/ch.eitchnet.privilege.git - scm:git:git@github.com:eitch/ch.eitchnet.privilege.git - https://github.com/eitch/ch.eitchnet.privilege - + + scm:git:https://github.com/eitch/ch.eitchnet.privilege.git + scm:git:git@github.com:eitch/ch.eitchnet.privilege.git + https://github.com/eitch/ch.eitchnet.privilege + - + - - - junit - junit - 4.10 - test - - - log4j - log4j - 1.2.17 - - - maven - dom4j - 1.7-20060614 - - - ch.eitchnet - ch.eitchnet.utils - 0.1.0-SNAPSHOT - - + + + junit + junit + 4.10 + test + + + log4j + log4j + 1.2.17 + + + maven + dom4j + 1.7-20060614 + + + ch.eitchnet + ch.eitchnet.utils + 0.1.0-SNAPSHOT + + - - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - true - true - - + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar-no-fork - - - - + + org.apache.maven.plugins + maven-compiler-plugin + 3.0 + + 1.6 + 1.6 + + - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - true - true - - - - - + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + - - org.apache.maven.plugins - maven-site-plugin - 2.3 - - UTF-8 - - + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + + + - - + + org.apache.maven.plugins + maven-site-plugin + 2.3 + + UTF-8 + + + + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 98a5609ba..76d63ce5c 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -124,7 +124,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { // test hash algorithm try { convertToHash("test"); - logger.info("Using hashing algorithm " + this.hashAlgorithm); + DefaultEncryptionHandler.logger.info("Using hashing algorithm " + this.hashAlgorithm); } catch (Exception e) { throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid because of underlying exception: " diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index d4444d9af..cf87c63b8 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -403,7 +403,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // ignore if user already has role Set currentRoles = user.getRoles(); if (currentRoles.contains(roleName)) { - logger.error("User " + username + " already has role " + roleName); + DefaultPrivilegeHandler.logger.error("User " + username + " already has role " + roleName); return; } @@ -496,7 +496,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // ignore if user does not have role Set currentRoles = user.getRoles(); if (!currentRoles.contains(roleName)) { - logger.error("User " + user + " does not have role " + roleName); + DefaultPrivilegeHandler.logger.error("User " + user + " does not have role " + roleName); return; } @@ -714,10 +714,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); // log - logger.info("User " + username + " authenticated: " + session); + DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + session); } catch (RuntimeException e) { - logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); + DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); throw e; } finally { clearPassword(password); @@ -742,9 +742,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // return true if object was really removed boolean loggedOut = certificateSessionPair != null; if (loggedOut) - logger.info("User " + certificate.getUsername() + " logged out."); + DefaultPrivilegeHandler.logger.info("User " + certificate.getUsername() + " logged out."); else - logger.warn("User already logged out!"); + DefaultPrivilegeHandler.logger.warn("User already logged out!"); return loggedOut; } @@ -785,7 +785,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role role = this.persistenceHandler.getRole(roleName); if (role == null) { - logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + DefaultPrivilegeHandler.logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; } @@ -861,7 +861,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // otherwise delegate checking to the policy configured for this privilege - PrivilegePolicy policy = this.getPolicy(privilege.getPolicy()); + PrivilegePolicy policy = getPolicy(privilege.getPolicy()); if (policy == null) { throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " + privilegeName); @@ -921,7 +921,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { // validate certificate - this.isCertificateValid(certificate); + isCertificateValid(certificate); // get user object User user = this.persistenceHandler.getUser(certificate.getUsername()); @@ -1139,7 +1139,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private Certificate getSystemUserCertificate(String systemUsername) { // see if a certificate has already been created for this system user - Certificate systemUserCertificate = systemUserCertificateMap.get(systemUsername); + Certificate systemUserCertificate = this.systemUserCertificateMap.get(systemUsername); if (systemUserCertificate != null) return systemUserCertificate; @@ -1187,7 +1187,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.sessionMap.put(sessionId, new CertificateSessionPair(session, systemUserCertificate)); // log - logger.info("The system user " + systemUsername + " is logged in with session " + session); + DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " + session); return systemUserCertificate; } diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index e1323f7ff..0dd164e7d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -153,7 +153,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { File modelFile = new File(this.modelPath); boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; if (!(modelFileUnchanged && this.roleMapDirty && this.userMapDirty)) { - logger.warn("Not persisting as current file is unchanged and model data is not dirty"); + XmlPersistenceHandler.logger.warn("Not persisting as current file is unchanged and model data is not dirty"); return false; } @@ -164,7 +164,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // USERS // build XML DOM of users - List users = toDomUsers(this.userMap); + List users = XmlPersistenceHandler.toDomUsers(this.userMap); Element usersElement = docFactory.createElement(XmlConstants.XML_USERS); for (Element userElement : users) { usersElement.add(userElement); @@ -173,7 +173,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // ROLES // build XML DOM of roles - List roles = toDomRoles(this.roleMap); + List roles = XmlPersistenceHandler.toDomRoles(this.roleMap); Element rolesElement = docFactory.createElement(XmlConstants.XML_ROLES); for (Element roleElement : roles) { rolesElement.add(roleElement); @@ -233,8 +233,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMapDirty = false; this.roleMapDirty = false; - logger.info("Read " + this.userMap.size() + " Users"); - logger.info("Read " + this.roleMap.size() + " Roles"); + XmlPersistenceHandler.logger.info("Read " + this.userMap.size() + " Users"); + XmlPersistenceHandler.logger.info("Read " + this.roleMap.size() + " Roles"); // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; @@ -247,7 +247,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } if (!privilegeAdminExists) { - logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + XmlPersistenceHandler.logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + "' exists. Privilege modifications will not be possible!"); } @@ -279,7 +279,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.modelPath = basePath + "/" + modelFileName; if (reload()) - logger.info("Privilege Data loaded."); + XmlPersistenceHandler.logger.info("Privilege Data loaded."); } /** @@ -320,9 +320,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { for (Element roleElement : rolesElementList) { String roleName = roleElement.getTextTrim(); if (roleName.isEmpty()) { - logger.error("User " + username + " has a role defined with no name, Skipped."); + XmlPersistenceHandler.logger.error("User " + username + " has a role defined with no name, Skipped."); } else if (!this.roleMap.containsKey(roleName)) { - logger.error("User " + username + " has a inexistant role " + roleName + ", Skipped."); + XmlPersistenceHandler.logger.error("User " + username + " has a inexistant role " + roleName + ", Skipped."); } else { roles.add(roleName); } @@ -330,14 +330,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read properties Element propertiesElement = userElement.element(XmlConstants.XML_PROPERTIES); - Map propertyMap = convertToPropertyMap(propertiesElement); + Map propertyMap = XmlPersistenceHandler.convertToPropertyMap(propertiesElement); // create user User user = new User(userId, username, password, firstname, surname, userState, roles, locale, propertyMap); // put user in map userMap.put(username, user); - logger.info("Loaded user " + user); + XmlPersistenceHandler.logger.info("Loaded user " + user); } return userMap; @@ -508,7 +508,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); // add all the privileges - toDomPrivileges(roleElement, role.getPrivilegeMap()); + XmlPersistenceHandler.toDomPrivileges(roleElement, role.getPrivilegeMap()); // add element to return list rolesAsElements.add(roleElement); diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 42677a198..f24577af0 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -75,12 +75,12 @@ public class BootstrapConfigurationHelper { Logger.getRootLogger().setLevel(Level.INFO); // get current directory - path = System.getProperty("user.dir") + "/newConfig"; + BootstrapConfigurationHelper.path = System.getProperty("user.dir") + "/newConfig"; // TODO ask user where to save configuration, default is pwd/newConfig/.... // see if path already exists - File pathF = new File(path); + File pathF = new File(BootstrapConfigurationHelper.path); if (pathF.exists()) { throw new RuntimeException("Path already exists: " + pathF.getAbsolutePath()); } @@ -92,9 +92,9 @@ public class BootstrapConfigurationHelper { // TODO ask other questions... // now perform work: - createXmlPrivilegeContainer(); - createPolicyConfiguration(); - createModel(); + BootstrapConfigurationHelper.createXmlPrivilegeContainer(); + BootstrapConfigurationHelper.createPolicyConfiguration(); + BootstrapConfigurationHelper.createModel(); } /** @@ -133,34 +133,34 @@ public class BootstrapConfigurationHelper { // create PersistenceHandler Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); containerElement.add(persistenceHandlerElem); - persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPersistenceHandler); + persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, BootstrapConfigurationHelper.defaultPersistenceHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); persistenceHandlerElem.add(parametersElement); // Parameter basePath parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_BASE_PATH); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, basePath); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.basePath); parametersElement.add(parameterElement); // Parameter modelXmlFile parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_MODEL_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, modelFileName); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.modelFileName); parametersElement.add(parameterElement); // create EncryptionHandler Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); containerElement.add(encryptionHandlerElem); - encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultEncryptionHandler); + encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, BootstrapConfigurationHelper.defaultEncryptionHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); encryptionHandlerElem.add(parametersElement); // Parameter hashAlgorithm parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_HASH_ALGORITHM); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, hashAlgorithm); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.hashAlgorithm); parametersElement.add(parameterElement); // write the container file to disk - File privilegeContainerFile = new File(path + "/" + defaultPrivilegeContainerXmlFile); + File privilegeContainerFile = new File(BootstrapConfigurationHelper.path + "/" + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); XmlHelper.writeDocument(doc, privilegeContainerFile); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java index 9830541e3..161687258 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java +++ b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java @@ -33,14 +33,14 @@ public class CertificateThreadLocal extends ThreadLocal { } public static CertificateThreadLocal getInstance() { - return instance; + return CertificateThreadLocal.instance; } public static Certificate getCert() { - return instance.get(); + return CertificateThreadLocal.instance.get(); } public static void setCert(Certificate certificate) { - instance.set(certificate); + CertificateThreadLocal.instance.set(certificate); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java index b0da71fee..c038740bd 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java @@ -54,7 +54,7 @@ public class HashHelper { */ public static String stringToHash(String hashAlgorithm, String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { - return stringToHash(hashAlgorithm, string.getBytes()); + return HashHelper.stringToHash(hashAlgorithm, string.getBytes()); } /** @@ -83,8 +83,8 @@ public class HashHelper { for (byte b : hashArray) { int v = b & 0xFF; - hex[index++] = HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = HEX_CHAR_TABLE[v & 0xF]; + hex[index++] = HashHelper.HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = HashHelper.HEX_CHAR_TABLE[v & 0xF]; } return new String(hex, "ASCII"); diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java index fad40de25..0a9164091 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -35,7 +35,6 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.policy.PrivilegePolicy; import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.helper.SystemHelper; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the @@ -81,19 +80,19 @@ public class InitializationHelper { // get policies Element policiesElement = rootElement.element(XmlConstants.XML_POLICIES); - Map> policyMap = convertToPolicyMap(policiesElement); + Map> policyMap = InitializationHelper.convertToPolicyMap(policiesElement); try { // get parameters Element parameterElement = encryptionHandlerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = convertToParameterMap(parameterElement); + Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); // initialize encryption handler encryptionHandler.initialize(parameterMap); } catch (Exception e) { - logger.error(e, e); + InitializationHelper.logger.error(e, e); throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + " could not be initialized"); } @@ -102,13 +101,13 @@ public class InitializationHelper { // get parameters Element parameterElement = persistenceHandlerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = convertToParameterMap(parameterElement); + Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); // initialize persistence handler persistenceHandler.initialize(parameterMap); } catch (Exception e) { - logger.error(e, e); + InitializationHelper.logger.error(e, e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + " could not be initialized"); } @@ -117,13 +116,13 @@ public class InitializationHelper { // get parameters Element parameterElement = containerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = convertToParameterMap(parameterElement); + Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); // initialize privilege handler privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { - logger.error(e, e); + InitializationHelper.logger.error(e, e); throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + " could not be initialized"); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index 9cb3786b7..ea86556b7 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -69,7 +69,7 @@ public class XmlHelper { SAXReader reader = new SAXReader(); Document document = reader.read(inStream); - logger.info("Read XML document " + document.getRootElement().getName()); + XmlHelper.logger.info("Read XML document " + document.getRootElement().getName()); return document; } catch (FileNotFoundException e) { @@ -89,7 +89,7 @@ public class XmlHelper { */ public static void writeDocument(Document document, File file) { - logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); + XmlHelper.logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); OutputStream fileOutputStream = null; @@ -99,7 +99,7 @@ public class XmlHelper { String aEncodingScheme = document.getXMLEncoding(); if (aEncodingScheme == null || aEncodingScheme.isEmpty()) { - aEncodingScheme = DEFAULT_ENCODING; + aEncodingScheme = XmlHelper.DEFAULT_ENCODING; } OutputFormat outformat = OutputFormat.createPrettyPrint(); outformat.setEncoding(aEncodingScheme); @@ -117,7 +117,7 @@ public class XmlHelper { try { fileOutputStream.close(); } catch (IOException e) { - logger.error("Could not close file output stream: " + e, e); + XmlHelper.logger.error("Could not close file output stream: " + e, e); } } } @@ -133,10 +133,10 @@ public class XmlHelper { */ public static void writeElement(Element rootElement, File file) { - Document document = DocumentFactory.getInstance().createDocument(DEFAULT_ENCODING); + Document document = DocumentFactory.getInstance().createDocument(XmlHelper.DEFAULT_ENCODING); document.setRootElement(rootElement); document.setName(rootElement.getName()); - writeDocument(document, file); + XmlHelper.writeDocument(document, file); } } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index cd1ada34d..778ac48fd 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -87,9 +87,9 @@ public class PrivilegeTest { // initialize container String pwd = System.getProperty("user.dir"); File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); - privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); + PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); } catch (Exception e) { - logger.error(e, e); + PrivilegeTest.logger.error(e, e); throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); } @@ -102,9 +102,9 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } private byte[] copyBytes(byte[] bytes) { @@ -120,9 +120,9 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_BAD)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_BAD)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -132,9 +132,9 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, null); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -144,18 +144,18 @@ public class PrivilegeTest { @Test public void testAddUserBobAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, + UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, new HashMap()); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user " + BOB); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); // set bob's password - privilegeHandler.setUserPassword(certificate, BOB, copyBytes(PASS_BOB)); - logger.info("Set Bob's password"); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.logger.info("Set Bob's password"); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -166,8 +166,8 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -176,9 +176,9 @@ public class PrivilegeTest { */ @Test public void testEnableUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -190,9 +190,9 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -201,13 +201,13 @@ public class PrivilegeTest { */ @Test public void testAddRole() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); + RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); - privilegeHandler.addOrReplaceRole(certificate, roleRep); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -216,9 +216,9 @@ public class PrivilegeTest { */ @Test public void testAddRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -227,8 +227,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -241,15 +241,15 @@ public class PrivilegeTest { public void testFailAddUserTedAsBob() throws Exception { // auth as Bog - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted - UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, + UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, new HashMap()); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user " + TED); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -259,10 +259,10 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -272,18 +272,18 @@ public class PrivilegeTest { @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted HashSet roles = new HashSet(); - roles.add(ROLE_USER); - UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, + roles.add(PrivilegeTest.ROLE_USER); + UserRep userRep = new UserRep("2", PrivilegeTest.TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user " + TED); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -293,13 +293,13 @@ public class PrivilegeTest { @Test public void testSetTedPwdAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set ted's password to default - privilegeHandler.setUserPassword(certificate, TED, copyBytes(PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -308,9 +308,9 @@ public class PrivilegeTest { */ @Test public void testTedChangesOwnPwd() throws Exception { - Certificate certificate = privilegeHandler.authenticate(TED, copyBytes(PASS_DEF)); - privilegeHandler.setUserPassword(certificate, TED, copyBytes(PASS_TED)); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -319,8 +319,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsTed() throws Exception { - Certificate certificate = privilegeHandler.authenticate(TED, copyBytes(PASS_TED)); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -330,13 +330,13 @@ public class PrivilegeTest { @Test public void testPerformRestrictableAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); - privilegeHandler.actionAllowed(certificate, restrictable); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -347,15 +347,15 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailPerformRestrictableAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); try { - privilegeHandler.actionAllowed(certificate, restrictable); + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); } finally { - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } } @@ -366,10 +366,10 @@ public class PrivilegeTest { @Test public void testAddAppRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); - logger.info("Added " + ROLE_APP_USER + " to " + BOB); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); + PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -380,15 +380,15 @@ public class PrivilegeTest { */ @Test public void testPerformRestrictableAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); try { - privilegeHandler.actionAllowed(certificate, restrictable); + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); } finally { - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } } @@ -402,10 +402,10 @@ public class PrivilegeTest { public void testPerformSystemRestrictable() throws Exception { // create the action to be performed as a system user - TestSystemUserAction action = new TestSystemUserAction(privilegeHandler); + TestSystemUserAction action = new TestSystemUserAction(PrivilegeTest.privilegeHandler); // and then perform the action - privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + PrivilegeTest.privilegeHandler.runAsSystem(PrivilegeTest.SYSTEM_USER_ADMIN, action); } /** @@ -418,10 +418,10 @@ public class PrivilegeTest { public void testPerformSystemRestrictableFailPrivilege() throws Exception { // create the action to be performed as a system user - TestSystemUserActionDeny action = new TestSystemUserActionDeny(privilegeHandler); + TestSystemUserActionDeny action = new TestSystemUserActionDeny(PrivilegeTest.privilegeHandler); // and then perform the action - privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + PrivilegeTest.privilegeHandler.runAsSystem(PrivilegeTest.SYSTEM_USER_ADMIN, action); } /** @@ -433,13 +433,13 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testLoginSystemUser() throws Exception { - privilegeHandler.authenticate(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.SYSTEM_USER_ADMIN, PrivilegeTest.SYSTEM_USER_ADMIN.getBytes()); } @Test public void testCertificateThreadLocal() { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set certificate into thread local @@ -448,9 +448,9 @@ public class PrivilegeTest { // see if bob can perform restrictable by returning certificate from CertificateThreadLocal Restrictable restrictable = new TestRestrictable(); try { - privilegeHandler.actionAllowed(CertificateThreadLocal.getInstance().get(), restrictable); + PrivilegeTest.privilegeHandler.actionAllowed(CertificateThreadLocal.getInstance().get(), restrictable); } finally { - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } } } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index 2b7cfc786..f247d680b 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -46,7 +46,7 @@ public class TestSystemUserAction implements SystemUserAction { TestSystemRestrictable restrictable = new TestSystemRestrictable(); - handler.actionAllowed(certificate, restrictable); + this.handler.actionAllowed(certificate, restrictable); } } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java index 1220d008b..14f6e566a 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -45,6 +45,6 @@ public class TestSystemUserActionDeny implements SystemUserAction { public void execute(Certificate certificate) { TestRestrictable restrictable = new TestRestrictable(); - handler.actionAllowed(certificate, restrictable); + this.handler.actionAllowed(certificate, restrictable); } } From 3f5bf2d33434df7b1218773a4a7b3ef87416c1d2 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 24 Nov 2012 13:22:40 +0100 Subject: [PATCH 098/457] [Major] refactored use of log4j to slf4j --- pom.xml | 14 +- .../java/ch/eitchnet/rmi/RmiFileHandler.java | 5 +- src/main/java/ch/eitchnet/rmi/RmiHelper.java | 5 +- .../ch/eitchnet/utils/helper/FileHelper.java | 16 +- .../utils/helper/Log4jConfigurator.java | 309 ------ .../utils/helper/Log4jPropertyWatchDog.java | 123 --- .../eitchnet/utils/helper/ProcessHelper.java | 50 +- .../eitchnet/utils/helper/StringHelper.java | 931 +++++++++--------- .../eitchnet/utils/helper/SystemHelper.java | 9 +- .../utils/objectfilter/ObjectCache.java | 12 +- .../utils/objectfilter/ObjectFilter.java | 7 +- 11 files changed, 525 insertions(+), 956 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java delete mode 100644 src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java diff --git a/pom.xml b/pom.xml index 22a55833e..722434b6e 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,7 @@ jar 0.1.0-SNAPSHOT ch.eitchnet.utils + These utils contain project independent helper classes and utilities for reuse https://github.com/eitch/ch.eitchnet.utils @@ -82,9 +83,15 @@ test - log4j - log4j - 1.2.17 + org.slf4j + slf4j-api + 1.7.2 + + + org.slf4j + slf4j-log4j12 + 1.7.2 + test @@ -132,7 +139,6 @@ true true - diff --git a/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java b/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java index 7d3ff04b4..6dcc256b4 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java +++ b/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java @@ -24,7 +24,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.utils.helper.StringHelper; @@ -37,7 +38,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class RmiFileHandler { - private static final Logger logger = Logger.getLogger(RmiFileHandler.class); + private static final Logger logger = LoggerFactory.getLogger(RmiFileHandler.class); /** * DEF_PART_SIZE = default part size which is set to 1048576 bytes (1 MiB) diff --git a/src/main/java/ch/eitchnet/rmi/RmiHelper.java b/src/main/java/ch/eitchnet/rmi/RmiHelper.java index 6cf6dae2a..049e4dbd7 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiHelper.java +++ b/src/main/java/ch/eitchnet/rmi/RmiHelper.java @@ -25,7 +25,8 @@ import java.io.FileInputStream; import java.io.IOException; import java.rmi.RemoteException; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.utils.helper.StringHelper; @@ -36,7 +37,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class RmiHelper { - private static final Logger logger = Logger.getLogger(RmiHelper.class); + private static final Logger logger = LoggerFactory.getLogger(RmiHelper.class); /** * @param rmiFileClient diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index aac499b67..0bcbbf1f6 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -36,7 +36,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Helper class for dealing with files @@ -45,7 +46,7 @@ import org.apache.log4j.Logger; */ public class FileHelper { - private static final Logger logger = Logger.getLogger(FileHelper.class); + private static final Logger logger = LoggerFactory.getLogger(FileHelper.class); /** * Reads the contents of a file into a string. Note, no encoding is checked. It is expected to be UTF-8 @@ -55,7 +56,7 @@ public class FileHelper { * @return the contents of a file as a string */ public static final String readFileToString(File file) { - + BufferedReader bufferedReader = null; try { @@ -195,7 +196,8 @@ public class FileHelper { String fromFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(fromFile)); String toFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(toFile)); if (!fromFileMD5.equals(toFileMD5)) { - FileHelper.logger.error("Copying failed, as MD5 sums are not equal: " + fromFileMD5 + " / " + toFileMD5); + FileHelper.logger.error("Copying failed, as MD5 sums are not equal: " + fromFileMD5 + " / " + + toFileMD5); toFile.delete(); return false; @@ -204,8 +206,8 @@ public class FileHelper { // cleanup if files are not the same length if (fromFile.length() != toFile.length()) { - FileHelper.logger.error("Copying failed, as new files are not the same length: " + fromFile.length() + " / " - + toFile.length()); + FileHelper.logger.error("Copying failed, as new files are not the same length: " + fromFile.length() + + " / " + toFile.length()); toFile.delete(); return false; @@ -213,7 +215,7 @@ public class FileHelper { } catch (Exception e) { - FileHelper.logger.error(e, e); + FileHelper.logger.error(e.getMessage(), e); return false; } finally { diff --git a/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java b/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java deleted file mode 100644 index 678ff78f3..000000000 --- a/src/main/java/ch/eitchnet/utils/helper/Log4jConfigurator.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . - * - */ -package ch.eitchnet.utils.helper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; -import java.util.Properties; - -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.ConsoleAppender; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PatternLayout; -import org.apache.log4j.PropertyConfigurator; - -/** - * A simple configurator to configure log4j, with fall back default configuration - * - * @author Robert von Burg - */ -public class Log4jConfigurator { - - /** - * system property used to override the log4j configuration file - */ - public static final String PROP_FILE_LOG4J = "rsp.log4j.properties"; - - /** - * default log4j configuration file - */ - public static final String FILE_LOG4J = "log4j.properties"; - - /** - * runtime log4j configuration file which is a copy of the original file but has any place holders overwritten - */ - public static final String FILE_LOG4J_TEMP = "log4j.properties.tmp"; - - private static final Logger logger = Logger.getLogger(Log4jConfigurator.class); - private static Log4jPropertyWatchDog watchDog; - - /** - * Configures log4j with the default {@link ConsoleAppender} - */ - public static synchronized void configure() { - Log4jConfigurator.cleanupOldWatchdog(); - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(Log4jConfigurator.getDefaulLayout())); - Logger.getRootLogger().setLevel(Level.INFO); - } - - /** - * Returns the default layout: %d %5p [%t] %C{1} %M - %m%n - * - * @return the default layout - */ - public static PatternLayout getDefaulLayout() { - return new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"); - } - - /** - *

    - * Loads the log4j configuration - *

    - * - *

    - * This file is configurable through the {@link Log4jConfigurator#PROP_FILE_LOG4J} system property, but uses the - * default {@link Log4jConfigurator#FILE_LOG4J} file, if no configuration option is set. The path used is - * /config/ whereas is a system property - *

    - * - *

    - * Any properties in the properties are substituted using - * {@link StringHelper#replaceProperties(Properties, Properties)} and then the configuration file is written to a - * new file /tmp/ {@link Log4jConfigurator#FILE_LOG4J_TEMP} and then finally - * {@link PropertyConfigurator#configureAndWatch(String)} is called so that the configuration is loaded and log4j - * watches the temporary file for configuration changes - *

    - */ - public static synchronized void loadLog4jConfiguration() { - - // get a configured log4j properties file, or use default - // RSPConfigConstants.FILE_LOG4J - String fileLog4j = SystemHelper.getProperty(Log4jConfigurator.class.getName(), - Log4jConfigurator.PROP_FILE_LOG4J, Log4jConfigurator.FILE_LOG4J); - - // get the root directory - String userDir = System.getProperty("user.dir"); - String configDir = userDir + "/config/"; - - String pathNameToLog4j = configDir + fileLog4j; - File log4JPath = new File(pathNameToLog4j); - - try { - - // load the log4j.properties file - if (!log4JPath.exists()) - throw new RuntimeException("The log4j configuration file does not exist at " - + log4JPath.getAbsolutePath()); - - // now perform the loading - Log4jConfigurator.loadLog4jConfiguration(log4JPath); - - } catch (Exception e) { - - Log4jConfigurator.configure(); - Log4jConfigurator.logger.error(e, e); - Log4jConfigurator.logger.error("Log4j COULD NOT BE INITIALIZED. Please check the log4j configuration file exists at " - + log4JPath.getAbsolutePath()); - - } - } - - /** - *

    - * Loads the given log4j configuration - *

    - * - *

    - * Any properties in the properties are substituted using - * {@link StringHelper#replaceProperties(Properties, Properties)} and then the configuration file is written to a - * new file /tmp/ {@link Log4jConfigurator#FILE_LOG4J_TEMP} and then finally - * {@link PropertyConfigurator#configureAndWatch(String)} is called so that the configuration is loaded and log4j - * watches the temporary file for configuration changes - *

    - * - * @param log4jConfigPath - */ - public static synchronized void loadLog4jConfiguration(File log4jConfigPath) { - - if (log4jConfigPath == null) - throw new RuntimeException("log4jConfigPath may not be null!"); - - // first clean up any old watch dog in case of a programmatic re-load of the configuration - Log4jConfigurator.cleanupOldWatchdog(); - - // get the root directory - String userDir = System.getProperty("user.dir"); - String tmpDir = userDir + "/tmp/"; - - String pathNameToLog4jTemp = tmpDir + Log4jConfigurator.FILE_LOG4J_TEMP; - Properties log4jProperties = new Properties(); - FileInputStream fin = null; - FileOutputStream fout = null; - - try { - - fin = new FileInputStream(log4jConfigPath); - log4jProperties.load(fin); - fin.close(); - - // replace any variables - StringHelper.replaceProperties(log4jProperties, System.getProperties()); - - // write this as the temporary log4j file - File logsFileDir = new File(tmpDir); - if (!logsFileDir.exists() && !logsFileDir.mkdirs()) - throw new RuntimeException("Could not create log path " + logsFileDir.getAbsolutePath()); - - fout = new FileOutputStream(pathNameToLog4jTemp); - log4jProperties.store(fout, "Running instance log4j configuration " + new Date()); - fout.close(); - - // XXX if the server is in a web context, then we may not use the - // FileWatchDog - BasicConfigurator.resetConfiguration(); - Log4jConfigurator.watchDog = new Log4jPropertyWatchDog(pathNameToLog4jTemp); - Log4jConfigurator.watchDog.start(); - - Log4jConfigurator.logger.info("Log4j is configured to use and watch file " + pathNameToLog4jTemp); - - } catch (Exception e) { - - Log4jConfigurator.configure(); - Log4jConfigurator.logger.error(e, e); - Log4jConfigurator.logger.error("Log4j COULD NOT BE INITIALIZED. Please check the log4j configuration file at " - + log4jConfigPath); - - } finally { - if (fin != null) { - try { - fin.close(); - } catch (IOException e) { - Log4jConfigurator.logger.error("Exception closing input file: " + e, e); - } - } - if (fout != null) { - try { - fout.close(); - } catch (IOException e) { - Log4jConfigurator.logger.error("Exception closing output file: " + e, e); - } - } - } - } - - /** - *

    - * Loads the log4j configuration file as a class resource by calling {@link Class#getResourceAsStream(String)} for - * the given class - *

    - * - * @param clazz - */ - public static synchronized void loadLog4jConfigurationAsResource(Class clazz) { - - try { - - if (clazz == null) - throw new RuntimeException("clazz may not be null!"); - - InputStream resourceAsStream = clazz.getResourceAsStream("/" + Log4jConfigurator.FILE_LOG4J); - if (resourceAsStream == null) { - throw new RuntimeException("The resource '" + Log4jConfigurator.FILE_LOG4J + "' could not be found for class " - + clazz.getName()); - } - - // load the properties from the input stream - Properties log4jProperties = new Properties(); - log4jProperties.load(resourceAsStream); - - // and then - Log4jConfigurator.loadLog4jConfiguration(log4jProperties); - - } catch (Exception e) { - Log4jConfigurator.configure(); - Log4jConfigurator.logger.error(e, e); - Log4jConfigurator.logger.error("Log4j COULD NOT BE INITIALIZED. Please check that the log4j configuration file '" - + Log4jConfigurator.FILE_LOG4J + "' exists as a resource for class " + clazz.getName() - + " and is a valid properties configuration"); - } - } - - /** - *

    - * Loads the given log4j configuration. Log4j is configured with the given properties. The only change is that - * {@link StringHelper#replaceProperties(Properties, Properties)} is used to replace any properties - *

    - * - *

    - * No property watch dog is loaded - *

    - * - * @param log4jProperties - * the properties to use for the log4j configuration - */ - public static synchronized void loadLog4jConfiguration(Properties log4jProperties) { - - try { - - if (log4jProperties == null) - throw new RuntimeException("log4jProperties may not be null!"); - - // first clean up any old watch dog in case of a programmatic re-load of the configuration - Log4jConfigurator.cleanupOldWatchdog(); - - // replace any variables - StringHelper.replaceProperties(log4jProperties, System.getProperties()); - - // now configure log4j - PropertyConfigurator.configure(log4jProperties); - Log4jConfigurator.logger.info("Log4j is configured using the given properties."); - - } catch (Exception e) { - Log4jConfigurator.configure(); - Log4jConfigurator.logger.error(e, e); - Log4jConfigurator.logger.error("Log4j COULD NOT BE INITIALIZED. The given log4jProperties seem not to be valid!"); - } - } - - /** - * Cleanup a running watch dog - */ - public static synchronized void cleanupOldWatchdog() { - // clean up an old watch dog - if (Log4jConfigurator.watchDog != null) { - Log4jConfigurator.logger.info("Stopping old Log4j watchdog."); - Log4jConfigurator.watchDog.interrupt(); - try { - Log4jConfigurator.watchDog.join(1000l); - } catch (InterruptedException e) { - Log4jConfigurator.logger.error("Oops. Could not terminate an old WatchDog."); - } finally { - Log4jConfigurator.watchDog = null; - } - Log4jConfigurator.logger.info("Done."); - } - } -} diff --git a/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java b/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java deleted file mode 100644 index e13794550..000000000 --- a/src/main/java/ch/eitchnet/utils/helper/Log4jPropertyWatchDog.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . - * - */ -package ch.eitchnet.utils.helper; - -import java.io.File; - -import org.apache.log4j.LogManager; -import org.apache.log4j.PropertyConfigurator; -import org.apache.log4j.helpers.LogLog; - -/** - * @author Robert von Burg - * - */ -public class Log4jPropertyWatchDog extends Thread { - - /** - * The default delay between every file modification check, set to 60 seconds. - */ - public static final long DEFAULT_DELAY = 60000; - - /** - * The name of the file to observe for changes. - */ - protected String filename; - - /** - * The delay to observe between every check. By default set {@link #DEFAULT_DELAY}. - */ - protected long delay = Log4jPropertyWatchDog.DEFAULT_DELAY; - - protected File file; - protected long lastModif = 0; - protected boolean warnedAlready = false; - protected boolean interrupted = false; - - /** - * @param filename - */ - protected Log4jPropertyWatchDog(String filename) { - super("FileWatchdog"); - this.filename = filename; - this.file = new File(filename); - setDaemon(true); - checkAndConfigure(); - } - - /** - * Set the delay to observe between each check of the file changes. - */ - public void setDelay(long delay) { - this.delay = delay; - } - - /** - * - */ - protected void checkAndConfigure() { - boolean fileExists; - try { - fileExists = this.file.exists(); - } catch (SecurityException e) { - LogLog.warn("Was not allowed to read check file existance, file:[" + this.filename + "]."); - this.interrupted = true; // there is no point in continuing - return; - } - - if (fileExists) { - long l = this.file.lastModified(); // this can also throw a SecurityException - if (l > this.lastModif) { // however, if we reached this point this - this.lastModif = l; // is very unlikely. - doOnChange(); - this.warnedAlready = false; - } - } else { - if (!this.warnedAlready) { - LogLog.debug("[" + this.filename + "] does not exist."); - this.warnedAlready = true; - } - } - } - - /** - * Call {@link PropertyConfigurator#configure(String)} with the filename to reconfigure log4j. - */ - public void doOnChange() { - PropertyConfigurator propertyConfigurator = new PropertyConfigurator(); - propertyConfigurator.doConfigure(this.filename, LogManager.getLoggerRepository()); - } - - /** - * @see java.lang.Thread#run() - */ - @Override - public void run() { - while (!this.interrupted) { - try { - Thread.sleep(this.delay); - } catch (InterruptedException e) { - // no interruption expected - this.interrupted = true; - } - checkAndConfigure(); - } - } -} diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index c30366426..b2c974b48 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -24,14 +24,15 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @author Robert von Burg */ public class ProcessHelper { - private static final Logger logger = Logger.getLogger(ProcessHelper.class); + private static final Logger logger = LoggerFactory.getLogger(ProcessHelper.class); public static ProcessResult runCommand(String command) { final StringBuffer sb = new StringBuffer(); @@ -40,8 +41,7 @@ public class ProcessHelper { final Process process = Runtime.getRuntime().exec(command); - final BufferedReader errorStream = new BufferedReader( - new InputStreamReader(process.getErrorStream())); + final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); Thread errorIn = new Thread("errorIn") { @Override public void run() { @@ -50,8 +50,7 @@ public class ProcessHelper { }; errorIn.start(); - final BufferedReader inputStream = new BufferedReader( - new InputStreamReader(process.getInputStream())); + final BufferedReader inputStream = new BufferedReader(new InputStreamReader(process.getInputStream())); Thread infoIn = new Thread("infoIn") { @Override public void run() { @@ -69,8 +68,7 @@ public class ProcessHelper { return new ProcessResult(returnValue, sb.toString(), null); } catch (IOException e) { - throw new RuntimeException("Failed to perform command: " - + e.getLocalizedMessage(), e); + throw new RuntimeException("Failed to perform command: " + e.getLocalizedMessage(), e); } catch (InterruptedException e) { ProcessHelper.logger.error("Interrupted!"); sb.append("[FATAL] Interrupted"); @@ -78,12 +76,10 @@ public class ProcessHelper { } } - public static ProcessResult runCommand(File workingDirectory, - String... commandAndArgs) { + public static ProcessResult runCommand(File workingDirectory, String... commandAndArgs) { if (!workingDirectory.exists()) - throw new RuntimeException("Working directory does not exist at " - + workingDirectory.getAbsolutePath()); + throw new RuntimeException("Working directory does not exist at " + workingDirectory.getAbsolutePath()); if (commandAndArgs == null || commandAndArgs.length == 0) throw new RuntimeException("No command passed!"); @@ -97,8 +93,7 @@ public class ProcessHelper { final Process process = processBuilder.start(); - final BufferedReader errorStream = new BufferedReader( - new InputStreamReader(process.getErrorStream())); + final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); Thread errorIn = new Thread("errorIn") { @Override public void run() { @@ -107,8 +102,7 @@ public class ProcessHelper { }; errorIn.start(); - final BufferedReader inputStream = new BufferedReader( - new InputStreamReader(process.getInputStream())); + final BufferedReader inputStream = new BufferedReader(new InputStreamReader(process.getInputStream())); Thread infoIn = new Thread("infoIn") { @Override public void run() { @@ -126,8 +120,7 @@ public class ProcessHelper { return new ProcessResult(returnValue, sb.toString(), null); } catch (IOException e) { - throw new RuntimeException("Failed to perform command: " - + e.getLocalizedMessage(), e); + throw new RuntimeException("Failed to perform command: " + e.getLocalizedMessage(), e); } catch (InterruptedException e) { ProcessHelper.logger.error("Interrupted!"); sb.append("[FATAL] Interrupted"); @@ -147,16 +140,14 @@ public class ProcessHelper { } } - private static void readStream(StringBuffer sb, String prefix, - BufferedReader bufferedReader) { + private static void readStream(StringBuffer sb, String prefix, BufferedReader bufferedReader) { String line; try { while ((line = bufferedReader.readLine()) != null) { sb.append(prefix + line + "\n"); } } catch (IOException e) { - String msg = "Faild to read from " + prefix + " stream: " - + e.getLocalizedMessage(); + String msg = "Faild to read from " + prefix + " stream: " + e.getLocalizedMessage(); sb.append("[FATAL] " + msg + "\n"); } } @@ -173,11 +164,9 @@ public class ProcessHelper { String pdfFile = pdfPath.getAbsolutePath(); if (pdfFile.charAt(0) == '/') pdfFile = pdfFile.substring(1); - processResult = ProcessHelper.runCommand("rundll32 url.dll,FileProtocolHandler " - + pdfFile); + processResult = ProcessHelper.runCommand("rundll32 url.dll,FileProtocolHandler " + pdfFile); } else { - throw new UnsupportedOperationException("Unexpected OS: " - + SystemHelper.osName); + throw new UnsupportedOperationException("Unexpected OS: " + SystemHelper.osName); } ProcessHelper.logProcessResult(processResult); @@ -187,14 +176,11 @@ public class ProcessHelper { if (processResult.returnValue == 0) { ProcessHelper.logger.info("Process executed successfully"); } else if (processResult.returnValue == -1) { - ProcessHelper.logger.error("Process execution failed:\n" - + processResult.processOutput); - ProcessHelper.logger.error(processResult.t, processResult.t); + ProcessHelper.logger.error("Process execution failed:\n" + processResult.processOutput); + ProcessHelper.logger.error(processResult.t.getMessage(), processResult.t); } else { ProcessHelper.logger.info("Process execution was not successful with return value:" - + processResult.returnValue - + "\n" - + processResult.processOutput); + + processResult.returnValue + "\n" + processResult.processOutput); } } } diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 9ba4b56fb..d64cbee6a 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -1,465 +1,466 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . - * - */ -package ch.eitchnet.utils.helper; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Properties; - -import org.apache.log4j.Logger; - -/** - * A helper class to perform different actions on {@link String}s - * - * @author Robert von Burg - */ -public class StringHelper { - - private static final Logger logger = Logger.getLogger(StringHelper.class); - - /** - * Hex char table for fast calculating of hex value - */ - 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++] = StringHelper.HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = StringHelper.HEX_CHAR_TABLE[v & 0xF]; - } - - return new String(hex, "ASCII"); - - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Something went wrong while converting to HEX: " + e.getLocalizedMessage(), 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."); - - 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 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 StringHelper.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 StringHelper.hash("MD5", bytes); - } - - /** - * 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 StringHelper.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 StringHelper.hash("SHA-1", bytes); - } - - /** - * 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 StringHelper.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 StringHelper.hash("SHA-256", 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) { - throw new RuntimeException("Algorithm " + algorithm + " does not exist!", 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 StringHelper.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) { - - StringHelper.logger.warn("Shortening length of value: " + value); - StringHelper.logger.warn("Length is: " + value.length() + " max: " + length); - - 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 StringHelper.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 alue) { - - // get a copy of the value - String tmpValue = alue; - - // get first occurrence of $ character - int pos = -1; - int stop = 0; - - // loop on $ character positions - while ((pos = tmpValue.indexOf('$', 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) { - StringHelper.logger.error("Sequence starts at offset " + pos + " but does not end!"); - tmpValue = alue; - 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("$") || sequence.contains("{") || sequence.contains("}")) { - StringHelper.logger.error("Enclosed sequence in offsets " + pos + " - " + stop - + " contains one of the illegal chars: $ { }: " + sequence); - tmpValue = alue; - break; - } - - // sequence is good, so see if we have a property for it - String property = properties.getProperty(sequence, ""); - - // 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("${" + sequence + "}", property); - } - - 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) { - StringHelper.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 = StringHelper.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 = StringHelper.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 + ". Showing " + maxContext - + " extra characters and start and end:\n"); - sb.append("context s1: " + s1.substring(start, end) + "\n"); - sb.append("context s2: " + s2.substring(start, end) + "\n"); - - return sb.toString(); - } - - /** - * Formats the given number of milliseconds to a time like 0.000s/ms - * - * @param millis - * the number of milliseconds - * - * @return format the given number of milliseconds to a time like 0.000s/ms - */ - public static String formatMillisecondsDuration(final long millis) { - if (millis > 1000) { - return String.format("%.3fs", (((double) millis) / 1000)); //$NON-NLS-1$ - } - - return millis + "ms"; //$NON-NLS-1$ - } - - /** - * Formats the given number of nanoseconds to a time like 0.000s/ms/us/ns - * - * @param nanos - * the number of nanoseconds - * - * @return format the given number of nanoseconds to a time like 0.000s/ms/us/ns - */ - public static String formatNanoDuration(final long nanos) { - if (nanos > 1000000000) { - return String.format("%.3fs", (((double) nanos) / 1000000000)); //$NON-NLS-1$ - } else if (nanos > 1000000) { - return String.format("%.3fms", (((double) nanos) / 1000000)); //$NON-NLS-1$ - } else if (nanos > 1000) { - return String.format("%.3fus", (((double) nanos) / 1000)); //$NON-NLS-1$ - } else { - return nanos + "ns"; //$NON-NLS-1$ - } - } - - /** - * 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(); - } -} +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.utils + * + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.utils. If not, see . + * + */ +package ch.eitchnet.utils.helper; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A helper class to perform different actions on {@link String}s + * + * @author Robert von Burg + */ +public class StringHelper { + + private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); + + /** + * Hex char table for fast calculating of hex value + */ + 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++] = StringHelper.HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = StringHelper.HEX_CHAR_TABLE[v & 0xF]; + } + + return new String(hex, "ASCII"); + + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Something went wrong while converting to HEX: " + e.getLocalizedMessage(), 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."); + + 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 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 StringHelper.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 StringHelper.hash("MD5", bytes); + } + + /** + * 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 StringHelper.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 StringHelper.hash("SHA-1", bytes); + } + + /** + * 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 StringHelper.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 StringHelper.hash("SHA-256", 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) { + throw new RuntimeException("Algorithm " + algorithm + " does not exist!", 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 StringHelper.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) { + + StringHelper.logger.warn("Shortening length of value: " + value); + StringHelper.logger.warn("Length is: " + value.length() + " max: " + length); + + 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 StringHelper.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 alue) { + + // get a copy of the value + String tmpValue = alue; + + // get first occurrence of $ character + int pos = -1; + int stop = 0; + + // loop on $ character positions + while ((pos = tmpValue.indexOf('$', 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) { + StringHelper.logger.error("Sequence starts at offset " + pos + " but does not end!"); + tmpValue = alue; + 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("$") || sequence.contains("{") || sequence.contains("}")) { + StringHelper.logger.error("Enclosed sequence in offsets " + pos + " - " + stop + + " contains one of the illegal chars: $ { }: " + sequence); + tmpValue = alue; + break; + } + + // sequence is good, so see if we have a property for it + String property = properties.getProperty(sequence, ""); + + // 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("${" + sequence + "}", property); + } + + 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) { + StringHelper.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 = StringHelper.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 = StringHelper.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 + ". Showing " + maxContext + + " extra characters and start and end:\n"); + sb.append("context s1: " + s1.substring(start, end) + "\n"); + sb.append("context s2: " + s2.substring(start, end) + "\n"); + + return sb.toString(); + } + + /** + * Formats the given number of milliseconds to a time like 0.000s/ms + * + * @param millis + * the number of milliseconds + * + * @return format the given number of milliseconds to a time like 0.000s/ms + */ + public static String formatMillisecondsDuration(final long millis) { + if (millis > 1000) { + return String.format("%.3fs", (((double) millis) / 1000)); //$NON-NLS-1$ + } + + return millis + "ms"; //$NON-NLS-1$ + } + + /** + * Formats the given number of nanoseconds to a time like 0.000s/ms/us/ns + * + * @param nanos + * the number of nanoseconds + * + * @return format the given number of nanoseconds to a time like 0.000s/ms/us/ns + */ + public static String formatNanoDuration(final long nanos) { + if (nanos > 1000000000) { + return String.format("%.3fs", (((double) nanos) / 1000000000)); //$NON-NLS-1$ + } else if (nanos > 1000000) { + return String.format("%.3fms", (((double) nanos) / 1000000)); //$NON-NLS-1$ + } else if (nanos > 1000) { + return String.format("%.3fus", (((double) nanos) / 1000)); //$NON-NLS-1$ + } else { + return nanos + "ns"; //$NON-NLS-1$ + } + } + + /** + * 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(); + } +} diff --git a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java index 50fb91628..fbf925bca 100644 --- a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java @@ -19,7 +19,6 @@ */ package ch.eitchnet.utils.helper; - /** * A helper class for {@link System} methods * @@ -74,7 +73,7 @@ public class SystemHelper { sb.append(SystemHelper.javaVersion); return sb.toString(); } - + public static String getUserDir() { return System.getProperty("user.dir"); } @@ -92,7 +91,8 @@ public class SystemHelper { } public static boolean is32bit() { - return SystemHelper.osArch.equals("x86") || SystemHelper.osArch.equals("i386") || SystemHelper.osArch.equals("i686"); + return SystemHelper.osArch.equals("x86") || SystemHelper.osArch.equals("i386") + || SystemHelper.osArch.equals("i686"); } public static boolean is64bit() { @@ -112,7 +112,8 @@ public class SystemHelper { } public static String getMemorySummary() { - return "Memory available " + SystemHelper.getMaxMemory() + " / Used: " + SystemHelper.getUsedMemory() + " / Free:" + SystemHelper.getFreeMemory(); + return "Memory available " + SystemHelper.getMaxMemory() + " / Used: " + SystemHelper.getUsedMemory() + + " / Free:" + SystemHelper.getFreeMemory(); } /** diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java index 2c55d2705..57e6a0132 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -19,7 +19,8 @@ */ package ch.eitchnet.utils.objectfilter; -import org.apache.log4j.Logger; +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 @@ -40,7 +41,7 @@ import org.apache.log4j.Logger; */ public class ObjectCache { - private final static Logger logger = Logger.getLogger(ObjectCache.class); + private final static Logger logger = LoggerFactory.getLogger(ObjectCache.class); /** * id The unique ID of this object in this session @@ -72,8 +73,8 @@ public class ObjectCache { this.operation = operation; if (ObjectCache.logger.isDebugEnabled()) { - ObjectCache.logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation + " / " - + object.toString()); + ObjectCache.logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation + + " / " + object.toString()); } } @@ -96,7 +97,8 @@ public class ObjectCache { */ public void setOperation(Operation newOperation) { if (ObjectCache.logger.isDebugEnabled()) { - ObjectCache.logger.debug("Updating Operation of ID " + this.id + " from " + this.operation + " to " + newOperation); + ObjectCache.logger.debug("Updating Operation of ID " + this.id + " from " + this.operation + " to " + + newOperation); } this.operation = newOperation; } diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index 51733db77..f7b93124b 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -26,7 +26,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; -import org.apache.log4j.Logger; +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 @@ -79,10 +80,10 @@ import org.apache.log4j.Logger; * @param */ public class ObjectFilter { - + // XXX think about removing the generic T, as there is no sense in it - private final static Logger logger = Logger.getLogger(ObjectFilter.class); + private final static Logger logger = LoggerFactory.getLogger(ObjectFilter.class); private HashMap> cache = new HashMap>(); private HashSet keySet = new HashSet(); From 38466685a04097f7f1410a26562fd2b59b5fb150 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 24 Nov 2012 13:23:24 +0100 Subject: [PATCH 099/457] [Major] refactored use of log4j to slf4j --- pom.xml | 22 ++++- .../ch/eitchnet/xmlpers/XmlFilePersister.java | 87 +++++++++-------- .../xmlpers/XmlPersistenceHandler.java | 27 +++--- .../xmlpers/XmlPersistencePathBuilder.java | 23 ++--- .../xmlpers/XmlPersistenceTransaction.java | 69 ++++++------- .../xmlpers/test/XmlPersistenceTest.java | 97 ++++++++++--------- 6 files changed, 171 insertions(+), 154 deletions(-) diff --git a/pom.xml b/pom.xml index 65b28f284..f43055b3e 100644 --- a/pom.xml +++ b/pom.xml @@ -117,16 +117,28 @@ 4.10 test - - log4j - log4j - 1.2.17 - ch.eitchnet ch.eitchnet.utils 0.1.0-SNAPSHOT + + ch.eitchnet + ch.eitchnet.log4j + 0.1.0-SNAPSHOT + test + + + org.slf4j + slf4j-api + 1.7.2 + + + org.slf4j + slf4j-log4j12 + 1.7.2 + test + diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java b/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java index f9d7bc4fd..8049a9946 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java @@ -31,7 +31,8 @@ import java.util.Set; import javax.xml.parsers.DocumentBuilder; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; @@ -50,7 +51,7 @@ public class XmlFilePersister { // private static final String XML_DEFAULT_ENCODING = "UTF-8"; - private static final Logger logger = Logger.getLogger(XmlFilePersister.class); + private static final Logger logger = LoggerFactory.getLogger(XmlFilePersister.class); private boolean verbose; private XmlPersistencePathBuilder xmlPathHelper; @@ -74,9 +75,9 @@ public class XmlFilePersister { File pathF; if (subType != null) - pathF = xmlPathHelper.getPathF(type, subType, id); + pathF = this.xmlPathHelper.getPathF(type, subType, id); else - pathF = xmlPathHelper.getPathF(type, id); + pathF = this.xmlPathHelper.getPathF(type, id); // if this is a new file, then check create parents, if the don't exist if (!pathF.exists()) { @@ -87,15 +88,15 @@ public class XmlFilePersister { } } - if (verbose) - logger.info("Persisting " + type + " / " + subType + " / " + id + " to " + pathF.getAbsolutePath() + "..."); + if (this.verbose) + XmlFilePersister.logger.info("Persisting " + type + " / " + subType + " / " + id + " to " + pathF.getAbsolutePath() + "..."); BufferedOutputStream outStream = null; try { outStream = new BufferedOutputStream(new FileOutputStream(pathF)); - OutputFormat outputFormat = new OutputFormat("XML", XML_DEFAULT_ENCODING, true); + OutputFormat outputFormat = new OutputFormat("XML", XmlFilePersister.XML_DEFAULT_ENCODING, true); outputFormat.setIndent(1); outputFormat.setIndenting(true); //of.setDoctype(null, null); @@ -113,13 +114,13 @@ public class XmlFilePersister { try { outStream.close(); } catch (IOException e) { - logger.error(e, e); + XmlFilePersister.logger.error(e.getMessage(), e); } } } - if (verbose) - logger.info("Done."); + if (this.verbose) + XmlFilePersister.logger.info("Done."); } /** @@ -131,24 +132,24 @@ public class XmlFilePersister { File pathF; if (subType != null) - pathF = xmlPathHelper.getPathF(type, subType, id); + pathF = this.xmlPathHelper.getPathF(type, subType, id); else - pathF = xmlPathHelper.getPathF(type, id); + pathF = this.xmlPathHelper.getPathF(type, id); - if (verbose) - logger.info("Remove persistence file for " + type + " / " + subType + " / " + id + " from " + if (this.verbose) + XmlFilePersister.logger.info("Remove persistence file for " + type + " / " + subType + " / " + id + " from " + pathF.getAbsolutePath() + "..."); if (!pathF.exists()) { - logger.error("Persistence file for " + type + " / " + subType + " / " + id + " does not exist at " + XmlFilePersister.logger.error("Persistence file for " + type + " / " + subType + " / " + id + " does not exist at " + pathF.getAbsolutePath()); } else if (!pathF.delete()) { throw new XmlPersistenceExecption("Could not delete persistence file for " + type + " / " + subType + " / " + id + " at " + pathF.getAbsolutePath()); } - if (verbose) - logger.info("Done."); + if (this.verbose) + XmlFilePersister.logger.info("Done."); } /** @@ -159,22 +160,22 @@ public class XmlFilePersister { File pathF; if (subType == null) - pathF = xmlPathHelper.getPathF(type); + pathF = this.xmlPathHelper.getPathF(type); else - pathF = xmlPathHelper.getPathF(type, subType); + pathF = this.xmlPathHelper.getPathF(type, subType); if (!pathF.exists()) { if (subType == null) - logger.error("Path for " + type + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " at " + pathF.getAbsolutePath() + " does not exist, so removing not possible!"); else - logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + " does not exist, so removing not possible!"); } else { File[] filesToRemove = pathF.listFiles(); - boolean removed = FileHelper.deleteFiles(filesToRemove, verbose); + boolean removed = FileHelper.deleteFiles(filesToRemove, this.verbose); if (!removed) { if (subType == null) @@ -201,7 +202,7 @@ public class XmlFilePersister { File pathF = this.xmlPathHelper.getPathF(type, subType); if (!pathF.exists()) { - logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + " does not exist, so no objects exist!"); return Collections.emptySet(); } @@ -212,8 +213,8 @@ public class XmlFilePersister { keySet.add(name.substring(0, name.length() - XmlPersistencePathBuilder.FILE_EXT.length())); } - if (verbose) - logger.info("Found " + keySet.size() + " elements for " + type + " / " + subType); + if (this.verbose) + XmlFilePersister.logger.info("Found " + keySet.size() + " elements for " + type + " / " + subType); return keySet; } @@ -221,7 +222,7 @@ public class XmlFilePersister { // otherwise we need to iterate any existing subTypes and create a combined key set File pathF = this.xmlPathHelper.getPathF(type); if (!pathF.exists()) { - logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + " does not exist, so no objects exist!"); return Collections.emptySet(); } @@ -232,17 +233,17 @@ public class XmlFilePersister { for (File subTypeFile : subTypeFiles) { if (subTypeFile.isFile()) { - keySet.add(xmlPathHelper.getId(subTypeFile.getName())); + keySet.add(this.xmlPathHelper.getId(subTypeFile.getName())); } else { for (File f : subTypeFile.listFiles()) { - keySet.add(xmlPathHelper.getId(f.getName())); + keySet.add(this.xmlPathHelper.getId(f.getName())); } } } - if (verbose) - logger.info("Found " + keySet.size() + " elements for " + type); + if (this.verbose) + XmlFilePersister.logger.info("Found " + keySet.size() + " elements for " + type); return keySet; } @@ -261,15 +262,15 @@ public class XmlFilePersister { File pathF = this.xmlPathHelper.getPathF(type, subType); if (!pathF.exists()) { - logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + " does not exist, so no objects exist!"); return 0l; } int length = pathF.listFiles().length; - if (verbose) - logger.info("Found " + length + " elements for " + type + " / " + subType); + if (this.verbose) + XmlFilePersister.logger.info("Found " + length + " elements for " + type + " / " + subType); return length; } @@ -279,7 +280,7 @@ public class XmlFilePersister { File pathF = this.xmlPathHelper.getPathF(type); if (!pathF.exists()) { - logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + " does not exist, so no objects exist!"); return 0l; } @@ -296,8 +297,8 @@ public class XmlFilePersister { } } - if (verbose) - logger.info("Found " + numberOfFiles + " elements for " + type); + if (this.verbose) + XmlFilePersister.logger.info("Found " + numberOfFiles + " elements for " + type); return numberOfFiles; } @@ -361,7 +362,7 @@ public class XmlFilePersister { File pathF = this.xmlPathHelper.getPathF(type, subType); if (!pathF.exists()) { - logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + " does not exist, so no objects exist!"); return Collections.emptyList(); } @@ -371,8 +372,8 @@ public class XmlFilePersister { list.add(parseFile(subTypeF, docBuilder)); } - if (verbose) - logger.info("Loaded " + list.size() + " elements for " + type + " / " + subType); + if (this.verbose) + XmlFilePersister.logger.info("Loaded " + list.size() + " elements for " + type + " / " + subType); return list; } @@ -382,7 +383,7 @@ public class XmlFilePersister { File pathF = this.xmlPathHelper.getPathF(type); if (!pathF.exists()) { - logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() + " does not exist, so no objects exist!"); return Collections.emptyList(); } @@ -402,8 +403,8 @@ public class XmlFilePersister { } } - if (verbose) - logger.info("Loaded " + list.size() + " elements for " + type); + if (this.verbose) + XmlFilePersister.logger.info("Loaded " + list.size() + " elements for " + type); return list; } @@ -420,7 +421,7 @@ public class XmlFilePersister { File pathF = this.xmlPathHelper.getPathF(type, subType, id); if (!pathF.exists()) { - logger.error("Path for " + type + " / " + subType + " / " + id + " at " + pathF.getAbsolutePath() + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " / " + id + " at " + pathF.getAbsolutePath() + " does not exist, so object does not exist!"); return null; } diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java index 7382d30c2..c615795ac 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java @@ -19,7 +19,8 @@ */ package ch.eitchnet.xmlpers; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.SystemHelper; import ch.eitchnet.utils.objectfilter.ITransactionObject; @@ -46,7 +47,7 @@ public class XmlPersistenceHandler { */ public static final String CONFIG_DAO_FACTORY_CLASS = "ch.eitchnet.xmlpers.config.daoFactoryClass"; - protected static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); + protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); protected boolean verbose; protected ThreadLocal xmlPersistenceTxThreadLocal; @@ -58,18 +59,18 @@ public class XmlPersistenceHandler { */ public void initialize() { - String basePath = SystemHelper.getProperty(XmlPersistenceHandler.class.getSimpleName(), CONFIG_BASEPATH, null); - verbose = SystemHelper.getPropertyBool(XmlPersistenceHandler.class.getSimpleName(), CONFIG_VERBOSE, + String basePath = SystemHelper.getProperty(XmlPersistenceHandler.class.getSimpleName(), XmlPersistenceHandler.CONFIG_BASEPATH, null); + this.verbose = SystemHelper.getPropertyBool(XmlPersistenceHandler.class.getSimpleName(), XmlPersistenceHandler.CONFIG_VERBOSE, Boolean.FALSE).booleanValue(); // get class to use as transaction String daoFactoryClassName = SystemHelper.getProperty(XmlPersistenceHandler.class.getSimpleName(), - CONFIG_DAO_FACTORY_CLASS, null); + XmlPersistenceHandler.CONFIG_DAO_FACTORY_CLASS, null); try { @SuppressWarnings("unchecked") Class xmlDaoFactoryClass = (Class) Class.forName(daoFactoryClassName); - xmlDaoFactory = xmlDaoFactoryClass.newInstance(); + this.xmlDaoFactory = xmlDaoFactoryClass.newInstance(); } catch (ClassNotFoundException e) { throw new XmlPersistenceExecption("XmlDaoFactory class does not exist " + daoFactoryClassName, e); @@ -78,10 +79,10 @@ public class XmlPersistenceHandler { } XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(basePath); - persister = new XmlFilePersister(pathBuilder, verbose); + this.persister = new XmlFilePersister(pathBuilder, this.verbose); // initialize the Thread local object which is used per transaction - xmlPersistenceTxThreadLocal = new ThreadLocal(); + this.xmlPersistenceTxThreadLocal = new ThreadLocal(); } /** @@ -89,8 +90,8 @@ public class XmlPersistenceHandler { */ public XmlPersistenceTransaction openTx() { - if (verbose) - logger.info("Opening new transaction..."); + if (this.verbose) + XmlPersistenceHandler.logger.info("Opening new transaction..."); // make sure no previous filter exists XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); @@ -100,7 +101,7 @@ public class XmlPersistenceHandler { // set a new persistence transaction object ObjectFilter objectFilter = new ObjectFilter(); xmlPersistenceTx = new XmlPersistenceTransaction(); - xmlPersistenceTx.initialize(persister, xmlDaoFactory, objectFilter, verbose); + xmlPersistenceTx.initialize(this.persister, this.xmlDaoFactory, objectFilter, this.verbose); this.xmlPersistenceTxThreadLocal.set(xmlPersistenceTx); @@ -123,8 +124,8 @@ public class XmlPersistenceHandler { */ public void commitTx() { - if (verbose) - logger.info("Committing transaction..."); + if (this.verbose) + XmlPersistenceHandler.logger.info("Committing transaction..."); try { XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java index 5a5fcf7d8..b92107cfc 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java @@ -22,14 +22,15 @@ package ch.eitchnet.xmlpers; import java.io.File; import java.io.IOException; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @author Robert von Burg * */ public class XmlPersistencePathBuilder { - private static final Logger logger = Logger.getLogger(XmlPersistencePathBuilder.class); + private static final Logger logger = LoggerFactory.getLogger(XmlPersistencePathBuilder.class); /** * @@ -39,7 +40,7 @@ public class XmlPersistencePathBuilder { /** * */ - public static final int EXT_LENGTH = FILE_EXT.length(); + public static final int EXT_LENGTH = XmlPersistencePathBuilder.FILE_EXT.length(); private String basePath; @@ -61,7 +62,7 @@ public class XmlPersistencePathBuilder { throw new XmlPersistenceExecption("Failed to build canonical path from " + basePath, e); } - logger.info("Using base path " + basePath); + XmlPersistencePathBuilder.logger.info("Using base path " + basePath); } /** @@ -69,7 +70,7 @@ public class XmlPersistencePathBuilder { * @return */ public String getFilename(String id) { - return id.concat(FILE_EXT); + return id.concat(XmlPersistencePathBuilder.FILE_EXT); } /** @@ -77,11 +78,11 @@ public class XmlPersistencePathBuilder { * @return */ public String getId(String filename) { - if (filename.charAt(filename.length() - EXT_LENGTH) != '.') + if (filename.charAt(filename.length() - XmlPersistencePathBuilder.EXT_LENGTH) != '.') throw new XmlPersistenceExecption("The filename does not have a . at index " - + (filename.length() - EXT_LENGTH)); + + (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); - return filename.substring(0, filename.length() - EXT_LENGTH); + return filename.substring(0, filename.length() - XmlPersistencePathBuilder.EXT_LENGTH); } /** @@ -91,7 +92,7 @@ public class XmlPersistencePathBuilder { */ public String getPath(String type) { - StringBuilder sb = new StringBuilder(basePath); + StringBuilder sb = new StringBuilder(this.basePath); sb.append("/"); sb.append(type); @@ -114,7 +115,7 @@ public class XmlPersistencePathBuilder { */ public String getPath(String type, String subType) { - StringBuilder sb = new StringBuilder(basePath); + StringBuilder sb = new StringBuilder(this.basePath); sb.append("/"); sb.append(type); sb.append("/"); @@ -140,7 +141,7 @@ public class XmlPersistencePathBuilder { */ public String getPath(String type, String subType, String id) { - StringBuilder sb = new StringBuilder(basePath); + StringBuilder sb = new StringBuilder(this.basePath); sb.append("/"); sb.append(type); sb.append("/"); diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java index e844d3d4e..5e9a2664b 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java @@ -27,7 +27,8 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -41,7 +42,7 @@ import ch.eitchnet.utils.objectfilter.ObjectFilter; */ public class XmlPersistenceTransaction { - private static final Logger logger = Logger.getLogger(XmlPersistenceTransaction.class); + private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTransaction.class); private boolean verbose; private XmlFilePersister persister; @@ -64,23 +65,23 @@ public class XmlPersistenceTransaction { } private DocumentBuilder getDocBuilder() { - if (docBuilder == null) { + if (this.docBuilder == null) { try { - docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + this.docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new XmlPersistenceExecption("Failed to load document builder: " + e.getLocalizedMessage(), e); } } - return docBuilder; + return this.docBuilder; } /** * @return */ protected DOMImplementation getDomImpl() { - if (domImplementation == null) - domImplementation = getDocBuilder().getDOMImplementation(); - return domImplementation; + if (this.domImplementation == null) + this.domImplementation = getDocBuilder().getDOMImplementation(); + return this.domImplementation; } /* @@ -183,7 +184,7 @@ public class XmlPersistenceTransaction { public List queryAll(String type, String subType) { // XXX ok, this is very ugly, but for starters it will have to do - XmlDao dao = xmlDaoFactory.getDao(type); + XmlDao dao = this.xmlDaoFactory.getDao(type); List elements = this.persister.queryAll(type, subType, getDocBuilder()); List objects = new ArrayList(elements.size()); @@ -214,7 +215,7 @@ public class XmlPersistenceTransaction { */ public T queryById(String type, String subType, String id) { - XmlDao dao = xmlDaoFactory.getDao(type); + XmlDao dao = this.xmlDaoFactory.getDao(type); Element element = this.persister.queryById(type, subType, id, getDocBuilder()); if (element == null) @@ -235,24 +236,24 @@ public class XmlPersistenceTransaction { */ void commitTx() { - if (verbose) - logger.info("Committing..."); + if (this.verbose) + XmlPersistenceTransaction.logger.info("Committing..."); - Set keySet = objectFilter.keySet(); + Set keySet = this.objectFilter.keySet(); if (keySet.isEmpty()) return; for (String key : keySet) { - XmlDao dao = xmlDaoFactory.getDao(key); + XmlDao dao = this.xmlDaoFactory.getDao(key); - List removed = objectFilter.getRemoved(key); + List removed = this.objectFilter.getRemoved(key); if (removed.isEmpty()) { - if (verbose) - logger.info("No objects removed in this tx."); + if (this.verbose) + XmlPersistenceTransaction.logger.info("No objects removed in this tx."); } else { - if (verbose) - logger.info(removed.size() + " objects removed in this tx."); + if (this.verbose) + XmlPersistenceTransaction.logger.info(removed.size() + " objects removed in this tx."); for (ITransactionObject object : removed) { @@ -260,17 +261,17 @@ public class XmlPersistenceTransaction { String subType = dao.getSubType(object); String id = dao.getId(object); - persister.remove(type, subType, id); + this.persister.remove(type, subType, id); } } - List updated = objectFilter.getUpdated(key); + List updated = this.objectFilter.getUpdated(key); if (updated.isEmpty()) { - if (verbose) - logger.info("No objects updated in this tx."); + if (this.verbose) + XmlPersistenceTransaction.logger.info("No objects updated in this tx."); } else { - if (verbose) - logger.info(updated.size() + " objects updated in this tx."); + if (this.verbose) + XmlPersistenceTransaction.logger.info(updated.size() + " objects updated in this tx."); for (ITransactionObject object : updated) { @@ -279,17 +280,17 @@ public class XmlPersistenceTransaction { String id = dao.getId(object); Document asDom = dao.serializeToDom(object, getDomImpl()); - persister.saveOrUpdate(type, subType, id, asDom); + this.persister.saveOrUpdate(type, subType, id, asDom); } } - List added = objectFilter.getAdded(key); + List added = this.objectFilter.getAdded(key); if (added.isEmpty()) { - if (verbose) - logger.info("No objects added in this tx."); + if (this.verbose) + XmlPersistenceTransaction.logger.info("No objects added in this tx."); } else { - if (verbose) - logger.info(updated.size() + " objects added in this tx."); + if (this.verbose) + XmlPersistenceTransaction.logger.info(updated.size() + " objects added in this tx."); for (ITransactionObject object : added) { @@ -298,12 +299,12 @@ public class XmlPersistenceTransaction { String id = dao.getId(object); Document asDom = dao.serializeToDom(object, getDomImpl()); - persister.saveOrUpdate(type, subType, id, asDom); + this.persister.saveOrUpdate(type, subType, id, asDom); } } } - objectFilter.clearCache(); - logger.info("Completed TX"); + this.objectFilter.clearCache(); + XmlPersistenceTransaction.logger.info("Completed TX"); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java index 3c0220186..4ac22b6ee 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java @@ -23,7 +23,8 @@ import java.io.File; import java.util.List; import java.util.Set; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -42,7 +43,7 @@ import ch.eitchnet.xmlpers.test.impl.MyDaoFactory; */ public class XmlPersistenceTest { - private static final Logger logger = Logger.getLogger(XmlPersistenceTest.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTest.class.getName()); private static XmlPersistenceHandler persistenceHandler; @@ -67,13 +68,13 @@ public class XmlPersistenceTest { System.setProperty(XmlPersistenceHandler.CONFIG_VERBOSE, "true"); System.setProperty(XmlPersistenceHandler.CONFIG_DAO_FACTORY_CLASS, MyDaoFactory.class.getName()); - persistenceHandler = new XmlPersistenceHandler(); - persistenceHandler.initialize(); + XmlPersistenceTest.persistenceHandler = new XmlPersistenceHandler(); + XmlPersistenceTest.persistenceHandler.initialize(); - logger.info("Initialized persistence handler."); + XmlPersistenceTest.logger.info("Initialized persistence handler."); } catch (Exception e) { - logger.error(e, e); + XmlPersistenceTest.logger.error(e.getMessage(), e); throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); } @@ -86,20 +87,20 @@ public class XmlPersistenceTest { public void testCreate() { try { - logger.info("Trying to create..."); + XmlPersistenceTest.logger.info("Trying to create..."); // new instance MyClass myClass = new MyClass("@id", "@name", "@subtype"); // persist instance - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); tx.add(myClass); - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); - logger.info("Done creating."); + XmlPersistenceTest.logger.info("Done creating."); } catch (Exception e) { - logger.error(e, e); + XmlPersistenceTest.logger.error(e.getMessage(), e); Assert.fail("Failed: " + e.getLocalizedMessage()); } } @@ -111,18 +112,18 @@ public class XmlPersistenceTest { public void testRead() { try { - logger.info("Trying to read..."); + XmlPersistenceTest.logger.info("Trying to read..."); // query MyClass with id @id - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); - logger.info("Found MyClass: " + myClass); - persistenceHandler.commitTx(); + XmlPersistenceTest.logger.info("Found MyClass: " + myClass); + XmlPersistenceTest.persistenceHandler.commitTx(); - logger.info("Done reading."); + XmlPersistenceTest.logger.info("Done reading."); } catch (Exception e) { - logger.error(e, e); + XmlPersistenceTest.logger.error(e.getMessage(), e); Assert.fail("Failed: " + e.getLocalizedMessage()); } } @@ -134,24 +135,24 @@ public class XmlPersistenceTest { public void testUpdate() { try { - logger.info("Trying to update an object..."); + XmlPersistenceTest.logger.info("Trying to update an object..."); // query the instance - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); - logger.info("Found MyClass: " + myClass); + XmlPersistenceTest.logger.info("Found MyClass: " + myClass); // modify the instance myClass.setName("@name_modified"); // update the instance tx.update(myClass); - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); - logger.info("Done updating."); + XmlPersistenceTest.logger.info("Done updating."); } catch (Exception e) { - logger.error(e, e); + XmlPersistenceTest.logger.error(e.getMessage(), e); Assert.fail("Failed: " + e.getLocalizedMessage()); } } @@ -162,17 +163,17 @@ public class XmlPersistenceTest { @Test public void testRemove() { - logger.info("Trying to remove..."); + XmlPersistenceTest.logger.info("Trying to remove..."); // query the instance - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); - logger.info("Found MyClass: " + myClass); + XmlPersistenceTest.logger.info("Found MyClass: " + myClass); tx.remove(myClass); - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); - logger.info("Done removing."); + XmlPersistenceTest.logger.info("Done removing."); } /** @@ -182,13 +183,13 @@ public class XmlPersistenceTest { public void testQueryFail() { try { - logger.info("Trying to query removed object..."); - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTest.logger.info("Trying to query removed object..."); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); - logger.info("Found MyClass: " + myClass); - logger.info("Done querying removed object"); + XmlPersistenceTest.logger.info("Found MyClass: " + myClass); + XmlPersistenceTest.logger.info("Done querying removed object"); } finally { - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); } } @@ -199,20 +200,20 @@ public class XmlPersistenceTest { public void testReCreate() { try { - logger.info("Trying to recreate..."); + XmlPersistenceTest.logger.info("Trying to recreate..."); // new instance MyClass myClass = new MyClass("@id", "@name", "@subtype"); // persist instance - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); tx.add(myClass); - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); - logger.info("Done creating."); + XmlPersistenceTest.logger.info("Done creating."); } catch (Exception e) { - logger.error(e, e); + XmlPersistenceTest.logger.error(e.getMessage(), e); Assert.fail("Failed: " + e.getLocalizedMessage()); } } @@ -233,10 +234,10 @@ public class XmlPersistenceTest { try { - logger.info("Trying to query all..."); + XmlPersistenceTest.logger.info("Trying to query all..."); // query all - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); List list = tx.queryAll(MyClass.class.getName()); Assert.assertTrue("Expected only one object, found " + list.size(), list.size() == 1); @@ -248,10 +249,10 @@ public class XmlPersistenceTest { list = tx.queryAll(MyClass.class.getName(), "@inexistant"); Assert.assertTrue("Expected no objects, found " + list.size(), list.size() == 0); - logger.info("Done querying."); + XmlPersistenceTest.logger.info("Done querying."); } finally { - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); } } @@ -262,7 +263,7 @@ public class XmlPersistenceTest { public void testKeySet() { try { - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); Set keySet = tx.queryKeySet(MyClass.class.getName()); Assert.assertTrue("Expected one key, found " + keySet.size(), keySet.size() == 1); @@ -276,7 +277,7 @@ public class XmlPersistenceTest { Assert.assertTrue("Expected no keys, found " + keySet, keySet.size() == 0); } finally { - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); } } @@ -287,13 +288,13 @@ public class XmlPersistenceTest { public void testRemoveAll() { try { - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); List objects = tx.queryAll(MyClass.class.getName(), "@subType"); tx.removeAll(objects); } finally { - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); } } @@ -304,13 +305,13 @@ public class XmlPersistenceTest { public void testSize() { try { - XmlPersistenceTransaction tx = persistenceHandler.openTx(); + XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); long size = tx.querySize(MyClass.class.getName(), "@subType"); Assert.assertTrue("Expected size = 0, found: " + size, size == 0); } finally { - persistenceHandler.commitTx(); + XmlPersistenceTest.persistenceHandler.commitTx(); } } } From 2b0e23eb285520acf084a90a6cee413bf0ac2e1f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 24 Nov 2012 13:23:49 +0100 Subject: [PATCH 100/457] [Major] refactored use of log4j to slf4j --- pom.xml | 20 +++- .../handler/DefaultEncryptionHandler.java | 5 +- .../handler/DefaultPrivilegeHandler.java | 5 +- .../handler/XmlPersistenceHandler.java | 5 +- .../helper/BootstrapConfigurationHelper.java | 21 ++-- .../helper/InitializationHelper.java | 11 ++- .../eitchnet/privilege/helper/XmlHelper.java | 5 +- .../privilege/test/PrivilegeTest.java | 99 +++++++++++-------- 8 files changed, 102 insertions(+), 69 deletions(-) diff --git a/pom.xml b/pom.xml index 0a189f43f..7605305ef 100644 --- a/pom.xml +++ b/pom.xml @@ -81,11 +81,6 @@ 4.10 test - - log4j - log4j - 1.2.17 - maven dom4j @@ -96,6 +91,21 @@ ch.eitchnet.utils 0.1.0-SNAPSHOT + + ch.eitchnet + ch.eitchnet.log4j + 0.1.0-SNAPSHOT + + + org.slf4j + slf4j-api + 1.7.2 + + + org.slf4j + slf4j-log4j12 + 1.7.2 + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 76d63ce5c..1dbd99a0f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -25,7 +25,8 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.helper.HashHelper; import ch.eitchnet.privilege.helper.XmlConstants; @@ -50,7 +51,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { /** * The log4j logger used in this instance */ - private static final Logger logger = Logger.getLogger(DefaultEncryptionHandler.class); + private static final Logger logger = LoggerFactory.getLogger(DefaultEncryptionHandler.class); /** * The {@link SecureRandom} which is used to create new tokens diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index cf87c63b8..9cc7e9127 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -28,7 +28,8 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; @@ -69,7 +70,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * log4j logger */ - protected static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); + protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); /** * last assigned id for the {@link Session}s diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 0dd164e7d..2d5fc12b8 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -30,9 +30,10 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import org.apache.log4j.Logger; import org.dom4j.DocumentFactory; import org.dom4j.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; @@ -50,7 +51,7 @@ import ch.eitchnet.privilege.model.internal.User; */ public class XmlPersistenceHandler implements PersistenceHandler { - protected static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); + protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); private Map userMap; private Map roleMap; diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index f24577af0..3412f24e7 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -21,16 +21,12 @@ package ch.eitchnet.privilege.helper; import java.io.File; -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.ConsoleAppender; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PatternLayout; import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.utils.helper.Log4jConfigurator; /** *

    @@ -51,7 +47,7 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; */ public class BootstrapConfigurationHelper { - // private static final Logger logger = Logger.getLogger(BootstrapConfigurationHelper.class); + // private static final Logger logger = LoggerFactory.getLogger(BootstrapConfigurationHelper.class); private static String path; @@ -70,9 +66,7 @@ public class BootstrapConfigurationHelper { * the args from the command line */ public static void main(String[] args) { - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); - Logger.getRootLogger().setLevel(Level.INFO); + Log4jConfigurator.configure(); // get current directory BootstrapConfigurationHelper.path = System.getProperty("user.dir") + "/newConfig"; @@ -133,7 +127,8 @@ public class BootstrapConfigurationHelper { // create PersistenceHandler Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); containerElement.add(persistenceHandlerElem); - persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, BootstrapConfigurationHelper.defaultPersistenceHandler); + persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, + BootstrapConfigurationHelper.defaultPersistenceHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); persistenceHandlerElem.add(parametersElement); // Parameter basePath @@ -150,7 +145,8 @@ public class BootstrapConfigurationHelper { // create EncryptionHandler Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); containerElement.add(encryptionHandlerElem); - encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, BootstrapConfigurationHelper.defaultEncryptionHandler); + encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, + BootstrapConfigurationHelper.defaultEncryptionHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); encryptionHandlerElem.add(parametersElement); // Parameter hashAlgorithm @@ -160,7 +156,8 @@ public class BootstrapConfigurationHelper { parametersElement.add(parameterElement); // write the container file to disk - File privilegeContainerFile = new File(BootstrapConfigurationHelper.path + "/" + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); + File privilegeContainerFile = new File(BootstrapConfigurationHelper.path + "/" + + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); XmlHelper.writeDocument(doc, privilegeContainerFile); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java index 0a9164091..94ff3d9d2 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -25,8 +25,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.log4j.Logger; import org.dom4j.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; import ch.eitchnet.privilege.handler.EncryptionHandler; @@ -44,7 +45,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class InitializationHelper { - private static final Logger logger = Logger.getLogger(InitializationHelper.class); + private static final Logger logger = LoggerFactory.getLogger(InitializationHelper.class); /** * Initializes the {@link DefaultPrivilegeHandler} from the configuration file @@ -92,7 +93,7 @@ public class InitializationHelper { encryptionHandler.initialize(parameterMap); } catch (Exception e) { - InitializationHelper.logger.error(e, e); + InitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + " could not be initialized"); } @@ -107,7 +108,7 @@ public class InitializationHelper { persistenceHandler.initialize(parameterMap); } catch (Exception e) { - InitializationHelper.logger.error(e, e); + InitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + " could not be initialized"); } @@ -122,7 +123,7 @@ public class InitializationHelper { privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { - InitializationHelper.logger.error(e, e); + InitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + " could not be initialized"); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index ea86556b7..eec3c9eb2 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -27,7 +27,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentFactory; @@ -35,6 +34,8 @@ import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -50,7 +51,7 @@ public class XmlHelper { */ public static final String DEFAULT_ENCODING = "UTF-8"; - private static final Logger logger = Logger.getLogger(XmlHelper.class); + private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); /** * Parses an XML file on the file system using dom4j and returns the resulting {@link Document} object diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 778ac48fd..2fe79e34e 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -24,13 +24,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.ConsoleAppender; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PatternLayout; import org.junit.BeforeClass; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.CertificateThreadLocal; @@ -46,6 +43,7 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.test.model.TestRestrictable; import ch.eitchnet.privilege.test.model.TestSystemUserAction; import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; +import ch.eitchnet.utils.helper.Log4jConfigurator; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more @@ -67,7 +65,7 @@ public class PrivilegeTest { private static final byte[] PASS_BAD = "123".getBytes(); private static final byte[] PASS_TED = "12345".getBytes(); - private static final Logger logger = Logger.getLogger(PrivilegeTest.class); + private static final Logger logger = LoggerFactory.getLogger(PrivilegeTest.class); private static PrivilegeHandler privilegeHandler; @@ -80,16 +78,14 @@ public class PrivilegeTest { try { // set up log4j - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); - Logger.getRootLogger().setLevel(Level.INFO); + Log4jConfigurator.configure(); // initialize container String pwd = System.getProperty("user.dir"); File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); } catch (Exception e) { - PrivilegeTest.logger.error(e, e); + PrivilegeTest.logger.error(e.getMessage(), e); throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); } @@ -102,7 +98,8 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -120,7 +117,8 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_BAD)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_BAD)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -144,16 +142,18 @@ public class PrivilegeTest { @Test public void testAddUserBobAsAdmin() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, - new HashMap()); + UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), + null, new HashMap()); PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); // set bob's password - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); PrivilegeTest.logger.info("Set Bob's password"); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -166,7 +166,8 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -176,7 +177,8 @@ public class PrivilegeTest { */ @Test public void testEnableUserBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -190,7 +192,8 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -201,7 +204,8 @@ public class PrivilegeTest { */ @Test public void testAddRole() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); @@ -216,7 +220,8 @@ public class PrivilegeTest { */ @Test public void testAddRoleToBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -227,7 +232,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -241,12 +247,13 @@ public class PrivilegeTest { public void testFailAddUserTedAsBob() throws Exception { // auth as Bog - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted - UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, - new HashMap()); + UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, + new HashSet(), null, new HashMap()); PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); PrivilegeTest.privilegeHandler.invalidateSession(certificate); @@ -259,8 +266,10 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -272,7 +281,8 @@ public class PrivilegeTest { @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted @@ -293,11 +303,13 @@ public class PrivilegeTest { @Test public void testSetTedPwdAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set ted's password to default - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_DEF)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -308,8 +320,10 @@ public class PrivilegeTest { */ @Test public void testTedChangesOwnPwd() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_TED)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -319,7 +333,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsTed() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_TED)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -330,7 +345,8 @@ public class PrivilegeTest { @Test public void testPerformRestrictableAsAdmin() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable @@ -347,7 +363,8 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailPerformRestrictableAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable @@ -366,7 +383,8 @@ public class PrivilegeTest { @Test public void testAddAppRoleToBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); PrivilegeTest.privilegeHandler.invalidateSession(certificate); @@ -380,7 +398,8 @@ public class PrivilegeTest { */ @Test public void testPerformRestrictableAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable @@ -433,13 +452,15 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testLoginSystemUser() throws Exception { - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.SYSTEM_USER_ADMIN, PrivilegeTest.SYSTEM_USER_ADMIN.getBytes()); + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.SYSTEM_USER_ADMIN, + PrivilegeTest.SYSTEM_USER_ADMIN.getBytes()); } @Test public void testCertificateThreadLocal() { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set certificate into thread local From eac0782810c43fb89f41ecf1be2747e7ee433c8e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 25 Nov 2012 00:44:56 +0100 Subject: [PATCH 101/457] [Minor] Removed dependency to ch.eitchnet.log4j and thus added log4j.xml The log4j.xml configuration file is in the test resources and now the project is completely free of a dependency to a concrete logging implementation as all logging of the sources is done over slf4j --- pom.xml | 6 +--- .../helper/BootstrapConfigurationHelper.java | 2 -- .../privilege/test/PrivilegeTest.java | 5 ---- src/test/resources/log4j.xml | 29 +++++++++++++++++++ 4 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/log4j.xml diff --git a/pom.xml b/pom.xml index 7605305ef..875dcab4f 100644 --- a/pom.xml +++ b/pom.xml @@ -91,11 +91,6 @@ ch.eitchnet.utils 0.1.0-SNAPSHOT - - ch.eitchnet - ch.eitchnet.log4j - 0.1.0-SNAPSHOT - org.slf4j slf4j-api @@ -105,6 +100,7 @@ org.slf4j slf4j-log4j12 1.7.2 + test diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 3412f24e7..9f9bde839 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -26,7 +26,6 @@ import org.dom4j.DocumentFactory; import org.dom4j.Element; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.utils.helper.Log4jConfigurator; /** *

    @@ -66,7 +65,6 @@ public class BootstrapConfigurationHelper { * the args from the command line */ public static void main(String[] args) { - Log4jConfigurator.configure(); // get current directory BootstrapConfigurationHelper.path = System.getProperty("user.dir") + "/newConfig"; diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 2fe79e34e..11837917d 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -43,7 +43,6 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.test.model.TestRestrictable; import ch.eitchnet.privilege.test.model.TestSystemUserAction; import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; -import ch.eitchnet.utils.helper.Log4jConfigurator; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more @@ -75,11 +74,7 @@ public class PrivilegeTest { */ @BeforeClass public static void init() throws Exception { - try { - // set up log4j - Log4jConfigurator.configure(); - // initialize container String pwd = System.getProperty("user.dir"); File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml new file mode 100644 index 000000000..a35a3c351 --- /dev/null +++ b/src/test/resources/log4j.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5e56bf28d4d9c56a427dc75751a56f5c9e0ac125 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 25 Nov 2012 00:45:47 +0100 Subject: [PATCH 102/457] [Minor] Removed dependency to ch.eitchnet.log4j and thus added log4j.xml The log4j.xml configuration file is in the test resources and now the project is completely free of a dependency to a concrete logging implementation as all logging of the sources is done over slf4j --- src/test/resources/log4j.xml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/test/resources/log4j.xml diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml new file mode 100644 index 000000000..a35a3c351 --- /dev/null +++ b/src/test/resources/log4j.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e5c23a89241bb869e8e01a665467752ea3494e24 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 25 Nov 2012 00:46:19 +0100 Subject: [PATCH 103/457] [Minor] Removed dependency to ch.eitchnet.log4j and thus added log4j.xml The log4j.xml configuration file is in the test resources and now the project is completely free of a dependency to a concrete logging implementation as all logging of the sources is done over slf4j --- pom.xml | 6 ---- src/main/resources/log4j.properties | 12 -------- .../xmlpers/test/XmlPersistenceTest.java | 9 ++---- src/test/resources/log4j.xml | 29 +++++++++++++++++++ 4 files changed, 31 insertions(+), 25 deletions(-) delete mode 100644 src/main/resources/log4j.properties create mode 100644 src/test/resources/log4j.xml diff --git a/pom.xml b/pom.xml index f43055b3e..0cf9eb662 100644 --- a/pom.xml +++ b/pom.xml @@ -122,12 +122,6 @@ ch.eitchnet.utils 0.1.0-SNAPSHOT - - ch.eitchnet - ch.eitchnet.log4j - 0.1.0-SNAPSHOT - test - org.slf4j slf4j-api diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties deleted file mode 100644 index 6aa19bb24..000000000 --- a/src/main/resources/log4j.properties +++ /dev/null @@ -1,12 +0,0 @@ -log4j.rootLogger = info, stdout - -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] %C{1} %M - %m%n - -#log4j.appender.file=org.apache.log4j.RollingFileAppender -#log4j.appender.file.File=${user.dir}/logs/app.log -#log4j.appender.file.MaxFileSize=10000KB -#log4j.appender.file.MaxBackupIndex=0 -#log4j.appender.file.layout=org.apache.log4j.PatternLayout -#log4j.appender.file.layout.ConversionPattern=%d %5p [%t] %C{1} %M - %m%n diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java index 4ac22b6ee..6e3f4912a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java @@ -23,13 +23,12 @@ import java.io.File; import java.util.List; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.Log4jConfigurator; import ch.eitchnet.utils.objectfilter.ITransactionObject; import ch.eitchnet.xmlpers.XmlPersistenceExecption; import ch.eitchnet.xmlpers.XmlPersistenceHandler; @@ -53,11 +52,7 @@ public class XmlPersistenceTest { */ @BeforeClass public static void init() throws Exception { - try { - // set up log4j - Log4jConfigurator.configure(); - String userDir = System.getProperty("user.dir"); String basePath = userDir + "/tmp/testdb"; File basePathF = new File(basePath); diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml new file mode 100644 index 000000000..a35a3c351 --- /dev/null +++ b/src/test/resources/log4j.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f73d829822c8240870f43e8672787c8768913e9c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Dec 2012 00:04:45 +0100 Subject: [PATCH 104/457] [Minor] moved exceptions to base package Exceptions were previously in package i18n, this didn't make sense, so they were move to base --- .../{i18n => base}/AccessDeniedException.java | 2 +- .../{i18n => base}/PrivilegeException.java | 2 +- .../handler/DefaultEncryptionHandler.java | 2 +- .../handler/DefaultPrivilegeHandler.java | 13 ++++--- .../privilege/handler/PersistenceHandler.java | 2 +- .../privilege/handler/PrivilegeHandler.java | 6 ++-- .../handler/XmlPersistenceHandler.java | 36 ++++++++++--------- .../privilege/helper/ClassHelper.java | 2 +- .../helper/InitializationHelper.java | 2 +- .../eitchnet/privilege/helper/XmlHelper.java | 2 +- .../eitchnet/privilege/model/Certificate.java | 2 +- .../privilege/model/PrivilegeRep.java | 2 +- .../privilege/model/internal/Privilege.java | 2 +- .../privilege/model/internal/Role.java | 2 +- .../privilege/model/internal/Session.java | 2 +- .../privilege/model/internal/User.java | 25 ++++++++----- .../privilege/policy/DefaultPrivilege.java | 4 +-- .../privilege/policy/PrivilegePolicy.java | 2 +- 18 files changed, 61 insertions(+), 49 deletions(-) rename src/main/java/ch/eitchnet/privilege/{i18n => base}/AccessDeniedException.java (96%) rename src/main/java/ch/eitchnet/privilege/{i18n => base}/PrivilegeException.java (97%) diff --git a/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java similarity index 96% rename from src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java rename to src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java index 818335103..0c8fa5283 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java @@ -17,7 +17,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.privilege.i18n; +package ch.eitchnet.privilege.base; /** * Exception thrown if access is denied during login, or if a certain privilege is not granted diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java similarity index 97% rename from src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java rename to src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java index 0f9fe5111..dc254418a 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java @@ -17,7 +17,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.privilege.i18n; +package ch.eitchnet.privilege.base; /** * Main {@link RuntimeException} thrown if something goes wrong in Privilege diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 1dbd99a0f..fb614673e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -28,9 +28,9 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.HashHelper; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.i18n.PrivilegeException; /** *

    diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 9cc7e9127..a152e8cbd 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -31,9 +31,9 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; @@ -718,7 +718,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + session); } catch (RuntimeException e) { - DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); + DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " + + e.getLocalizedMessage()); throw e; } finally { clearPassword(password); @@ -786,7 +787,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role role = this.persistenceHandler.getRole(roleName); if (role == null) { - DefaultPrivilegeHandler.logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + DefaultPrivilegeHandler.logger.error("No role is defined with name " + roleName + + " which is configured for user " + user); continue; } @@ -1188,7 +1190,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.sessionMap.put(sessionId, new CertificateSessionPair(session, systemUserCertificate)); // log - DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " + session); + DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " + + session); return systemUserCertificate; } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index bba8f20c0..498f6e8e7 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -118,7 +118,7 @@ public interface PersistenceHandler { /** * Informs this {@link PersistenceHandler} to persist any changes which need to be saved * - * @return true if changes were persisted successfully, false if something went wrong + * @return true if changes were persisted successfully, false if nothing needed to be persisted */ public boolean persist(); diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 4f2c54b2a..7889905e7 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -22,8 +22,8 @@ package ch.eitchnet.privilege.handler; import java.util.List; import java.util.Locale; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; @@ -401,7 +401,7 @@ public interface PrivilegeHandler { *

    * *

    - * If the user is not the administrator, then a {@link ch.eitchnet.privilege.i18n.PrivilegeException} is thrown + * If the user is not the administrator, then a {@link ch.eitchnet.privilege.base.PrivilegeException} is thrown *

    * * @param certificate diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 2d5fc12b8..6ea6fda2e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -35,9 +35,9 @@ import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -150,11 +150,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); } + // get model file File modelFile = new File(this.modelPath); boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; - if (!(modelFileUnchanged && this.roleMapDirty && this.userMapDirty)) { - XmlPersistenceHandler.logger.warn("Not persisting as current file is unchanged and model data is not dirty"); + if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { + XmlPersistenceHandler.logger + .warn("Not persisting as current file is unchanged and model data is not dirty"); return false; } @@ -181,19 +183,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { } rootElement.add(rolesElement); - // reset dirty states and return if something was dirty, false otherwise - if (this.userMapDirty || this.roleMapDirty) { - this.userMapDirty = false; - this.roleMapDirty = false; - - return true; - - } + // now write the file + XmlHelper.writeElement(rootElement, modelFile); + // reset dirty states this.userMapDirty = false; this.roleMapDirty = false; - return false; + return true; } /** @@ -259,10 +256,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { * @see ch.eitchnet.privilege.handler.PersistenceHandler#initialize(java.util.Map) */ @Override - public void initialize(Map parameterMap) { + public void initialize(Map paramsMap) { + + // copy parameter map + this.parameterMap = Collections.unmodifiableMap(new HashMap(paramsMap)); // get and validate base bath - String basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + String basePath = this.parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); File basePathF = new File(basePath); if (!basePathF.exists() && !basePathF.isDirectory()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " @@ -270,7 +270,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get model file name - String modelFileName = parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); if (modelFileName == null || modelFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); @@ -321,9 +321,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { for (Element roleElement : rolesElementList) { String roleName = roleElement.getTextTrim(); if (roleName.isEmpty()) { - XmlPersistenceHandler.logger.error("User " + username + " has a role defined with no name, Skipped."); + XmlPersistenceHandler.logger.error("User " + username + + " has a role defined with no name, Skipped."); } else if (!this.roleMap.containsKey(roleName)) { - XmlPersistenceHandler.logger.error("User " + username + " has a inexistant role " + roleName + ", Skipped."); + XmlPersistenceHandler.logger.error("User " + username + " has a inexistant role " + roleName + + ", Skipped."); } else { roles.add(roleName); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java index 41b10b775..5852805d8 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java @@ -19,7 +19,7 @@ */ package ch.eitchnet.privilege.helper; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.PrivilegeException; /** * The {@link ClassHelper} class is a helper to instantiate classes using reflection diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java index 94ff3d9d2..401cdb5fd 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -29,11 +29,11 @@ import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; import ch.eitchnet.privilege.handler.EncryptionHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.policy.PrivilegePolicy; import ch.eitchnet.utils.helper.StringHelper; diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index eec3c9eb2..900c8ca87 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -37,7 +37,7 @@ import org.dom4j.io.XMLWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.PrivilegeException; /** * Helper class for performing XML based tasks using Dom4J diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 87d30a3da..0d165e57c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -23,8 +23,8 @@ import java.io.Serializable; import java.util.Locale; import java.util.Map; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.internal.Session; /** diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index e13d6a1f4..ca3b596d9 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -22,8 +22,8 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Set; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.policy.PrivilegePolicy; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java index c62456fbd..55c4fe1a5 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java @@ -23,8 +23,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.policy.PrivilegePolicy; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index ab929ffde..d6825717b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.RoleRep; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java index 7e3bb8614..4abcd08d5 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java @@ -19,8 +19,8 @@ */ package ch.eitchnet.privilege.model.internal; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 9e12026ac..0405ecec3 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -26,7 +26,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -91,9 +91,6 @@ public final class User { if (username == null || username.isEmpty()) { throw new PrivilegeException("No username defined!"); } - - // password may be null, meaning not able to login - if (firstname == null || firstname.isEmpty()) { throw new PrivilegeException("No firstname defined!"); } @@ -104,9 +101,10 @@ public final class User { throw new PrivilegeException("No userState defined!"); } + // password may be null, meaning not able to login // roles may be null, meaning not able to login and must be added later - - // local may be null, meaning use system default + // locale may be null, meaning use system default + // properties may be null, meaning no properties this.userId = userId; @@ -117,11 +115,20 @@ public final class User { this.firstname = firstname; this.surname = surname; - this.roles = Collections.unmodifiableSet(roles); + if (roles == null) + this.roles = Collections.emptySet(); + else + this.roles = Collections.unmodifiableSet(roles); - this.locale = locale; + if (locale == null) + this.locale = Locale.getDefault(); + else + this.locale = locale; - this.propertyMap = Collections.unmodifiableMap(propertyMap); + if (propertyMap == null) + this.propertyMap = Collections.emptyMap(); + else + this.propertyMap = Collections.unmodifiableMap(propertyMap); } /** diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index ebb061656..3db6be69b 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -19,8 +19,8 @@ */ package ch.eitchnet.privilege.policy; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 50fc79837..62041e636 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -19,7 +19,7 @@ */ package ch.eitchnet.privilege.policy; -import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; From 28a60b52f7ea4bad1f951fcb37a9ca296a7cdfba Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Dec 2012 00:06:54 +0100 Subject: [PATCH 105/457] [New] implemented auto persist on password change Through configuration option it is now possible to enable automatic persisting after password change, no matter who the user is. --- config/Privilege.xml | 3 ++- .../handler/DefaultPrivilegeHandler.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/config/Privilege.xml b/config/Privilege.xml index 2cfaf41e1..b37070a5a 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -28,6 +28,7 @@ along with Privilege. If not, see . + @@ -38,7 +39,7 @@ along with Privilege. If not, see . - + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index a152e8cbd..4c45f060f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -67,6 +67,11 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public class DefaultPrivilegeHandler implements PrivilegeHandler { + /** + * configuration parameter to define automatic persisting on password change + */ + private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; + /** * log4j logger */ @@ -107,6 +112,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private boolean initialized; + /** + * flag to define if a persist should be performed after a user changes their password + */ + private boolean autoPersistOnPasswordChange; + /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) */ @@ -624,6 +634,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); + // perform automatic persisting, if enabled + if (this.autoPersistOnPasswordChange) { + this.persistenceHandler.persist(); + } + } finally { clearPassword(password); } @@ -997,6 +1012,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; + String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE); + if (autoPersistS == null || autoPersistS.equals(Boolean.FALSE.toString())) { + this.autoPersistOnPasswordChange = false; + } else if (autoPersistS.equals(Boolean.TRUE.toString())) { + this.autoPersistOnPasswordChange = true; + logger.info("Enabling automatic persistence on password change."); + } else { + logger.error("Parameter " + PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE + " has illegal value " + autoPersistS + + ". Overriding with " + Boolean.FALSE.toString()); + } + // validate policies on privileges of Roles for (Role role : persistenceHandler.getAllRoles()) { validatePolicies(role); From e0f0b67f605d1e49b0b408643a7c7d3f0e693127 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Dec 2012 00:08:09 +0100 Subject: [PATCH 106/457] [Bugfix] fixed failing tests The tests did not work because of some previous refactoring or some other weirdness. Solved this issue now by initializing better the persistence handler for testing --- .../privilege/test/PrivilegeTest.java | 84 ++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 11837917d..5357faaa7 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -24,16 +24,19 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import junit.framework.Assert; + +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.CertificateThreadLocal; import ch.eitchnet.privilege.helper.InitializationHelper; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; @@ -43,10 +46,12 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.test.model.TestRestrictable; import ch.eitchnet.privilege.test.model.TestSystemUserAction; import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; +import ch.eitchnet.utils.helper.FileHelper; /** - * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more - * tests, especially with deny and allow lists + * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum.br /> + * + * TODO add more tests, especially with deny and allow lists * * @author Robert von Burg */ @@ -75,10 +80,25 @@ public class PrivilegeTest { @BeforeClass public static void init() throws Exception { try { - // initialize container + // copy configuration to tmp String pwd = System.getProperty("user.dir"); - File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); - PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); + + File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModel.xml"); + File tmpPrivilegeModelFile = new File(pwd + "/target/test/PrivilegeModel.xml"); + if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { + throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + + tmpPrivilegeModelFile.getAbsolutePath()); + } + + File parentFile = tmpPrivilegeModelFile.getParentFile(); + if (!parentFile.exists()) { + if (!parentFile.mkdirs()) + throw new RuntimeException("Could not create parent for tmp " + tmpPrivilegeModelFile); + } + + if (!FileHelper.copy(origPrivilegeModelFile, tmpPrivilegeModelFile, true)) + throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); + } catch (Exception e) { PrivilegeTest.logger.error(e.getMessage(), e); @@ -86,6 +106,24 @@ public class PrivilegeTest { } } + @Before + public void setup() throws Exception { + try { + + String pwd = System.getProperty("user.dir"); + + File privilegeConfigFile = new File(pwd + "/config/Privilege.xml"); + + // initialize privilege + PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeConfigFile); + + } catch (Exception e) { + PrivilegeTest.logger.error(e.getMessage(), e); + + throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); + } + } + /** * @throws Exception * if something goes wrong @@ -95,7 +133,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -114,7 +152,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_BAD)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -126,7 +164,7 @@ public class PrivilegeTest { public void testFailAuthenticationPWNull() throws Exception { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, null); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -150,6 +188,7 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); PrivilegeTest.logger.info("Set Bob's password"); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -175,6 +214,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -189,7 +229,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -206,6 +246,7 @@ public class PrivilegeTest { RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -218,6 +259,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -244,7 +286,7 @@ public class PrivilegeTest { // auth as Bog Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, @@ -266,6 +308,7 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -278,7 +321,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted HashSet roles = new HashSet(); @@ -287,7 +330,7 @@ public class PrivilegeTest { new HashMap()); PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -300,12 +343,12 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // set ted's password to default PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); - + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -342,7 +385,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); @@ -360,7 +403,7 @@ public class PrivilegeTest { public void testFailPerformRestrictableAsBob() throws Exception { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); @@ -382,6 +425,7 @@ public class PrivilegeTest { copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -395,7 +439,7 @@ public class PrivilegeTest { public void testPerformRestrictableAsBob() throws Exception { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); @@ -456,7 +500,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // set certificate into thread local CertificateThreadLocal.getInstance().set(certificate); From 717e2de556ae32c8baf09683f2fd7abc452ec4f8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Dec 2012 00:12:00 +0100 Subject: [PATCH 107/457] [Minor] cleaned up test tests left some state files, which are now cleaned up as well --- .../privilege/test/PrivilegeTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 5357faaa7..47e2c69e7 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -26,6 +26,7 @@ import java.util.Map; import junit.framework.Assert; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -106,6 +107,25 @@ public class PrivilegeTest { } } + @AfterClass + public static void destroy() throws Exception { + + // delete temporary file + String pwd = System.getProperty("user.dir"); + + File tmpPrivilegeModelFile = new File(pwd + "/target/test/PrivilegeModel.xml"); + if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { + throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + + tmpPrivilegeModelFile.getAbsolutePath()); + } + + // and temporary parent + File parentFile = tmpPrivilegeModelFile.getParentFile(); + if (parentFile.exists() && !parentFile.delete()) { + throw new RuntimeException("Could not remove temporary parent for tmp " + tmpPrivilegeModelFile); + } + } + @Before public void setup() throws Exception { try { From 9970055cf945ae1220b6223b2ff475def0c43510 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 4 Dec 2012 20:30:04 +0100 Subject: [PATCH 108/457] [New] added preliminary XML schemas for XML files --- src/test/resources/Privilege.xsd | 88 ++++++++++++++++++++++ src/test/resources/PrivilegeModel.xsd | 103 ++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 src/test/resources/Privilege.xsd create mode 100644 src/test/resources/PrivilegeModel.xsd diff --git a/src/test/resources/Privilege.xsd b/src/test/resources/Privilege.xsd new file mode 100644 index 000000000..0f9baa712 --- /dev/null +++ b/src/test/resources/Privilege.xsd @@ -0,0 +1,88 @@ + + + + + +Copyright (c) 2010, 2011 + +Robert von Burg <eitch@eitchnet.ch> + +This file is part of Privilege. + +Privilege is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Privilege 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with Privilege. If not, see <http://www.gnu.org/licenses/>. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/PrivilegeModel.xsd b/src/test/resources/PrivilegeModel.xsd new file mode 100644 index 000000000..cab4d1d3b --- /dev/null +++ b/src/test/resources/PrivilegeModel.xsd @@ -0,0 +1,103 @@ + + + + + +Copyright (c) 2010, 2011 + +Robert von Burg <eitch@eitchnet.ch> + +This file is part of Privilege. + +Privilege is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Privilege 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with Privilege. If not, see <http://www.gnu.org/licenses/>. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e76d9f91214d75fab1dba5cd6f50a17c29afce04 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 21:26:18 +0100 Subject: [PATCH 109/457] Implemented XML parsing and writing by using basic Java implementation --- config/PrivilegeModel.xml | 7 + pom.xml | 5 - .../handler/XmlPersistenceHandler.java | 495 +++--------------- .../helper/BootstrapConfigurationHelper.java | 102 +--- .../helper/InitializationHelper.java | 204 -------- .../eitchnet/privilege/helper/XmlHelper.java | 135 +++-- .../internal/PrivilegeContainerModel.java | 167 ++++++ .../eitchnet/privilege/xml/ElementParser.java | 36 ++ .../privilege/xml/ElementParserAdapter.java | 48 ++ .../privilege/xml/InitializationHelper.java | 106 ++++ .../xml/PrivilegeConfigDomWriter.java | 121 +++++ .../xml/PrivilegeConfigSaxReader.java | 183 +++++++ .../xml/PrivilegeModelDomWriter.java | 160 ++++++ .../xml/PrivilegeModelSaxReader.java | 347 ++++++++++++ .../privilege/test/PrivilegeTest.java | 2 +- .../ch/eitchnet/privilege/test/XmlTest.java | 219 ++++++++ 16 files changed, 1580 insertions(+), 757 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java create mode 100644 src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/ElementParser.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/XmlTest.java diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 396878bc4..4c788b66e 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -71,6 +71,13 @@ along with Privilege. If not, see . true + + + + hello + goodbye + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 875dcab4f..2776e6b91 100644 --- a/pom.xml +++ b/pom.xml @@ -81,11 +81,6 @@ 4.10 test - - maven - dom4j - 1.7-20060614 - ch.eitchnet ch.eitchnet.utils diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 6ea6fda2e..60d5828c7 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -20,28 +20,22 @@ package ch.eitchnet.privilege.handler; import java.io.File; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Set; -import org.dom4j.DocumentFactory; -import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed @@ -138,120 +132,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.roleMapDirty = true; } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() - */ - @Override - public boolean persist() { - - // get models file name - String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); - if (modelFileName == null || modelFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); - } - - // get model file - File modelFile = new File(this.modelPath); - boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; - if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { - XmlPersistenceHandler.logger - .warn("Not persisting as current file is unchanged and model data is not dirty"); - return false; - } - - DocumentFactory docFactory = DocumentFactory.getInstance(); - - // create root element - Element rootElement = docFactory.createElement(XmlConstants.XML_ROOT_PRIVILEGE_USERS_AND_ROLES); - - // USERS - // build XML DOM of users - List users = XmlPersistenceHandler.toDomUsers(this.userMap); - Element usersElement = docFactory.createElement(XmlConstants.XML_USERS); - for (Element userElement : users) { - usersElement.add(userElement); - } - rootElement.add(usersElement); - - // ROLES - // build XML DOM of roles - List roles = XmlPersistenceHandler.toDomRoles(this.roleMap); - Element rolesElement = docFactory.createElement(XmlConstants.XML_ROLES); - for (Element roleElement : roles) { - rolesElement.add(roleElement); - } - rootElement.add(rolesElement); - - // now write the file - XmlHelper.writeElement(rootElement, modelFile); - - // reset dirty states - this.userMapDirty = false; - this.roleMapDirty = false; - - return true; - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#reload() - */ - @Override - public boolean reload() { - - // validate file exists - File modelsFile = new File(this.modelPath); - if (!modelsFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " - + modelsFile.getAbsolutePath()); - } - - this.roleMap = Collections.synchronizedMap(new HashMap()); - this.userMap = Collections.synchronizedMap(new HashMap()); - - // parse models xml file to XML document - Element modelsRootElement = XmlHelper.parseDocument(modelsFile).getRootElement(); - this.modelsFileDate = modelsFile.lastModified(); - - // ROLES - // get roles element - Element rolesElement = modelsRootElement.element(XmlConstants.XML_ROLES); - // read roles - Map roles = readRoles(rolesElement); - this.roleMap = roles; - - // USERS - // get users element - Element usersElement = modelsRootElement.element(XmlConstants.XML_USERS); - // read users - Map users = readUsers(usersElement); - this.userMap = users; - - this.userMapDirty = false; - this.roleMapDirty = false; - - XmlPersistenceHandler.logger.info("Read " + this.userMap.size() + " Users"); - XmlPersistenceHandler.logger.info("Read " + this.roleMap.size() + " Roles"); - - // validate we have a user with PrivilegeAdmin access - boolean privilegeAdminExists = false; - for (String username : this.userMap.keySet()) { - User user = this.userMap.get(username); - if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { - privilegeAdminExists = true; - break; - } - } - - if (!privilegeAdminExists) { - XmlPersistenceHandler.logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE - + "' exists. Privilege modifications will not be possible!"); - } - - return true; - } - /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#initialize(java.util.Map) */ @@ -284,317 +164,94 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * Parses {@link User} objects from their XML representations - * - * @param usersRootElement - * the element containing suer elements - * - * @return the map of converted {@link User} objects + * @see ch.eitchnet.privilege.handler.PersistenceHandler#reload() */ - protected Map readUsers(Element usersRootElement) { + @Override + public boolean reload() { - Map userMap = new HashMap(); - - @SuppressWarnings("unchecked") - List userElements = usersRootElement.elements(XmlConstants.XML_USER); - for (Element userElement : userElements) { - - String userId = userElement.attributeValue(XmlConstants.XML_ATTR_USER_ID); - - String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); - String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); - - String firstname = userElement.element(XmlConstants.XML_FIRSTNAME).getTextTrim(); - String surname = userElement.element(XmlConstants.XML_SURNAME).getTextTrim(); - - UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); - - // TODO better parsing needed - String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); - Locale locale = new Locale(localeName); - - // read roles - Element rolesElement = userElement.element(XmlConstants.XML_ROLES); - @SuppressWarnings("unchecked") - List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); - Set roles = new HashSet(); - for (Element roleElement : rolesElementList) { - String roleName = roleElement.getTextTrim(); - if (roleName.isEmpty()) { - XmlPersistenceHandler.logger.error("User " + username - + " has a role defined with no name, Skipped."); - } else if (!this.roleMap.containsKey(roleName)) { - XmlPersistenceHandler.logger.error("User " + username + " has a inexistant role " + roleName - + ", Skipped."); - } else { - roles.add(roleName); - } - } - - // read properties - Element propertiesElement = userElement.element(XmlConstants.XML_PROPERTIES); - Map propertyMap = XmlPersistenceHandler.convertToPropertyMap(propertiesElement); - - // create user - User user = new User(userId, username, password, firstname, surname, userState, roles, locale, propertyMap); - - // put user in map - userMap.put(username, user); - XmlPersistenceHandler.logger.info("Loaded user " + user); + // validate file exists + File modelsFile = new File(this.modelPath); + if (!modelsFile.exists()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " + + modelsFile.getAbsolutePath()); } - return userMap; + this.roleMap = Collections.synchronizedMap(new HashMap()); + this.userMap = Collections.synchronizedMap(new HashMap()); + + // parse models xml file to XML document + PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); + XmlHelper.parseDocument(modelsFile, xmlHandler); + + this.modelsFileDate = modelsFile.lastModified(); + + // ROLES + List roles = xmlHandler.getRoles(); + for (Role role : roles) { + this.roleMap.put(role.getName(), role); + } + + // USERS + List users = xmlHandler.getUsers(); + for (User user : users) { + this.userMap.put(user.getUsername(), user); + } + + this.userMapDirty = false; + this.roleMapDirty = false; + + XmlPersistenceHandler.logger.info("Read " + this.userMap.size() + " Users"); + XmlPersistenceHandler.logger.info("Read " + this.roleMap.size() + " Roles"); + + // validate we have a user with PrivilegeAdmin access + boolean privilegeAdminExists = false; + for (String username : this.userMap.keySet()) { + User user = this.userMap.get(username); + if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { + privilegeAdminExists = true; + break; + } + } + + if (!privilegeAdminExists) { + XmlPersistenceHandler.logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + + "' exists. Privilege modifications will not be possible!"); + } + + return true; } /** - * Parses {@link Role} objects from their XML representations - * - * @param rolesRootElement - * the element containing role elements - * - * @return the map of converted {@link Role} objects + * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() */ - protected Map readRoles(Element rolesRootElement) { + @Override + public boolean persist() { - Map roleMap = new HashMap(); - - @SuppressWarnings("unchecked") - List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); - for (Element roleElement : roleElements) { - - String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); - - Map privilegeMap = readPrivileges(roleElement); - - Role role = new Role(roleName, privilegeMap); - roleMap.put(roleName, role); + // get models file name + String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + if (modelFileName == null || modelFileName.isEmpty()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); } - return roleMap; - } - - /** - * Parses {@link Privilege} objects from their XML representation to their objects - * - * @param roleParentElement - * the parent on which the Privilege XML elements are - * - * @return the map of {@link Privilege} objects - */ - protected Map readPrivileges(Element roleParentElement) { - - Map privilegeMap = new HashMap(); - - @SuppressWarnings("unchecked") - List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); - for (Element privilegeElement : privilegeElements) { - - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - - Element allAllowedE = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED); - boolean allAllowed = false; - if (allAllowedE != null) { - allAllowed = Boolean.valueOf(allAllowedE.getTextTrim()).booleanValue(); - } - - @SuppressWarnings("unchecked") - List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - Set denyList = new HashSet(denyElements.size()); - for (Element denyElement : denyElements) { - String denyValue = denyElement.getTextTrim(); - if (!denyValue.isEmpty()) - denyList.add(denyValue); - } - - @SuppressWarnings("unchecked") - List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - Set allowList = new HashSet(allowElements.size()); - for (Element allowElement : allowElements) { - String allowValue = allowElement.getTextTrim(); - if (!allowValue.isEmpty()) - allowList.add(allowValue); - } - - Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); + // get model file + File modelFile = new File(this.modelPath); + boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; + if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { + XmlPersistenceHandler.logger + .warn("Not persisting as current file is unchanged and model data is not dirty"); + return false; } - return privilegeMap; - } + // delegate writing + PrivilegeModelDomWriter modelWriter = new PrivilegeModelDomWriter(getAllUsers(), getAllRoles(), modelFile); + modelWriter.write(); - /** - * Converts {@link User} objects to their XML representations - * - * @param userMap - * the map of users to convert - * - * @return the list of XML User elements - */ - protected static List toDomUsers(Map userMap) { + // reset dirty states + this.userMapDirty = false; + this.roleMapDirty = false; - List usersAsElements = new ArrayList(userMap.size()); - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - - synchronized (userMap) { - for (String userName : userMap.keySet()) { - - // get the user object - User user = userMap.get(userName); - - // create the user element - Element userElement = documentFactory.createElement(XmlConstants.XML_USER); - userElement.addAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); - userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); - - // add first name element - Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); - firstnameElement.setText(user.getFirstname()); - userElement.add(firstnameElement); - - // add surname element - Element surnameElement = documentFactory.createElement(XmlConstants.XML_SURNAME); - surnameElement.setText(user.getSurname()); - userElement.add(surnameElement); - - // add state element - Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); - stateElement.setText(user.getUserState().toString()); - userElement.add(stateElement); - - // add locale element - Element localeElement = documentFactory.createElement(XmlConstants.XML_LOCALE); - localeElement.setText(user.getLocale().toString()); - userElement.add(localeElement); - - // add all the role elements - Element rolesElement = documentFactory.createElement(XmlConstants.XML_ROLES); - userElement.add(rolesElement); - for (String roleName : user.getRoles()) { - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.setText(roleName); - rolesElement.add(roleElement); - } - - // add element to return list - usersAsElements.add(userElement); - } - } - - return usersAsElements; - } - - /** - * Converts {@link Role} objects to their XML representations - * - * @param roleMap - * the roles to convert - * - * @return the list of XML Role elements - */ - protected static List toDomRoles(Map roleMap) { - - List rolesAsElements = new ArrayList(roleMap.size()); - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - - synchronized (roleMap) { - for (String roleName : roleMap.keySet()) { - - // get the role object - Role role = roleMap.get(roleName); - - // create the role element - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - - // add all the privileges - XmlPersistenceHandler.toDomPrivileges(roleElement, role.getPrivilegeMap()); - - // add element to return list - rolesAsElements.add(roleElement); - } - } - - return rolesAsElements; - } - - /** - * Converts {@link Privilege} objects to their XML representation - * - * @param roleParentElement - * the XML element of the parent {@link Role} - * @param privilegeMap - * the map of {@link Privilege}s to convert - */ - protected static void toDomPrivileges(Element roleParentElement, Map privilegeMap) { - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - - for (Privilege privilege : privilegeMap.values()) { - - // create the privilege element - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); - - // add the all allowed element - Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.add(allAllowedElement); - - // add all the deny values - for (String denyValue : privilege.getDenyList()) { - Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); - denyValueElement.setText(denyValue); - privilegeElement.add(denyValueElement); - } - - // add all the allow values - for (String allowValue : privilege.getAllowList()) { - Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); - allowValueElement.setText(allowValue); - privilegeElement.add(allowValueElement); - } - - // add element to parent - roleParentElement.add(privilegeElement); - } - } - - /** - * Converts an {@link XmlConstants#XML_PROPERTIES} element containing {@link XmlConstants#XML_PROPERTY} elements to - * a {@link Map} of String key/value pairs - * - * @param element - * the XML {@link Element} with name {@link XmlConstants#XML_PROPERTIES} containing - * {@link XmlConstants#XML_PROPERTY} elements - * - * @return the {@link Map} of the property name/value combinations from the given {@link Element} - */ - @SuppressWarnings("unchecked") - protected static Map convertToPropertyMap(Element element) { - - // if element is null then there are no properties, so return empty map - if (element == null) - return Collections.emptyMap(); - - List elements = element.elements(XmlConstants.XML_PROPERTY); - - // if elements is null or empty then there are no properties, so return empty map - if (elements == null || elements.isEmpty()) - return Collections.emptyMap(); - - Map propertyMap = new HashMap(); - - for (Element property : elements) { - String name = property.attributeValue(XmlConstants.XML_ATTR_NAME); - String value = property.attributeValue(XmlConstants.XML_ATTR_VALUE); - propertyMap.put(name, value); - } - - return propertyMap; + return true; } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 9f9bde839..56970e63b 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -20,12 +20,12 @@ package ch.eitchnet.privilege.helper; import java.io.File; - -import org.dom4j.Document; -import org.dom4j.DocumentFactory; -import org.dom4j.Element; +import java.util.HashMap; +import java.util.Map; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; /** *

    @@ -46,7 +46,7 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; */ public class BootstrapConfigurationHelper { - // private static final Logger logger = LoggerFactory.getLogger(BootstrapConfigurationHelper.class); + // private static final Logger logger = Loggerdoc.getLogger(BootstrapConfigurationHelper.class); private static String path; @@ -81,81 +81,29 @@ public class BootstrapConfigurationHelper { throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); } + Map parameterMap = new HashMap(); + Map encryptionHandlerParameterMap = new HashMap(); + Map persistenceHandlerParameterMap = new HashMap(); + // TODO ask other questions... + parameterMap.put("autoPersistOnPasswordChange", "true"); + encryptionHandlerParameterMap.put("hashAlgorithm", "SHA-256"); + persistenceHandlerParameterMap.put("basePath", "./target/test"); + persistenceHandlerParameterMap.put("modelXmlFile", "PrivilegeModel.xml"); + + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + containerModel.setParameterMap(parameterMap); + containerModel.setEncryptionHandlerClassName(defaultEncryptionHandler); + containerModel.setEncryptionHandlerParameterMap(encryptionHandlerParameterMap); + containerModel.setPersistenceHandlerClassName(defaultPersistenceHandler); + containerModel.setPersistenceHandlerParameterMap(persistenceHandlerParameterMap); + + containerModel.addPolicy("DefaultPrivilege", "ch.eitchnet.privilege.policy.DefaultPrivilege"); // now perform work: - BootstrapConfigurationHelper.createXmlPrivilegeContainer(); - BootstrapConfigurationHelper.createPolicyConfiguration(); - BootstrapConfigurationHelper.createModel(); - } - - /** - * - */ - private static void createModel() { - // TODO Auto-generated method stub - - } - - /** - * - */ - private static void createPolicyConfiguration() { - // TODO Auto-generated method stub - - } - - /** - * - */ - private static void createXmlPrivilegeContainer() { - - // create document root - DocumentFactory factory = DocumentFactory.getInstance(); - Document doc = factory.createDocument(XmlHelper.DEFAULT_ENCODING); - doc.setName(XmlConstants.XML_ROOT_PRIVILEGE); - Element rootElement = factory.createElement(XmlConstants.XML_ROOT_PRIVILEGE); - doc.setRootElement(rootElement); - - Element containerElement = factory.createElement(XmlConstants.XML_CONTAINER); - - Element parameterElement; - Element parametersElement; - - // create PersistenceHandler - Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); - containerElement.add(persistenceHandlerElem); - persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, - BootstrapConfigurationHelper.defaultPersistenceHandler); - parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); - persistenceHandlerElem.add(parametersElement); - // Parameter basePath - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_BASE_PATH); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.basePath); - parametersElement.add(parameterElement); - // Parameter modelXmlFile - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_MODEL_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.modelFileName); - parametersElement.add(parameterElement); - - // create EncryptionHandler - Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); - containerElement.add(encryptionHandlerElem); - encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, - BootstrapConfigurationHelper.defaultEncryptionHandler); - parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); - encryptionHandlerElem.add(parametersElement); - // Parameter hashAlgorithm - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_HASH_ALGORITHM); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.hashAlgorithm); - parametersElement.add(parameterElement); - - // write the container file to disk - File privilegeContainerFile = new File(BootstrapConfigurationHelper.path + "/" + File configFile = new File(BootstrapConfigurationHelper.path + "/" + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); - XmlHelper.writeDocument(doc, privilegeContainerFile); + PrivilegeConfigDomWriter configSaxWriter = new PrivilegeConfigDomWriter(containerModel, configFile); + configSaxWriter.write(); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java deleted file mode 100644 index 401cdb5fd..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2010 - 2012 - * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . - * - */ -package ch.eitchnet.privilege.helper; - -import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.dom4j.Element; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; -import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.PersistenceHandler; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.policy.PrivilegePolicy; -import ch.eitchnet.utils.helper.StringHelper; - -/** - * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the - * configuration - * - * @author Robert von Burg - */ -public class InitializationHelper { - - private static final Logger logger = LoggerFactory.getLogger(InitializationHelper.class); - - /** - * Initializes the {@link DefaultPrivilegeHandler} from the configuration file - * - * @param privilegeXmlFile - * a {@link File} reference to the XML file containing the configuration for Privilege - * - * @return the {@link PrivilegeHandler} instance loaded from the configuration file - */ - public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { - - // make sure file exists - if (!privilegeXmlFile.exists()) { - throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); - } - - // parse container xml file to XML document - Element rootElement = XmlHelper.parseDocument(privilegeXmlFile).getRootElement(); - Element containerElement = rootElement.element(XmlConstants.XML_CONTAINER); - - // instantiate encryption handler - Element encryptionHandlerElement = containerElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); - String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); - - // instantiate persistence handler - Element persistenceHandlerElement = containerElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); - String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); - - // instantiate privilege handler - DefaultPrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); - - // get policies - Element policiesElement = rootElement.element(XmlConstants.XML_POLICIES); - Map> policyMap = InitializationHelper.convertToPolicyMap(policiesElement); - - try { - - // get parameters - Element parameterElement = encryptionHandlerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); - - // initialize encryption handler - encryptionHandler.initialize(parameterMap); - - } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName - + " could not be initialized"); - } - - try { - - // get parameters - Element parameterElement = persistenceHandlerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); - - // initialize persistence handler - persistenceHandler.initialize(parameterMap); - - } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement - + " could not be initialized"); - } - - try { - - // get parameters - Element parameterElement = containerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); - - // initialize privilege handler - privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); - - } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() - + " could not be initialized"); - } - - return privilegeHandler; - } - - /** - * Converts an {@link XmlConstants#XML_PARAMETERS} element containing {@link XmlConstants#XML_PARAMETER} elements to - * a {@link Map} of String key/value pairs - * - * @param element - * the XML {@link Element} with name {@link XmlConstants#XML_PARAMETERS} containing - * {@link XmlConstants#XML_PARAMETER} elements - * - * @return the {@link Map} of the parameter name/value combinations from the given {@link Element} - */ - @SuppressWarnings("unchecked") - public static Map convertToParameterMap(Element element) { - - // if element is null then there are no parameters, so return empty map - if (element == null) - return Collections.emptyMap(); - - List elements = element.elements(XmlConstants.XML_PARAMETER); - - // if elements is null or empty then there are no parameters, so return empty map - if (elements == null || elements.isEmpty()) - return Collections.emptyMap(); - - Map parameterMap = new HashMap(); - - for (Element parameter : elements) { - String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); - String value = parameter.attributeValue(XmlConstants.XML_ATTR_VALUE); - - // replace any defined system properties - value = StringHelper.replaceSystemPropertiesIn(value); - - parameterMap.put(name, value); - } - - return parameterMap; - } - - /** - * Converts an {@link XmlConstants#XML_POLICIES} element containing {@link XmlConstants#XML_POLICY} elements to a - * {@link Map} of String/Class pairs - * - * @param element - * the XML {@link Element} with name {@link XmlConstants#XML_POLICIES} containing - * {@link XmlConstants#XML_POLICY} elements - * - * @return the {@link Map} of the policy name/class combinations from the given {@link Element} - */ - @SuppressWarnings("unchecked") - public static Map> convertToPolicyMap(Element element) { - - Map> policyMap = new HashMap>(); - - List policyElements = element.elements(XmlConstants.XML_POLICY); - for (Element policyElement : policyElements) { - String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - - Class clazz; - try { - clazz = ClassHelper.loadClass(policyClass); - } catch (PrivilegeException e) { - throw new PrivilegeException("The Policy with name " + policyName + " does not exist", e); - } - - policyMap.put(policyName, clazz); - } - - return policyMap; - } -} diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index 900c8ca87..c3507efea 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -20,24 +20,30 @@ package ch.eitchnet.privilege.helper; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.DocumentFactory; -import org.dom4j.Element; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.SAXReader; -import org.dom4j.io.XMLWriter; +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.privilege.base.PrivilegeException; +import ch.eitchnet.utils.exceptions.XmlException; /** * Helper class for performing XML based tasks using Dom4J @@ -61,22 +67,22 @@ public class XmlHelper { * * @return a {@link Document} object containing the dom4j {@link Element}s of the XML file */ - public static Document parseDocument(File xmlFile) { + public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { try { - InputStream inStream = new FileInputStream(xmlFile); + SAXParserFactory spf = SAXParserFactory.newInstance(); - SAXReader reader = new SAXReader(); - Document document = reader.read(inStream); + SAXParser sp = spf.newSAXParser(); + XmlHelper.logger.info("Parsing XML document " + xmlFile.getAbsolutePath()); + sp.parse(xmlFile, xmlHandler); - XmlHelper.logger.info("Read XML document " + document.getRootElement().getName()); - return document; - - } catch (FileNotFoundException e) { - throw new PrivilegeException("The XML file does not exist or is not readable: " + xmlFile.getAbsolutePath()); - } catch (DocumentException e) { - throw new PrivilegeException("the XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } catch (ParserConfigurationException e) { + throw new PrivilegeException("Failed to initialize a SAX Parser: " + e.getLocalizedMessage(), e); + } catch (SAXException e) { + throw new PrivilegeException("The XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } catch (IOException e) { + throw new PrivilegeException("The XML could not be read: " + xmlFile.getAbsolutePath()); } } @@ -87,40 +93,40 @@ public class XmlHelper { * 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 + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element */ - public static void writeDocument(Document document, File file) { + public static void writeDocument(Document document, File file) throws RuntimeException { - XmlHelper.logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); - - OutputStream fileOutputStream = null; + XmlHelper.logger.info("Exporting document element " + document.getNodeName() + " to " + file.getAbsolutePath()); try { - fileOutputStream = new FileOutputStream(file); - - String aEncodingScheme = document.getXMLEncoding(); - if (aEncodingScheme == null || aEncodingScheme.isEmpty()) { - aEncodingScheme = XmlHelper.DEFAULT_ENCODING; + String encoding = document.getInputEncoding(); + if (encoding == null || encoding.isEmpty()) { + encoding = XmlHelper.DEFAULT_ENCODING; } - OutputFormat outformat = OutputFormat.createPrettyPrint(); - outformat.setEncoding(aEncodingScheme); - XMLWriter writer = new XMLWriter(fileOutputStream, outformat); - writer.write(document); - writer.flush(); + + // Set up a transformer + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer transformer = transfac.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + //transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + + // Transform to file + StreamResult result = new StreamResult(file); + Source xmlSource = new DOMSource(document); + transformer.transform(xmlSource, result); } catch (Exception e) { throw new PrivilegeException("Exception while exporting to file: " + e, e); - } finally { - - if (fileOutputStream != null) { - try { - fileOutputStream.close(); - } catch (IOException e) { - XmlHelper.logger.error("Could not close file output stream: " + e, e); - } - } } } @@ -131,13 +137,40 @@ public class XmlHelper { * 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 + * @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 writeElement(Element rootElement, File file) { - - Document document = DocumentFactory.getInstance().createDocument(XmlHelper.DEFAULT_ENCODING); - document.setRootElement(rootElement); - document.setName(rootElement.getName()); + public static void writeElement(Element rootElement, File file, String encoding) throws RuntimeException { + Document document = createDocument(); + document.appendChild(rootElement); XmlHelper.writeDocument(document, file); } + + /** + * 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); + } catch (ParserConfigurationException e) { + throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); + } + } } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java new file mode 100644 index 000000000..9fff19710 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.model.internal; + +import java.util.HashMap; +import java.util.Map; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +public class PrivilegeContainerModel { + + String encryptionHandlerClassName; + Map encryptionHandlerParameterMap; + String persistenceHandlerClassName; + Map persistenceHandlerParameterMap; + Map parameterMap; + + private Map> policies = new HashMap>(); + + /** + * @return the parameterMap + */ + public Map getParameterMap() { + return this.parameterMap; + } + + /** + * @param parameterMap + * the parameterMap to set + */ + public void setParameterMap(Map parameterMap) { + this.parameterMap = parameterMap; + } + + /** + * @return the encryptionHandlerClassName + */ + public String getEncryptionHandlerClassName() { + return this.encryptionHandlerClassName; + } + + /** + * @param encryptionHandlerClassName + * the encryptionHandlerClassName to set + */ + public void setEncryptionHandlerClassName(String encryptionHandlerClassName) { + this.encryptionHandlerClassName = encryptionHandlerClassName; + } + + /** + * @return the encryptionHandlerParameterMap + */ + public Map getEncryptionHandlerParameterMap() { + return this.encryptionHandlerParameterMap; + } + + /** + * @param encryptionHandlerParameterMap + * the encryptionHandlerParameterMap to set + */ + public void setEncryptionHandlerParameterMap(Map encryptionHandlerParameterMap) { + this.encryptionHandlerParameterMap = encryptionHandlerParameterMap; + } + + /** + * @return the persistenceHandlerClassName + */ + public String getPersistenceHandlerClassName() { + return this.persistenceHandlerClassName; + } + + /** + * @param persistenceHandlerClassName + * the persistenceHandlerClassName to set + */ + public void setPersistenceHandlerClassName(String persistenceHandlerClassName) { + this.persistenceHandlerClassName = persistenceHandlerClassName; + } + + /** + * @return the persistenceHandlerParameterMap + */ + public Map getPersistenceHandlerParameterMap() { + return this.persistenceHandlerParameterMap; + } + + /** + * @param persistenceHandlerParameterMap + * the persistenceHandlerParameterMap to set + */ + public void setPersistenceHandlerParameterMap(Map persistenceHandlerParameterMap) { + this.persistenceHandlerParameterMap = persistenceHandlerParameterMap; + } + + /** + * @param name + * @param policyClass + */ + public void addPolicy(String privilegeName, String policyClassName) { + + try { + + // load class and try to create a new instance + @SuppressWarnings("unchecked") + Class clazz = (Class) Class.forName(policyClassName); + clazz.newInstance(); + + this.policies.put(privilegeName, clazz); + + } catch (InstantiationException e) { + throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " + + policyClassName + " could not be instantiated.", e); + } catch (IllegalAccessException e) { + throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " + + policyClassName + " can not be accessed.", e); + } catch (ClassNotFoundException e) { + throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " + + policyClassName + " does not exist.", e); + } + } + + /** + * @return the policies + */ + public Map> getPolicies() { + return this.policies; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrivilegeContainerModel [encryptionHandlerClassName="); + builder.append(this.encryptionHandlerClassName); + builder.append(", encryptionHandlerParameterMap="); + builder.append(this.encryptionHandlerParameterMap.size()); + builder.append(", persistenceHandlerClassName="); + builder.append(this.persistenceHandlerClassName); + builder.append(", persistenceHandlerParameterMap="); + builder.append(this.persistenceHandlerParameterMap.size()); + builder.append(", parameterMap="); + builder.append(this.parameterMap.size()); + builder.append(", policies="); + builder.append(this.policies.size()); + builder.append("]"); + return builder.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java new file mode 100644 index 000000000..35e75f251 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +public interface ElementParser { + + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException; + + public void characters(char[] ch, int start, int length) throws SAXException; + + public void endElement(String uri, String localName, String qName) throws SAXException; + + public void notifyChild(ElementParser child); +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java new file mode 100644 index 000000000..a881bc2b1 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +public abstract class ElementParserAdapter implements ElementParser { + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + // empty implementation + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + // empty implementation + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + // empty implementation + } + + @Override + public void notifyChild(ElementParser child) { + // empty implementation + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java new file mode 100644 index 000000000..edc7ec37b --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2010 - 2012 + * + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ +package ch.eitchnet.privilege.xml; + +import java.io.File; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; +import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.handler.PersistenceHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.ClassHelper; +import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the + * configuration + * + * @author Robert von Burg + */ +public class InitializationHelper { + + private static final Logger logger = LoggerFactory.getLogger(InitializationHelper.class); + + /** + * Initializes the {@link DefaultPrivilegeHandler} from the configuration file + * + * @param privilegeXmlFile + * a {@link File} reference to the XML file containing the configuration for Privilege + * + * @return the {@link PrivilegeHandler} instance loaded from the configuration file + */ + public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { + + // make sure file exists + if (!privilegeXmlFile.exists()) { + throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); + } + + // parse configuration file + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + PrivilegeConfigSaxReader xmlHandler = new PrivilegeConfigSaxReader(containerModel); + XmlHelper.parseDocument(privilegeXmlFile, xmlHandler); + + // initialize encryption handler + String encryptionHandlerClassName = containerModel.getEncryptionHandlerClassName(); + EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); + Map parameterMap = containerModel.getEncryptionHandlerParameterMap(); + try { + encryptionHandler.initialize(parameterMap); + } catch (Exception e) { + InitializationHelper.logger.error(e.getMessage(), e); + throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + + " could not be initialized"); + } + + // initialize persistence handler + String persistenceHandlerClassName = containerModel.getPersistenceHandlerClassName(); + PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); + parameterMap = containerModel.getPersistenceHandlerParameterMap(); + try { + persistenceHandler.initialize(parameterMap); + } catch (Exception e) { + InitializationHelper.logger.error(e.getMessage(), e); + throw new PrivilegeException("PersistenceHandler " + persistenceHandlerClassName + + " could not be initialized"); + } + + // initialize privilege handler + DefaultPrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); + parameterMap = containerModel.getParameterMap(); + Map> policyMap = containerModel.getPolicies(); + try { + privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); + } catch (Exception e) { + InitializationHelper.logger.error(e.getMessage(), e); + throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + + " could not be initialized"); + } + + return privilegeHandler; + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java new file mode 100644 index 000000000..4826da315 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +import java.io.File; +import java.util.Map.Entry; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeConfigDomWriter { + + private final PrivilegeContainerModel containerModel; + private final File configFile; + + /** + * + */ + public PrivilegeConfigDomWriter(PrivilegeContainerModel containerModel, File configFile) { + this.containerModel = containerModel; + this.configFile = configFile; + } + + /** + * + */ + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_ROOT_PRIVILEGE); + doc.appendChild(rootElement); + + Element containerElement = doc.createElement(XmlConstants.XML_CONTAINER); + rootElement.appendChild(containerElement); + + Element parameterElement; + Element parametersElement; + + // Parameters + parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + containerElement.appendChild(parametersElement); + for (Entry entry : this.containerModel.getParameterMap().entrySet()) { + parameterElement = doc.createElement(XmlConstants.XML_PARAMETER); + parameterElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + parameterElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(parameterElement); + } + + // create EncryptionHandler + Element encryptionHandlerElem = doc.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); + containerElement.appendChild(encryptionHandlerElem); + encryptionHandlerElem.setAttribute(XmlConstants.XML_ATTR_CLASS, + this.containerModel.getEncryptionHandlerClassName()); + // Parameters + parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + encryptionHandlerElem.appendChild(parametersElement); + for (Entry entry : this.containerModel.getEncryptionHandlerParameterMap().entrySet()) { + parameterElement = doc.createElement(XmlConstants.XML_PARAMETER); + parameterElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + parameterElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(parameterElement); + } + + // create PersistenceHandler + Element persistenceHandlerElem = doc.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); + containerElement.appendChild(persistenceHandlerElem); + persistenceHandlerElem.setAttribute(XmlConstants.XML_ATTR_CLASS, + this.containerModel.getPersistenceHandlerClassName()); + // Parameters + parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + persistenceHandlerElem.appendChild(parametersElement); + for (Entry entry : this.containerModel.getPersistenceHandlerParameterMap().entrySet()) { + parameterElement = doc.createElement(XmlConstants.XML_PARAMETER); + parameterElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + parameterElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(parameterElement); + } + + // Policies + Element policiesElem = doc.createElement(XmlConstants.XML_POLICIES); + rootElement.appendChild(policiesElem); + for (Entry> entry : this.containerModel.getPolicies().entrySet()) { + Element policyElem = doc.createElement(XmlConstants.XML_POLICY); + policyElem.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + policyElem.setAttribute(XmlConstants.XML_ATTR_CLASS, entry.getValue().getName()); + policiesElem.appendChild(policyElem); + } + + // write the container file to disk + XmlHelper.writeDocument(doc, this.configFile); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java new file mode 100644 index 000000000..95fb910d1 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeConfigSaxReader extends DefaultHandler { + + // private static final Logger logger = LoggerFactory.getLogger(PrivilegeConfigSaxReader.class); + + private Stack buildersStack = new Stack(); + + private PrivilegeContainerModel containerModel; + + public PrivilegeConfigSaxReader(PrivilegeContainerModel containerModel) { + this.containerModel = containerModel; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (qName.equals("Container")) { + this.buildersStack.add(new ContainerParser()); + } else if (qName.equals("Parameters")) { + this.buildersStack.add(new ParametersParser()); + } else if (qName.equals("Policies")) { + this.buildersStack.add(new PoliciesParser()); + } + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().startElement(uri, localName, qName, attributes); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().characters(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().endElement(uri, localName, qName); + + ElementParser elementParser = null; + if (qName.equals("Container")) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals("Parameters")) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals("Policies")) { + elementParser = this.buildersStack.pop(); + } + + if (!this.buildersStack.isEmpty() && elementParser != null) + this.buildersStack.peek().notifyChild(elementParser); + } + + public class ContainerParser extends ElementParserAdapter { + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + private String currentElement; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("Container")) { + this.currentElement = qName; + } else if (qName.equals("EncryptionHandler")) { + this.currentElement = qName; + PrivilegeConfigSaxReader.this.containerModel + .setEncryptionHandlerClassName(attributes.getValue("class")); + } else if (qName.equals("PersistenceHandler")) { + this.currentElement = qName; + PrivilegeConfigSaxReader.this.containerModel.setPersistenceHandlerClassName(attributes + .getValue("class")); + } + } + + @Override + public void notifyChild(ElementParser child) { + if (!(child instanceof ParametersParser)) + return; + + ParametersParser parametersChild = (ParametersParser) child; + + if (this.currentElement.equals("Container")) { + PrivilegeConfigSaxReader.this.containerModel.setParameterMap(parametersChild.getParameterMap()); + } else if (this.currentElement.equals("EncryptionHandler")) { + PrivilegeConfigSaxReader.this.containerModel.setEncryptionHandlerParameterMap(parametersChild + .getParameterMap()); + } else if (this.currentElement.equals("PersistenceHandler")) { + PrivilegeConfigSaxReader.this.containerModel.setPersistenceHandlerParameterMap(parametersChild + .getParameterMap()); + } + } + } + + class ParametersParser extends ElementParserAdapter { + +// + + private Map parameterMap = new HashMap(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("Parameter")) { + String key = attributes.getValue("name"); + String value = attributes.getValue("value"); + this.parameterMap.put(key, value); + } + } + + /** + * @return the parameterMap + */ + public Map getParameterMap() { + return this.parameterMap; + } + } + + class PoliciesParser extends ElementParserAdapter { + +// + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("Policy")) { + String policyName = attributes.getValue("name"); + String policyClassName = attributes.getValue("class"); + + PrivilegeConfigSaxReader.this.containerModel.addPolicy(policyName, policyClassName); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java new file mode 100644 index 000000000..e740dce35 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +import java.io.File; +import java.util.List; +import java.util.Map.Entry; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeModelDomWriter { + + private List users; + private List roles; + private File modelFile; + + /** + * + */ + public PrivilegeModelDomWriter(List users, List roles, File modelFile) { + this.users = users; + this.roles = roles; + this.modelFile = modelFile; + } + + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_ROOT_PRIVILEGE_USERS_AND_ROLES); + doc.appendChild(rootElement); + + Element usersElement = doc.createElement(XmlConstants.XML_USERS); + rootElement.appendChild(usersElement); + + for (User user : this.users) { + + // create the user element + Element userElement = doc.createElement(XmlConstants.XML_USER); + usersElement.appendChild(userElement); + + userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); + userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); + userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + + // add first name element + Element firstnameElement = doc.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setTextContent(user.getFirstname()); + userElement.appendChild(firstnameElement); + + // add surname element + Element surnameElement = doc.createElement(XmlConstants.XML_SURNAME); + surnameElement.setTextContent(user.getSurname()); + userElement.appendChild(surnameElement); + + // add state element + Element stateElement = doc.createElement(XmlConstants.XML_STATE); + stateElement.setTextContent(user.getUserState().toString()); + userElement.appendChild(stateElement); + + // add locale element + Element localeElement = doc.createElement(XmlConstants.XML_LOCALE); + localeElement.setTextContent(user.getLocale().toString()); + userElement.appendChild(localeElement); + + // add all the role elements + Element rolesElement = doc.createElement(XmlConstants.XML_ROLES); + userElement.appendChild(rolesElement); + for (String roleName : user.getRoles()) { + Element roleElement = doc.createElement(XmlConstants.XML_ROLE); + roleElement.setTextContent(roleName); + rolesElement.appendChild(roleElement); + } + + // add the parameters + Element parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + userElement.appendChild(parametersElement); + for (Entry entry : user.getProperties().entrySet()) { + Element paramElement = doc.createElement(XmlConstants.XML_PARAMETER); + paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(paramElement); + } + } + + Element rolesElement = doc.createElement(XmlConstants.XML_ROLES); + rootElement.appendChild(rolesElement); + + for (Role role : this.roles) { + + // create the role element + Element roleElement = doc.createElement(XmlConstants.XML_ROLE); + rolesElement.appendChild(roleElement); + + roleElement.setAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + + for (Privilege privilege : role.getPrivilegeMap().values()) { + + // create the privilege element + Element privilegeElement = doc.createElement(XmlConstants.XML_PRIVILEGE); + roleElement.appendChild(privilegeElement); + + privilegeElement.setAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.setAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + + // add the all allowed element + Element allAllowedElement = doc.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setTextContent(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.appendChild(allAllowedElement); + + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = doc.createElement(XmlConstants.XML_DENY); + denyValueElement.setTextContent(denyValue); + privilegeElement.appendChild(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = doc.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setTextContent(allowValue); + privilegeElement.appendChild(allowValueElement); + } + } + } + + // write the container file to disk + XmlHelper.writeDocument(doc, this.modelFile); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java new file mode 100644 index 000000000..c79378489 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeModelSaxReader extends DefaultHandler { + + private static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); + + private Stack buildersStack = new Stack(); + + private List users; + private List roles; + + private boolean insideUser; + + /** + * + */ + public PrivilegeModelSaxReader() { + this.users = new ArrayList(); + this.roles = new ArrayList(); + } + + /** + * @return the users + */ + public List getUsers() { + return this.users; + } + + /** + * @return the roles + */ + public List getRoles() { + return this.roles; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (qName.equals("Users")) { + this.buildersStack.add(new UserParser()); + this.insideUser = true; + } else if (qName.equals("Properties")) { + this.buildersStack.add(new PropertyParser()); + } else if (qName.equals("Roles") && !this.insideUser) { + this.buildersStack.add(new RoleParser()); + } + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().startElement(uri, localName, qName, attributes); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().characters(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().endElement(uri, localName, qName); + + ElementParser elementParser = null; + if (qName.equals("Users")) { + elementParser = this.buildersStack.pop(); + this.insideUser = false; + PrivilegeModelSaxReader.logger.info("Popping for Users"); + } else if (qName.equals("Properties")) { + elementParser = this.buildersStack.pop(); + PrivilegeModelSaxReader.logger.info("Popping for Properties"); + } else if (qName.equals("Roles") && !this.insideUser) { + elementParser = this.buildersStack.pop(); + PrivilegeModelSaxReader.logger.info("Popping for Roles"); + } + + if (!this.buildersStack.isEmpty() && elementParser != null) + this.buildersStack.peek().notifyChild(elementParser); + } + +// +// +// true +// +// +// +// +// true +// +// +// true +// +// + + public class RoleParser extends ElementParserAdapter { + + private StringBuilder text; + + private String roleName; + private String privilegeName; + private String privilegePolicy; + private boolean allAllowed; + private Set denyList; + private Set allowList; + + private Map privileges; + + /** + * + */ + public RoleParser() { + init(); + } + + /** + * + */ + private void init() { + this.privileges = new HashMap(); + + this.text = null; + + this.roleName = null; + this.privilegeName = null; + this.privilegePolicy = null; + this.allAllowed = false; + this.denyList = new HashSet(); + this.allowList = new HashSet(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + this.text = new StringBuilder(); + + if (qName.equals("Role")) { + this.roleName = attributes.getValue("name"); + } else if (qName.equals("Privilege")) { + this.privilegeName = attributes.getValue("name"); + this.privilegePolicy = attributes.getValue("policy"); + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (this.text != null) + this.text.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (qName.equals("AllAllowed")) { + this.allAllowed = StringHelper.parseBoolean(this.text.toString().trim()); + } else if (qName.equals("Allow")) { + this.allowList.add(this.text.toString().trim()); + } else if (qName.equals("Deny")) { + this.denyList.add(this.text.toString().trim()); + } else if (qName.equals("Privilege")) { + + Privilege privilege = new Privilege(this.privilegeName, this.privilegePolicy, this.allAllowed, + this.denyList, this.allowList); + this.privileges.put(this.privilegeName, privilege); + + } else if (qName.equals("Role")) { + + Role role = new Role(this.roleName, this.privileges); + + PrivilegeModelSaxReader.this.roles.add(role); + PrivilegeModelSaxReader.logger.info("New Role: " + role); + init(); + } + } + } + +// +// Application +// Administrator +// ENABLED +// en_GB +// +// PrivilegeAdmin +// AppUser +// +// +// +// +// +// + + public class UserParser extends ElementParserAdapter { + + StringBuilder text; + + String userId; + String username; + String password; + String firstName; + String surname; + UserState userState; + Locale locale; + Set userRoles; + Map parameters; + + public UserParser() { + this.userRoles = new HashSet(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + this.text = new StringBuilder(); + + if (qName.equals("User")) { + this.userId = attributes.getValue("userId"); + this.username = attributes.getValue("username"); + this.password = attributes.getValue("password"); + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + this.text.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (qName.equals("Firstname")) { + this.firstName = this.text.toString().trim(); + } else if (qName.equals("Surname")) { + this.surname = this.text.toString().trim(); + } else if (qName.equals("State")) { + this.userState = UserState.valueOf(this.text.toString().trim()); + } else if (qName.equals("Locale")) { + this.locale = Locale.forLanguageTag(this.text.toString().trim()); + } else if (qName.equals("Role")) { + this.userRoles.add(this.text.toString().trim()); + } else if (qName.equals("User")) { + + User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, + this.userState, this.userRoles, this.locale, this.parameters); + + StringBuilder builder = new StringBuilder(); + builder.append("UserParser [userId="); + builder.append(this.userId); + builder.append(", username="); + builder.append(this.username); + builder.append(", password="); + builder.append(this.password); + builder.append(", firstName="); + builder.append(this.firstName); + builder.append(", surname="); + builder.append(this.surname); + builder.append(", userState="); + builder.append(this.userState); + builder.append(", locale="); + builder.append(this.locale); + builder.append(", userRoles="); + builder.append(this.userRoles.size()); + builder.append(", parameters="); + builder.append(this.parameters.size()); + builder.append("]"); + PrivilegeModelSaxReader.logger.info(builder.toString()); + + PrivilegeModelSaxReader.this.users.add(user); + + } + } + + @Override + public void notifyChild(ElementParser child) { + if (child instanceof PropertyParser) { + this.parameters = ((PropertyParser) child).parameterMap; + } + } + } + + class PropertyParser extends ElementParserAdapter { + +// + + private Map parameterMap = new HashMap(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("Property")) { + String key = attributes.getValue("name"); + String value = attributes.getValue("value"); + this.parameterMap.put(key, value); + } + } + + /** + * @return the parameterMap + */ + public Map getParameterMap() { + return this.parameterMap; + } + } +} diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 47e2c69e7..e7854c128 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -37,7 +37,6 @@ import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.CertificateThreadLocal; -import ch.eitchnet.privilege.helper.InitializationHelper; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; @@ -47,6 +46,7 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.test.model.TestRestrictable; import ch.eitchnet.privilege.test.model.TestSystemUserAction; import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; +import ch.eitchnet.privilege.xml.InitializationHelper; import ch.eitchnet.utils.helper.FileHelper; /** diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java new file mode 100644 index 000000000..3b8de44f0 --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.test; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import junit.framework.Assert; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.handler.DefaultEncryptionHandler; +import ch.eitchnet.privilege.handler.XmlPersistenceHandler; +import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; +import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + * + */ +public class XmlTest { + + /** + * + */ + private static final String TARGET_TEST = "./target/test"; + private static final Logger logger = LoggerFactory.getLogger(XmlTest.class); + + /** + * @throws Exception + * if something goes wrong + */ + @BeforeClass + public static void init() throws Exception { + + File tmpDir = new File("target/test"); + if (tmpDir.exists()) + FileHelper.deleteFile(tmpDir, false); + tmpDir.mkdirs(); + } + + @AfterClass + public static void destroy() throws Exception { + + File tmpDir = new File("target/test"); + if (!tmpDir.exists()) + return; + + File tmpFile = new File("target/test/PrivilegeTest.xml"); + if (tmpFile.exists() && !tmpFile.delete()) { + throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); + } + + tmpFile = new File("target/test/PrivilegeModelTest.xml"); + if (tmpFile.exists() && !tmpFile.delete()) { + throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); + } + + // and temporary parent + if (!tmpDir.delete()) { + throw new RuntimeException("Could not remove temporary parent for tmp " + tmpFile); + } + } + + @Test + public void canReadConfig() { + + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + PrivilegeConfigSaxReader saxReader = new PrivilegeConfigSaxReader(containerModel); + File xmlFile = new File("config/Privilege.xml"); + XmlHelper.parseDocument(xmlFile, saxReader); + logger.info(containerModel.toString()); + + // assert all objects read + Assert.assertNotNull(containerModel.getParameterMap()); + Assert.assertNotNull(containerModel.getPolicies()); + Assert.assertNotNull(containerModel.getEncryptionHandlerClassName()); + Assert.assertNotNull(containerModel.getEncryptionHandlerParameterMap()); + Assert.assertNotNull(containerModel.getPersistenceHandlerClassName()); + Assert.assertNotNull(containerModel.getPersistenceHandlerParameterMap()); + + Assert.assertEquals(1, containerModel.getParameterMap().size()); + Assert.assertEquals(1, containerModel.getPolicies().size()); + Assert.assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); + Assert.assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); + + // TODO extend assertions to actual model + } + + @Test + public void canWriteConfig() { + + Map parameterMap = new HashMap(); + Map encryptionHandlerParameterMap = new HashMap(); + Map persistenceHandlerParameterMap = new HashMap(); + + // TODO ask other questions... + parameterMap.put("autoPersistOnPasswordChange", "true"); + encryptionHandlerParameterMap.put("hashAlgorithm", "SHA-256"); + persistenceHandlerParameterMap.put("basePath", TARGET_TEST); + persistenceHandlerParameterMap.put("modelXmlFile", "PrivilegeModel.xml"); + + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + containerModel.setParameterMap(parameterMap); + containerModel.setEncryptionHandlerClassName(DefaultEncryptionHandler.class.getName()); + containerModel.setEncryptionHandlerParameterMap(encryptionHandlerParameterMap); + containerModel.setPersistenceHandlerClassName(XmlPersistenceHandler.class.getName()); + containerModel.setPersistenceHandlerParameterMap(persistenceHandlerParameterMap); + + containerModel.addPolicy("DefaultPrivilege", "ch.eitchnet.privilege.policy.DefaultPrivilege"); + + File configFile = new File("./target/test/PrivilegeTest.xml"); + PrivilegeConfigDomWriter configSaxWriter = new PrivilegeConfigDomWriter(containerModel, configFile); + configSaxWriter.write(); + + String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); + Assert.assertEquals("22d4ba39605d49c758184d9bd63beae5ccf8786f3dabbab45cd9f59c2afbcbd0", fileHash); + } + + @Test + public void canReadModel() { + + PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); + File xmlFile = new File("config/PrivilegeModel.xml"); + XmlHelper.parseDocument(xmlFile, xmlHandler); + + List users = xmlHandler.getUsers(); + Assert.assertNotNull(users); + List roles = xmlHandler.getRoles(); + Assert.assertNotNull(roles); + + Assert.assertEquals(2, users.size()); + Assert.assertEquals(4, roles.size()); + + // TODO extend assertions to actual model + } + + @Test + public void canWriteModel() { + + Map propertyMap; + Set userRoles; + Map privilegeMap; + + List users = new ArrayList(); + propertyMap = new HashMap(); + propertyMap.put("prop1", "value1"); + userRoles = new HashSet(); + userRoles.add("role1"); + users.add(new User("1", "user1", "blabla", "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, + propertyMap)); + + propertyMap = new HashMap(); + propertyMap.put("prop2", "value2"); + userRoles = new HashSet(); + userRoles.add("role2"); + users.add(new User("2", "user2", "haha", "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, + propertyMap)); + + List roles = new ArrayList(); + privilegeMap = new HashMap(); + privilegeMap.put("priv1", new Privilege("priv1", "DefaultPrivilege", true, null, null)); + roles.add(new Role("role1", privilegeMap)); + + privilegeMap = new HashMap(); + Set denyList = new HashSet(); + denyList.add("myself"); + Set allowList = new HashSet(); + allowList.add("other"); + privilegeMap.put("priv2", new Privilege("priv2", "DefaultPrivilege", false, denyList, allowList)); + roles.add(new Role("role2", privilegeMap)); + + File modelFile = new File("./target/test/PrivilegeModelTest.xml"); + PrivilegeModelDomWriter configSaxWriter = new PrivilegeModelDomWriter(users, roles, modelFile); + configSaxWriter.write(); + + String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); + Assert.assertEquals("8e1e82278162f21b1654c2e059570bbcb3cb63b053c1dd784bc8e225e8cfd04f", fileHash); + } +} From 5a9104ebbb39d93d8ad2594719582708d4abd64d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:21:14 +0100 Subject: [PATCH 110/457] Fixed failing tests due to a problem in Java 7 changing the byte code compilation sequence, thus the tests must have better sequencing --- .../handler/DefaultPrivilegeHandler.java | 10 +- .../privilege/test/PrivilegeTest.java | 525 +++++++++--------- 2 files changed, 274 insertions(+), 261 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 4c45f060f..8aa236f38 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -420,7 +420,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate that role exists if (getRole(roleName) == null) { - throw new PrivilegeException("Role " + roleName + " doest not exist!"); + throw new PrivilegeException("Role " + roleName + " does not exist!"); } // create new user @@ -680,9 +680,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create certificate Certificate certificate; try { - // username must be at least 3 characters in length - if (username == null || username.length() < 3) - throw new PrivilegeException("The given username is shorter than 3 characters"); + // username must be at least 2 characters in length + if (username == null || username.length() < 2) + throw new PrivilegeException("The given username '" + username + "' is shorter than 2 characters"); // and validate the password validatePassword(password); @@ -699,7 +699,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate password String pwHash = user.getPassword(); if (pwHash == null) - throw new AccessDeniedException("User has no password and may not login!"); + throw new AccessDeniedException("User " + username + " has no password and may not login!"); if (!pwHash.equals(passwordHash)) throw new AccessDeniedException("Password is incorrect for " + username); diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 47e2c69e7..7df2aba2f 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -65,6 +65,7 @@ public class PrivilegeTest { private static final String SYSTEM_USER_ADMIN = "system_admin"; private static final byte[] PASS_BOB = "admin1".getBytes(); private static final String ROLE_APP_USER = "AppUser"; + private static final String ROLE_TEMP = "temp"; private static final String ROLE_USER = "user"; private static final byte[] PASS_DEF = "def".getBytes(); private static final byte[] PASS_BAD = "123".getBytes(); @@ -144,6 +145,12 @@ public class PrivilegeTest { } } + private byte[] copyBytes(byte[] bytes) { + byte[] copy = new byte[bytes.length]; + System.arraycopy(bytes, 0, copy, 0, bytes.length); + return copy; + } + /** * @throws Exception * if something goes wrong @@ -157,12 +164,6 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.invalidateSession(certificate); } - private byte[] copyBytes(byte[] bytes) { - byte[] copy = new byte[bytes.length]; - System.arraycopy(bytes, 0, copy, 0, bytes.length); - return copy; - } - /** * @throws Exception * if something goes wrong @@ -193,209 +194,18 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testAddUserBobAsAdmin() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - - // let's add a new user bob - UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), - null, new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); - - // set bob's password - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.logger.info("Set Bob's password"); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * Will fail because user bob is not yet enabled - * - * @throws Exception - * if something goes wrong - */ - @Test(expected = AccessDeniedException.class) - public void testFailAuthAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testEnableUserBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * Will fail as user bob has no role - * - * @throws Exception - * if something goes wrong - */ - @Test(expected = PrivilegeException.class) - public void testFailAuthUserBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddRole() throws Exception { + public void testAddRoleTemp() throws Exception { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); + RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_TEMP, privilegeMap); PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddRoleToBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAuthAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * Will fail because user bob does not have admin rights - * - * @throws Exception - * if something goes wrong - */ - @Test(expected = AccessDeniedException.class) - public void testFailAddUserTedAsBob() throws Exception { - - // auth as Bog - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // let's add a new user Ted - UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, - new HashSet(), null, new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddAdminRoleToBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, - PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddUserTedAsBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // let's add a new user ted - HashSet roles = new HashSet(); - roles.add(PrivilegeTest.ROLE_USER); - UserRep userRep = new UserRep("2", PrivilegeTest.TED, "Ted", "Newman", UserState.ENABLED, roles, null, - new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testSetTedPwdAsBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // set ted's password to default - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_DEF)); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testTedChangesOwnPwd() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_DEF)); - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_TED)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAuthAsTed() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_TED)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - /** * @throws Exception * if something goes wrong @@ -413,63 +223,6 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.invalidateSession(certificate); } - /** - * Tests if the user bob, who does not have AppUser role can perform restrictable - * - * @throws Exception - * if something goes wrong - */ - @Test(expected = AccessDeniedException.class) - public void testFailPerformRestrictableAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // see if bob can perform restrictable - Restrictable restrictable = new TestRestrictable(); - try { - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); - } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddAppRoleToBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); - PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * Tests if the user bob, who now has AppUser role can perform restrictable - * - * @throws Exception - * if something goes wrong - */ - @Test - public void testPerformRestrictableAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // see if bob can perform restrictable - Restrictable restrictable = new TestRestrictable(); - try { - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); - } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - } - /** * Tests if an action can be performed as a system user * @@ -533,4 +286,264 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.invalidateSession(certificate); } } + + /** + * This test performs multiple tests which are dependent on each other as the following is done: + *

      + *
    • add user bob
    • + *
    • fail to auth as bob as user is not enabled
    • + *
    • enable bob
    • + *
    • fail to auth as bot as bob has no role
    • + *
    • add role user to bob
    • + *
    • auth as bob
    • + *
    • fail to add user ted as bob as bob is not admin
    • + *
    • add admin role to bob
    • + *
    • add user ted as bob
    • + *
    • fail to auth as ted as ted has no password
    • + *
    • set ted's password as bob
    • + *
    • ted changes own password
    • + *
    • auth as ted
    • + *
    • fail to perform restrictable as bob as no app role
    • + *
    • add app role to bob
    • + *
    • perform restrictable as bob
    • + *
    + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testUserStory() throws Exception { + + addBobAsAdmin(); + failAuthAsBobNotEnabled(); + enableBob(); + failAuthAsBobNoRole(); + addRoleUser(); + addRoleUserToBob(); + authAsBob(); + failAddTedAsBobNotAdmin(); + addRoleAdminToBob(); + addTedAsBob(); + failAuthAsTedNoPass(); + setPassForTedAsBob(); + tedChangesOwnPass(); + authAsTed(); + failPerformRestrictableAsBobNoRoleApp(); + addRoleAppToBob(); + performRestrictableAsBob(); + } + + private void performRestrictableAsBob() { + Certificate certificate; + // testPerformRestrictableAsBob + // Tests if the user bob, who now has AppUser role can perform restrictable + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Assert.assertTrue("Certificate is null!", certificate != null); + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void addRoleAppToBob() { + Certificate certificate; + // testAddAppRoleToBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); + PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failPerformRestrictableAsBobNoRoleApp() { + Certificate certificate; + // testFailPerformRestrictableAsBob + // Tests if the user bob, who does not have AppUser role can perform restrictable + // this will fail as bob does not have role app + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Assert.assertTrue("Certificate is null!", certificate != null); + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + try { + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); + Assert.fail("Should fail as bob does not have role app"); + } catch (AccessDeniedException e) { + String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; + Assert.assertEquals(msg, e.getLocalizedMessage()); + } finally { + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + } + + private void authAsTed() { + Certificate certificate; + // testAuthAsTed + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void tedChangesOwnPass() { + Certificate certificate; + // testTedChangesOwnPwd + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_TED)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void setPassForTedAsBob() { + Certificate certificate; + // testSetTedPwdAsBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Assert.assertTrue("Certificate is null!", certificate != null); + // set ted's password to default + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_DEF)); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failAuthAsTedNoPass() { + // testFailAuthAsTedNoPass + // Will fail because user ted has no password + try { + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + org.junit.Assert.fail("User Ted may not authenticate because the user has no password!"); + } catch (PrivilegeException e) { + String msg = "User ted has no password and may not login!"; + Assert.assertEquals(msg, e.getMessage()); + } + } + + private void addTedAsBob() { + Certificate certificate; + UserRep userRep; + // testAddUserTedAsBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Assert.assertTrue("Certificate is null!", certificate != null); + // let's add a new user ted + HashSet roles = new HashSet(); + roles.add(PrivilegeTest.ROLE_USER); + userRep = new UserRep("2", PrivilegeTest.TED, "Ted", "Newman", UserState.ENABLED, roles, null, + new HashMap()); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void addRoleAdminToBob() { + Certificate certificate; + // testAddAdminRoleToBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failAddTedAsBobNotAdmin() { + Certificate certificate; + UserRep userRep; + // testFailAddUserTedAsBob + // Will fail because user bob does not have admin rights + // auth as Bob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + // let's add a new user Ted + userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, new HashSet(), + null, new HashMap()); + try { + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + Assert.fail("User bob may not add a user as bob does not have admin rights!"); + } catch (PrivilegeException e) { + String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate.toString(); + Assert.assertEquals(msg, e.getMessage()); + } finally { + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + } + + private void authAsBob() { + Certificate certificate; + // testAuthAsBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void addRoleUserToBob() { + Certificate certificate; + // testAddRoleUserToBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void addRoleUser() { + Certificate certificate; + // add role user + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + Map privilegeMap = new HashMap(); + RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); + PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failAuthAsBobNoRole() { + // testFailAuthUserBob + // Will fail as user bob has no role + try { + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + org.junit.Assert.fail("User Bob may not authenticate because the user has no role"); + } catch (PrivilegeException e) { + String msg = "User bob does not have any roles defined!"; + Assert.assertEquals(msg, e.getMessage()); + } + } + + private void enableBob() { + Certificate certificate; + // testEnableUserBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failAuthAsBobNotEnabled() { + // testFailAuthAsBob + // Will fail because user bob is not yet enabled + try { + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + org.junit.Assert.fail("User Bob may not authenticate because the user is not yet enabled!"); + } catch (PrivilegeException e) { + String msg = "User bob does not have state ENABLED and can not login!"; + Assert.assertEquals(msg, e.getMessage()); + } + } + + private void addBobAsAdmin() { + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + + // let's add a new user bob + UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), + null, new HashMap()); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); + + // set bob's password + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.logger.info("Set Bob's password"); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } } From 7814b844a1adab13023900e93d67f5187613a478 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:34:46 +0100 Subject: [PATCH 111/457] fixed an NPE when a user has no parameters during SAX parsing --- .../xml/PrivilegeModelSaxReader.java | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index c79378489..28c956700 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -287,30 +287,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, this.userState, this.userRoles, this.locale, this.parameters); - StringBuilder builder = new StringBuilder(); - builder.append("UserParser [userId="); - builder.append(this.userId); - builder.append(", username="); - builder.append(this.username); - builder.append(", password="); - builder.append(this.password); - builder.append(", firstName="); - builder.append(this.firstName); - builder.append(", surname="); - builder.append(this.surname); - builder.append(", userState="); - builder.append(this.userState); - builder.append(", locale="); - builder.append(this.locale); - builder.append(", userRoles="); - builder.append(this.userRoles.size()); - builder.append(", parameters="); - builder.append(this.parameters.size()); - builder.append("]"); - PrivilegeModelSaxReader.logger.info(builder.toString()); - PrivilegeModelSaxReader.this.users.add(user); - } } From b5b9465dbe2c061bc83e451db42a20adbb4f1c3a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:35:59 +0100 Subject: [PATCH 112/457] fixed compilation warning --- .../java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index f2c4231c8..122ba4011 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -62,7 +62,7 @@ public class PasswordCreaterUI { JLabel hash = new JLabel("Hash:", SwingConstants.RIGHT); String[] digests = new String[] { "MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512" }; - final JComboBox digestCombo = new JComboBox(digests); + final JComboBox digestCombo = new JComboBox(digests); digestCombo.setSelectedIndex(3); final JPasswordField passwordField = new JPasswordField(); final JTextField hashField = new JTextField(150); From f67fbe4edbb38d9ba0dd5d3bffeca98aad048650 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:44:25 +0100 Subject: [PATCH 113/457] fixed XXX in copyright headers --- .../ch/eitchnet/privilege/helper/PasswordCreator.java | 2 +- .../privilege/model/internal/PrivilegeContainerModel.java | 8 ++++---- .../java/ch/eitchnet/privilege/xml/ElementParser.java | 8 ++++---- .../ch/eitchnet/privilege/xml/ElementParserAdapter.java | 8 ++++---- .../eitchnet/privilege/xml/PrivilegeConfigDomWriter.java | 8 ++++---- .../eitchnet/privilege/xml/PrivilegeConfigSaxReader.java | 8 ++++---- .../eitchnet/privilege/xml/PrivilegeModelDomWriter.java | 8 ++++---- .../eitchnet/privilege/xml/PrivilegeModelSaxReader.java | 8 ++++---- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java index 228cd529c..8430e9be0 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -29,7 +29,7 @@ import java.security.MessageDigest; *

    * *

    - * TODO: Note: currently the password input is echoed which is a security risk. This is a TODO + * TODO: Note: currently the password input is echoed which is a security risk *

    * * @author Robert von Burg diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index 9fff19710..a38ba337b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege 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 + * Privilege 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 + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.model.internal; diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java index 35e75f251..0504a64b9 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege 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 + * Privilege 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 + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java index a881bc2b1..5f90bb59c 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege 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 + * Privilege 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 + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java index 4826da315..fab6e260e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege 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 + * Privilege 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 + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index 95fb910d1..d8c8b12e7 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege 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 + * Privilege 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 + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index e740dce35..8f136f2ea 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege 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 + * Privilege 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 + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index 28c956700..fa85e9843 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege 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 + * Privilege 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 + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 3b8de44f0..b730d1370 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege 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 + * Privilege 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 + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.test; From e2ebcd1fc6d3f8bf77f5c4ab05e261e510216d43 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:50:03 +0100 Subject: [PATCH 114/457] Fixed some unnecessary TODOs --- .../handler/DefaultPrivilegeHandler.java | 1 - .../privilege/model/internal/User.java | 69 +++++++++---------- .../privilege/policy/DefaultPrivilege.java | 2 - 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 8aa236f38..e7eab35f1 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -345,7 +345,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - // XXX should the collections be recreated and the getRoles() and getProperties() methods be removed? User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), userRep.getProperties()); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 0405ecec3..0428adf5f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -118,7 +118,7 @@ public final class User { if (roles == null) this.roles = Collections.emptySet(); else - this.roles = Collections.unmodifiableSet(roles); + this.roles = Collections.unmodifiableSet(new HashSet(roles)); if (locale == null) this.locale = Locale.getDefault(); @@ -128,7 +128,7 @@ public final class User { if (propertyMap == null) this.propertyMap = Collections.emptyMap(); else - this.propertyMap = Collections.unmodifiableMap(propertyMap); + this.propertyMap = Collections.unmodifiableMap(new HashMap(propertyMap)); } /** @@ -151,11 +151,6 @@ public final class User { * @return the hashed password for this {@link User} */ public String getPassword() { - - // TODO is it possible that there is a hidden way of accessing this - // field even though? The User object should be private, but maybe I - // forgot something? - return this.password; } @@ -206,6 +201,36 @@ public final class User { return this.locale; } + /** + * Returns the property with the given key + * + * @param key + * the key for which the property is to be returned + * + * @return the property with the given key, or null if the property is not defined + */ + public String getProperty(String key) { + return this.propertyMap.get(key); + } + + /** + * Returns the {@link Set} of keys of all properties + * + * @return the {@link Set} of keys of all properties + */ + public Set getPropertyKeySet() { + return this.propertyMap.keySet(); + } + + /** + * Returns the map of properties + * + * @return the map of properties + */ + public Map getProperties() { + return this.propertyMap; + } + /** * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients */ @@ -268,34 +293,4 @@ public final class User { return false; return true; } - - /** - * Returns the property with the given key - * - * @param key - * the key for which the property is to be returned - * - * @return the property with the given key, or null if the property is not defined - */ - public String getProperty(String key) { - return this.propertyMap.get(key); - } - - /** - * Returns the {@link Set} of keys of all properties - * - * @return the {@link Set} of keys of all properties - */ - public Set getPropertyKeySet() { - return this.propertyMap.keySet(); - } - - /** - * Returns the map of properties - * - * @return the map of properties - */ - public Map getProperties() { - return this.propertyMap; - } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 3db6be69b..f2e0115b0 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -26,8 +26,6 @@ import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; /** - * XXX re-think this implementation... - * * @author Robert von Burg */ public class DefaultPrivilege implements PrivilegePolicy { From dfbae9b6cb1d7cee3136367fc1986070d69b0a3d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:53:58 +0100 Subject: [PATCH 115/457] moved XML schemas to src/main/resoures/ --- src/{test => main}/resources/Privilege.xsd | 0 src/{test => main}/resources/PrivilegeModel.xsd | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{test => main}/resources/Privilege.xsd (100%) rename src/{test => main}/resources/PrivilegeModel.xsd (100%) diff --git a/src/test/resources/Privilege.xsd b/src/main/resources/Privilege.xsd similarity index 100% rename from src/test/resources/Privilege.xsd rename to src/main/resources/Privilege.xsd diff --git a/src/test/resources/PrivilegeModel.xsd b/src/main/resources/PrivilegeModel.xsd similarity index 100% rename from src/test/resources/PrivilegeModel.xsd rename to src/main/resources/PrivilegeModel.xsd From 5fcea2bb77778c131f483a22c1d3cf177755e370 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 23:17:14 +0100 Subject: [PATCH 116/457] [New] added StringHelper.parseBoolean() to check if the string value passed is either false or true, not allowing other string values. --- .../eitchnet/utils/helper/StringHelper.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index d64cbee6a..a0f3e2b95 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -463,4 +463,36 @@ public class StringHelper { public static boolean isEmpty(String value) { return value == null || value.isEmpty(); } + + /** + *

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

    + * + *

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

    + * + * @param value + * the value to check + * + * @return true or false, depending on the string value + * + * @throws RuntimeException + * if the value is empty, or not equal to the case insensitive value "true" or "false" + */ + public static boolean parseBoolean(String value) throws RuntimeException { + if (isEmpty(value)) + throw new RuntimeException("Value to parse to boolean is empty! Expected case insensitive true or false"); + String tmp = value.toLowerCase(); + if (tmp.equals(Boolean.TRUE.toString())) { + return true; + } else if (tmp.equals(Boolean.FALSE.toString())) { + return false; + } else { + throw new RuntimeException("Value " + value + + " can not be parsed to boolean! Expected case insensitive true or false"); + } + } } From 81a76a5fe0cb021e296db86c187f5f51eb4da68c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 23:20:36 +0100 Subject: [PATCH 117/457] [New] Added XmlHelper --- .../utils/exceptions/XmlException.java | 45 +++++ .../ch/eitchnet/utils/helper/XmlHelper.java | 175 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/exceptions/XmlException.java create mode 100644 src/main/java/ch/eitchnet/utils/helper/XmlHelper.java diff --git a/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java b/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java new file mode 100644 index 000000000..a453dbc86 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.utils.exceptions; + +/** + * @author Robert von Burg + * + */ +public class XmlException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * @param message + */ + public XmlException(String message) { + super(message); + } + + /** + * @param message + * @param cause + */ + public XmlException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java new file mode 100644 index 000000000..d8c61bb24 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2010 - 2012 + * + * This file is part of ch.eitchnet.java.utils. + * + * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.utils 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.utils. If not, see . + * + */ +package ch.eitchnet.utils.helper; + +import java.io.File; +import java.io.IOException; + +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 using Dom4J + * + * @author Robert von Burg + */ +public class XmlHelper { + + /** + * DEFAULT_ENCODING = "UTF-8" : defines the default UTF-8 encoding expected of XML files + */ + public static final String DEFAULT_ENCODING = "UTF-8"; + + private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); + + /** + * Parses an XML file on the file system using dom4j and returns the resulting {@link Document} object + * + * @param xmlFile + * the {@link File} which has the path to the XML file to read + * + * @return a {@link Document} object containing the dom4j {@link Element}s of the XML file + */ + public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { + + try { + + SAXParserFactory spf = SAXParserFactory.newInstance(); + + SAXParser sp = spf.newSAXParser(); + XmlHelper.logger.info("Parsing XML document " + xmlFile.getAbsolutePath()); + sp.parse(xmlFile, xmlHandler); + + } catch (ParserConfigurationException e) { + throw new XmlException("Failed to initialize a SAX Parser: " + e.getLocalizedMessage(), e); + } catch (SAXException e) { + throw new XmlException("The XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } catch (IOException e) { + throw new XmlException("The XML could not be read: " + xmlFile.getAbsolutePath()); + } + } + + /** + * Writes a dom4j {@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 + * + * @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 { + + XmlHelper.logger.info("Exporting document element " + document.getNodeName() + " to " + file.getAbsolutePath()); + + try { + + String encoding = document.getInputEncoding(); + if (encoding == null || encoding.isEmpty()) { + encoding = XmlHelper.DEFAULT_ENCODING; + } + + // Set up a transformer + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer transformer = transfac.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + //transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + + // Transform to file + StreamResult result = new StreamResult(file); + Source xmlSource = new DOMSource(document); + transformer.transform(xmlSource, result); + + } catch (Exception e) { + + throw new XmlException("Exception while exporting to file: " + e, e); + + } + } + + /** + * Writes a dom4j {@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 + * @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 writeElement(Element rootElement, File file, String encoding) throws RuntimeException { + + Document document = createDocument(); + document.appendChild(rootElement); + XmlHelper.writeDocument(document, file); + } + + /** + * 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); + } catch (ParserConfigurationException e) { + throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); + } + } +} From f92afab67181a406b6ce73607bb0cea55422e840 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 23:21:56 +0100 Subject: [PATCH 118/457] Removed local XmlHelper and moved it to ch.eitchnet.utils --- .../handler/XmlPersistenceHandler.java | 2 +- .../eitchnet/privilege/helper/XmlHelper.java | 176 ------------------ .../privilege/xml/InitializationHelper.java | 2 +- .../xml/PrivilegeConfigDomWriter.java | 2 +- .../xml/PrivilegeModelDomWriter.java | 2 +- .../ch/eitchnet/privilege/test/XmlTest.java | 2 +- 6 files changed, 5 insertions(+), 181 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 60d5828c7..b40820caa 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -31,11 +31,11 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; +import ch.eitchnet.utils.helper.XmlHelper; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java deleted file mode 100644 index c3507efea..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2010 - 2012 - * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . - * - */ -package ch.eitchnet.privilege.helper; - -import java.io.File; -import java.io.IOException; - -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.privilege.base.PrivilegeException; -import ch.eitchnet.utils.exceptions.XmlException; - -/** - * Helper class for performing XML based tasks using Dom4J - * - * @author Robert von Burg - */ -public class XmlHelper { - - /** - * DEFAULT_ENCODING = "UTF-8" : defines the default UTF-8 encoding expected of XML files - */ - public static final String DEFAULT_ENCODING = "UTF-8"; - - private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); - - /** - * Parses an XML file on the file system using dom4j and returns the resulting {@link Document} object - * - * @param xmlFile - * the {@link File} which has the path to the XML file to read - * - * @return a {@link Document} object containing the dom4j {@link Element}s of the XML file - */ - public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { - - try { - - SAXParserFactory spf = SAXParserFactory.newInstance(); - - SAXParser sp = spf.newSAXParser(); - XmlHelper.logger.info("Parsing XML document " + xmlFile.getAbsolutePath()); - sp.parse(xmlFile, xmlHandler); - - } catch (ParserConfigurationException e) { - throw new PrivilegeException("Failed to initialize a SAX Parser: " + e.getLocalizedMessage(), e); - } catch (SAXException e) { - throw new PrivilegeException("The XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); - } catch (IOException e) { - throw new PrivilegeException("The XML could not be read: " + xmlFile.getAbsolutePath()); - } - } - - /** - * Writes a dom4j {@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 - * - * @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 { - - XmlHelper.logger.info("Exporting document element " + document.getNodeName() + " to " + file.getAbsolutePath()); - - try { - - String encoding = document.getInputEncoding(); - if (encoding == null || encoding.isEmpty()) { - encoding = XmlHelper.DEFAULT_ENCODING; - } - - // Set up a transformer - TransformerFactory transfac = TransformerFactory.newInstance(); - Transformer transformer = transfac.newTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.ENCODING, encoding); - transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); - //transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); - - // Transform to file - StreamResult result = new StreamResult(file); - Source xmlSource = new DOMSource(document); - transformer.transform(xmlSource, result); - - } catch (Exception e) { - - throw new PrivilegeException("Exception while exporting to file: " + e, e); - - } - } - - /** - * Writes a dom4j {@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 - * @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 writeElement(Element rootElement, File file, String encoding) throws RuntimeException { - - Document document = createDocument(); - document.appendChild(rootElement); - XmlHelper.writeDocument(document, file); - } - - /** - * 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); - } catch (ParserConfigurationException e) { - throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); - } - } -} diff --git a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java index edc7ec37b..e5d1a1a24 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java @@ -31,9 +31,9 @@ import ch.eitchnet.privilege.handler.EncryptionHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.XmlHelper; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java index fab6e260e..fae22d9f0 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -28,9 +28,9 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 8f136f2ea..18b0e7de7 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -29,10 +29,10 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index b730d1370..ec247fa45 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -40,7 +40,6 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.DefaultEncryptionHandler; import ch.eitchnet.privilege.handler.XmlPersistenceHandler; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; @@ -52,6 +51,7 @@ import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg From 33df39c03cb74a45005687326d9a13da6f324db2 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 21 Jan 2013 18:16:21 +0100 Subject: [PATCH 119/457] Modified XmlDao to have a Document as input to create the DOM representation of the object. --- src/main/java/ch/eitchnet/xmlpers/XmlDao.java | 6 +-- .../ch/eitchnet/xmlpers/XmlFilePersister.java | 47 +++++-------------- .../xmlpers/XmlPersistenceTransaction.java | 13 +++-- .../xmlpers/test/XmlPersistenceTest.java | 4 +- .../xmlpers/test/impl/MyClassDao.java | 20 +------- 5 files changed, 26 insertions(+), 64 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlDao.java b/src/main/java/ch/eitchnet/xmlpers/XmlDao.java index 234c0a9ea..134f290b1 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlDao.java @@ -19,7 +19,6 @@ */ package ch.eitchnet.xmlpers; -import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.ContentHandler; @@ -51,11 +50,10 @@ public interface XmlDao { /** * * @param object - * @param domImplementation + * @param document * @return */ - // XXX think about returning a document, instead of an element, or use document as input - public Document serializeToDom(T object, DOMImplementation domImplementation); + public Element serializeToDom(T object, Document document); /** * @param element diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java b/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java index 8049a9946..b077cb1b5 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java @@ -19,9 +19,7 @@ */ package ch.eitchnet.xmlpers; -import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -38,19 +36,14 @@ import org.w3c.dom.Element; import org.xml.sax.SAXException; import ch.eitchnet.utils.helper.FileHelper; - -import com.sun.org.apache.xml.internal.serialize.OutputFormat; -import com.sun.org.apache.xml.internal.serialize.XMLSerializer; +import ch.eitchnet.utils.helper.XmlHelper; /** - *@author Robert von Burg + * @author Robert von Burg * */ public class XmlFilePersister { - // - private static final String XML_DEFAULT_ENCODING = "UTF-8"; - private static final Logger logger = LoggerFactory.getLogger(XmlFilePersister.class); private boolean verbose; @@ -89,34 +82,16 @@ public class XmlFilePersister { } if (this.verbose) - XmlFilePersister.logger.info("Persisting " + type + " / " + subType + " / " + id + " to " + pathF.getAbsolutePath() + "..."); + XmlFilePersister.logger.info("Persisting " + type + " / " + subType + " / " + id + " to " + + pathF.getAbsolutePath() + "..."); - BufferedOutputStream outStream = null; try { - outStream = new BufferedOutputStream(new FileOutputStream(pathF)); - - OutputFormat outputFormat = new OutputFormat("XML", XmlFilePersister.XML_DEFAULT_ENCODING, true); - outputFormat.setIndent(1); - outputFormat.setIndenting(true); - //of.setDoctype(null, null); - - XMLSerializer serializer = new XMLSerializer(outStream, outputFormat); - serializer.asDOMSerializer(); - serializer.serialize(document); - outStream.flush(); + XmlHelper.writeDocument(document, pathF); } catch (Exception e) { throw new XmlPersistenceExecption("Could not persist object " + type + " / " + subType + " / " + id + " to " + pathF.getAbsolutePath(), e); - } finally { - if (outStream != null) { - try { - outStream.close(); - } catch (IOException e) { - XmlFilePersister.logger.error(e.getMessage(), e); - } - } } if (this.verbose) @@ -137,12 +112,12 @@ public class XmlFilePersister { pathF = this.xmlPathHelper.getPathF(type, id); if (this.verbose) - XmlFilePersister.logger.info("Remove persistence file for " + type + " / " + subType + " / " + id + " from " - + pathF.getAbsolutePath() + "..."); + XmlFilePersister.logger.info("Remove persistence file for " + type + " / " + subType + " / " + id + + " from " + pathF.getAbsolutePath() + "..."); if (!pathF.exists()) { - XmlFilePersister.logger.error("Persistence file for " + type + " / " + subType + " / " + id + " does not exist at " - + pathF.getAbsolutePath()); + XmlFilePersister.logger.error("Persistence file for " + type + " / " + subType + " / " + id + + " does not exist at " + pathF.getAbsolutePath()); } else if (!pathF.delete()) { throw new XmlPersistenceExecption("Could not delete persistence file for " + type + " / " + subType + " / " + id + " at " + pathF.getAbsolutePath()); @@ -421,8 +396,8 @@ public class XmlFilePersister { File pathF = this.xmlPathHelper.getPathF(type, subType, id); if (!pathF.exists()) { - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " / " + id + " at " + pathF.getAbsolutePath() - + " does not exist, so object does not exist!"); + XmlFilePersister.logger.error("Path for " + type + " / " + subType + " / " + id + " at " + + pathF.getAbsolutePath() + " does not exist, so object does not exist!"); return null; } diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java index 5e9a2664b..fdf1c6a57 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java @@ -33,6 +33,7 @@ import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; +import ch.eitchnet.utils.helper.XmlHelper; import ch.eitchnet.utils.objectfilter.ITransactionObject; import ch.eitchnet.utils.objectfilter.ObjectFilter; @@ -279,8 +280,10 @@ public class XmlPersistenceTransaction { String subType = dao.getSubType(object); String id = dao.getId(object); - Document asDom = dao.serializeToDom(object, getDomImpl()); - this.persister.saveOrUpdate(type, subType, id, asDom); + Document doc = XmlHelper.createDocument(); + Element asDom = dao.serializeToDom(object, doc); + doc.appendChild(asDom); + this.persister.saveOrUpdate(type, subType, id, doc); } } @@ -298,8 +301,10 @@ public class XmlPersistenceTransaction { String subType = dao.getSubType(object); String id = dao.getId(object); - Document asDom = dao.serializeToDom(object, getDomImpl()); - this.persister.saveOrUpdate(type, subType, id, asDom); + Document doc = XmlHelper.createDocument(); + Element asDom = dao.serializeToDom(object, doc); + doc.appendChild(asDom); + this.persister.saveOrUpdate(type, subType, id, doc); } } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java index 6e3f4912a..bd19e047c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java @@ -54,12 +54,12 @@ public class XmlPersistenceTest { public static void init() throws Exception { try { String userDir = System.getProperty("user.dir"); - String basePath = userDir + "/tmp/testdb"; + String basePath = userDir + "/target/testdb"; File basePathF = new File(basePath); if (!basePathF.exists() && !basePathF.mkdirs()) Assert.fail("Could not create temporaray database store in " + basePathF.getAbsolutePath()); - System.setProperty(XmlPersistenceHandler.CONFIG_BASEPATH, "tmp/testdb"); + System.setProperty(XmlPersistenceHandler.CONFIG_BASEPATH, "target/testdb"); System.setProperty(XmlPersistenceHandler.CONFIG_VERBOSE, "true"); System.setProperty(XmlPersistenceHandler.CONFIG_DAO_FACTORY_CLASS, MyDaoFactory.class.getName()); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java index 4686ba349..9e426d13a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java @@ -19,7 +19,6 @@ */ package ch.eitchnet.xmlpers.test.impl; -import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; @@ -35,39 +34,25 @@ import ch.eitchnet.xmlpers.XmlDao; */ public class MyClassDao implements XmlDao { - /** - * @see ch.eitchnet.xmlpers.XmlDao#getType(java.lang.Object) - */ @Override public String getType(MyClass object) { return MyClass.class.getName(); } - /** - * @see ch.eitchnet.xmlpers.XmlDao#getSubType(java.lang.Object) - */ @Override public String getSubType(MyClass object) { return object.getType(); } - /** - * @see ch.eitchnet.xmlpers.XmlDao#getId(java.lang.Object) - */ @Override public String getId(MyClass object) { return object.getId(); } - /** - * @see ch.eitchnet.xmlpers.XmlDao#serializeToDom(java.lang.Object, org.w3c.dom.DOMImplementation) - */ @Override - public Document serializeToDom(MyClass object, DOMImplementation domImplementation) { + public Element serializeToDom(MyClass object, Document document) { - Document document = domImplementation.createDocument(null, null, null); Element element = document.createElement("MyClass"); - document.appendChild(element); element.setAttribute("id", object.getId()); element.setAttribute("type", object.getType()); @@ -77,7 +62,7 @@ public class MyClassDao implements XmlDao { Text textNode = document.createTextNode(object.getName()); nameElement.appendChild(textNode); - return document; + return element; } /** @@ -131,5 +116,4 @@ public class MyClassDao implements XmlDao { throw new RuntimeException("Failed to serialize " + object + " to SAX", e); } } - } From 64e6111c5355a778468c0180100f03f151225ca7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 21 Jan 2013 18:29:27 +0100 Subject: [PATCH 120/457] Fixed a log message about number of objects added in transaction --- .../java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java index fdf1c6a57..f3641a817 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java @@ -293,7 +293,7 @@ public class XmlPersistenceTransaction { XmlPersistenceTransaction.logger.info("No objects added in this tx."); } else { if (this.verbose) - XmlPersistenceTransaction.logger.info(updated.size() + " objects added in this tx."); + XmlPersistenceTransaction.logger.info(added.size() + " objects added in this tx."); for (ITransactionObject object : added) { From 1fbe3cb0908c59a9b0b0bf6a060558f087916f2f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 30 Jan 2013 14:56:56 +0100 Subject: [PATCH 121/457] Update pom.xml Added distribution management to deploy to nexus.eitchnet.ch --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 0cf9eb662..469d460cf 100644 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,19 @@ ... --> + + + + deployment + Internal Releases + http://nexus.eitchnet.ch/content/repositories/releases/ + + + deployment + Internal Releases + http://nexus.eitchnet.ch/nexus/content/repositories/snapshots/ + + From 08c70f5ec07fa69b6eab3d45dbcb138ec8a11c51 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 30 Jan 2013 15:02:17 +0100 Subject: [PATCH 122/457] Update pom.xml Added distribution management to deploy to nexus.eitchnet.ch --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 2776e6b91..bddc78dba 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,19 @@ codehausSnapshots Codehaus Snapshots http://snapshots.maven.codehaus.org/maven2 default ... --> + + + + deployment + Internal Releases + http://nexus.eitchnet.ch/content/repositories/releases/ + + + deployment + Internal Releases + http://nexus.eitchnet.ch/content/repositories/snapshots/ + + From 8d893d87a0ce25a8e9bf8a74608aecf9b4c09b7c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 30 Jan 2013 15:05:22 +0100 Subject: [PATCH 123/457] Update pom.xml Added distribution management to deploy to nexus.eitchnet.ch (fixed wrong URL) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 469d460cf..9343932a7 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ deployment Internal Releases - http://nexus.eitchnet.ch/nexus/content/repositories/snapshots/ + http://nexus.eitchnet.ch/content/repositories/snapshots/ From a69946d8d4a910b9a43c38633c87b97ca820a6b5 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 30 Jan 2013 15:06:43 +0100 Subject: [PATCH 124/457] Update pom.xml Added distribution management to deploy to nexus.eitchnet.ch (fixed wrong URL) --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 722434b6e..ddd624b1d 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,19 @@ codehausSnapshots Codehaus Snapshots http://snapshots.maven.codehaus.org/maven2 default ... --> + + + + deployment + Internal Releases + http://nexus.eitchnet.ch/content/repositories/releases/ + + + deployment + Internal Releases + http://nexus.eitchnet.ch/content/repositories/snapshots/ + + From ca74bf2ae855ed93ec0bd78b5140b4f9b4badba8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 31 Jan 2013 18:57:38 +0100 Subject: [PATCH 125/457] [Minor] JavaDoc comments and file headers fixed some JavaDoc comments and file headers --- .../ch/eitchnet/utils/exceptions/XmlException.java | 8 ++++---- src/main/java/ch/eitchnet/utils/helper/XmlHelper.java | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java b/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java index a453dbc86..40dec4201 100644 --- a/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java +++ b/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the ch.eitchnet.utils. * - * XXX is free software: you can redistribute + * ch.eitchnet.utils 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 + * ch.eitchnet.utils 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 + * along with ch.eitchnet.utils. If not, see * . */ package ch.eitchnet.utils.exceptions; diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java index d8c61bb24..d9a0c3af9 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -45,7 +45,7 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; /** - * Helper class for performing XML based tasks using Dom4J + * Helper class for performing XML based tasks * * @author Robert von Burg */ @@ -59,12 +59,12 @@ public class XmlHelper { private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); /** - * Parses an XML file on the file system using dom4j and returns the resulting {@link Document} object + * 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 * - * @return a {@link Document} object containing the dom4j {@link Element}s of the XML file + * @return a {@link Document} object containing the {@link Element}s of the XML file */ public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { @@ -86,7 +86,7 @@ public class XmlHelper { } /** - * Writes a dom4j {@link Document} to an XML file on the file system + * Writes a {@link Document} to an XML file on the file system * * @param document * the {@link Document} to write to the file system @@ -130,7 +130,7 @@ public class XmlHelper { } /** - * Writes a dom4j {@link Element} to an XML file on the file system + * Writes an {@link Element} to an XML file on the file system * * @param rootElement * the {@link Element} to write to the file system From acef67a99f7e82886b45af1d0fa8f93150d981ce Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 31 Jan 2013 19:07:37 +0100 Subject: [PATCH 126/457] [Minor] code cleanup commented out unneeded code --- .../privilege/helper/BootstrapConfigurationHelper.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 56970e63b..31935e3ab 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -52,10 +52,9 @@ public class BootstrapConfigurationHelper { private static String defaultPrivilegeContainerXmlFile = "Privilege.xml"; - private static String basePath = ""; - private static String modelFileName = "PrivilegeUsers.xml"; - - private static String hashAlgorithm = "SHA-256"; + //private static String basePath = ""; + //private static String modelFileName = "PrivilegeUsers.xml"; + //private static String hashAlgorithm = "SHA-256"; private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; From 1cf1a16683cd27411057988adb3fcbbbc0113091 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 9 Feb 2013 15:52:25 +0100 Subject: [PATCH 127/457] [New] Added Base64, Base32 and Base16 encoding --- .../eitchnet/utils/helper/BaseEncoding.java | 373 ++++++++++++++++++ .../ch/eitchnet/utils/helper/ByteHelper.java | 255 ++++++++++++ .../utils/helper/BaseEncodingTest.java | 84 ++++ 3 files changed, 712 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java create mode 100644 src/main/java/ch/eitchnet/utils/helper/ByteHelper.java create mode 100644 src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java new file mode 100644 index 000000000..3b607ed08 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the ch.eitchnet.java.utils. + * + * ch.eitchnet.java.utils 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. + * + * ch.eitchnet.java.utils 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 ch.eitchnet.java.utils. If not, see + * . + */ +package ch.eitchnet.utils.helper; + +/** + *

    + * This class implements the encoding part of RFC 4648 https://tools.ietf.org/html/rfc4648. For the decoding see + * {@link BaseDecoding}. + *

    + * + *

    + * The following implementations are supported: + *

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

    + * + * @author Robert von Burg + */ +public class BaseEncoding { + + // private static final Logger logger = LoggerFactory.getLogger(BaseEncoding.class); + + private static final byte PAD = '='; + + private static final byte[] BASE_16 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', + 'F' }; + + private static final byte[] BASE_32 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7' }; + + private static final byte[] BASE_32_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' }; + + private static final byte[] BASE_32_CROCKFORD = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', + 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; + + private static final byte[] BASE_64 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '+', '/' }; + + private static final byte[] BASE_64_SAFE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '_' }; + + static { + if (BASE_16.length != 16) { + throw new RuntimeException("BASE_16 alphabet does not have expected size 16 but is " + BASE_16.length); + } + if (BASE_32.length != 32) { + throw new RuntimeException("BASE_32 alphabet does not have expected size 32 but is " + BASE_32.length); + } + if (BASE_32_HEX.length != 32) { + throw new RuntimeException("BASE_32_HEX alphabet does not have expected size 32 but is " + + BASE_32_HEX.length); + } + if (BASE_32_CROCKFORD.length != 32) { + throw new RuntimeException("BASE_32_CROCKFORD alphabet does not have expected size 32 but is " + + BASE_32_CROCKFORD.length); + } + if (BASE_64.length != 64) { + throw new RuntimeException("BASE_64 alphabet does not have expected size 64 but is " + BASE_64.length); + } + if (BASE_64_SAFE.length != 64) { + throw new RuntimeException("BASE_64_SAFE alphabet does not have expected size 64 but is " + + BASE_64_SAFE.length); + } + } + + public static byte[] toBase64(byte[] bytes) { + return toBase64(BASE_64, bytes); + } + + public static byte[] toBase64Safe(byte[] bytes) { + return toBase64(BASE_64_SAFE, bytes); + } + + public static byte[] toBase32(byte[] bytes) { + return toBase32(BASE_32, bytes); + } + + public static byte[] toBase32Hex(byte[] bytes) { + return toBase32(BASE_32_HEX, bytes); + } + + public static byte[] toBase32Crockford(byte[] bytes) { + return toBase32(BASE_32_CROCKFORD, bytes); + } + + public static byte[] toBase16(byte[] bytes) { + return toBase16(bytes, BASE_16); + } + + private static byte[] toBase64(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) { + return new byte[0]; + } + + // 6 bits input for every 8 bits (1 byte) output + // least common multiple of 6 bits input and 8 bits output = 24 + // and output multiple is then lcm(6, 8) / 6 = 4 + // thus we need to write multiples of 4 bytes of data + int bitsIn = 6; + int outputMultiple = 4; + + // first convert to bits + int nrOfInputBytes = bytes.length; + int nrOfInputBits = nrOfInputBytes * Byte.SIZE; + + // calculate number of bits missing for multiples of bitsIn + int inputPadding = nrOfInputBits % bitsIn; + int nrOfOutputBytes; + if (inputPadding == 0) + nrOfOutputBytes = nrOfInputBits / bitsIn; + else + nrOfOutputBytes = (nrOfInputBits + (bitsIn - (inputPadding))) / bitsIn; + + // calculate number of bits missing for multiple of bitsOut + int nrOfBytesPadding = outputMultiple - (nrOfOutputBytes % outputMultiple); + if (nrOfBytesPadding == outputMultiple) + nrOfBytesPadding = 0; + + // actual result array is multiples of bitsOut/8 thus sum of: + int txtLength = nrOfOutputBytes + nrOfBytesPadding; + +// logger.info(String.format("Input: %d bytes, Output: %d bytes, Padding: %d bytes, TextLength: %d", +// nrOfInputBytes, nrOfOutputBytes, nrOfBytesPadding, txtLength)); + + byte[] txt = new byte[txtLength]; + long bits; + int bytesPos = 0; + int txtPos = 0; + while (bytesPos < bytes.length) { + + int remaining = bytes.length - bytesPos; + // get up to 24 bits of data in 3 bytes + bits = 0; + if (remaining >= 3) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 16) // + | ((long) (bytes[bytesPos++] & 0xff) << 8) // + | ((bytes[bytesPos++] & 0xff)); + + } else if (remaining == 2) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 16) // + | ((long) (bytes[bytesPos++] & 0xff) << 8); + + } else if (remaining == 1) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 16); + } + + // always start at 24. bit + int bitPos = 23; + + // always write 24 bits (6 bytes * 4 multiples), but this will also write into the padding + // we will fix this by writing the padding as has been calculated previously + while (bitPos >= 0) { + + int index = 0; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + byte character = alphabet[index]; + txt[txtPos] = character; + txtPos++; + } + } + + // write any padding that was calculated + if (nrOfBytesPadding != 0) { + int paddingPos = txtPos - nrOfBytesPadding; + for (; paddingPos < txtLength; paddingPos++) { + txt[paddingPos] = PAD; + } + } + + return txt; + } + + private static byte[] toBase32(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) { + return new byte[0]; + } + + // 5 bits input for every 8 bits (1 byte) output + // least common multiple of 5 bits input and 8 bits output = 40 + // and output multiple is then lcm(5, 8) / 5 = 8 + // thus we need to write multiples of 8 bytes of data + int bitsIn = 5; + int outputMultiple = 8; + + // first convert to bits + int nrOfInputBytes = bytes.length; + int nrOfInputBits = nrOfInputBytes * Byte.SIZE; + + // calculate number of bits missing for multiples of bitsIn + int inputPadding = nrOfInputBits % bitsIn; + int nrOfOutputBytes; + if (inputPadding == 0) + nrOfOutputBytes = nrOfInputBits / bitsIn; + else + nrOfOutputBytes = (nrOfInputBits + (bitsIn - (inputPadding))) / bitsIn; + + // calculate number of bits missing for multiple of bitsOut + int nrOfBytesPadding = outputMultiple - (nrOfOutputBytes % outputMultiple); + if (nrOfBytesPadding == outputMultiple) + nrOfBytesPadding = 0; + + // actual result array is multiples of bitsOut/8 thus sum of: + int txtLength = nrOfOutputBytes + nrOfBytesPadding; + +// logger.info(String.format("Input: %d bytes, Output: %d bytes, Padding: %d bytes, TextLength: %d", +// nrOfInputBytes, nrOfOutputBytes, nrOfBytesPadding, txtLength)); + + byte[] txt = new byte[txtLength]; + long bits; + int bytesPos = 0; + int txtPos = 0; + while (bytesPos < bytes.length) { + + int remaining = bytes.length - bytesPos; + // get up to 40 bits of data in 5 bytes + bits = 0; + if (remaining >= 5) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // + | ((long) (bytes[bytesPos++] & 0xff) << 24) // + | ((long) (bytes[bytesPos++] & 0xff) << 16) // + | ((long) (bytes[bytesPos++] & 0xff) << 8) // + | ((bytes[bytesPos++] & 0xff)); + + } else if (remaining == 4) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // + | ((long) (bytes[bytesPos++] & 0xff) << 24) // + | ((long) (bytes[bytesPos++] & 0xff) << 16) // + | ((long) (bytes[bytesPos++] & 0xff) << 8); + + } else if (remaining == 3) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // + | ((long) (bytes[bytesPos++] & 0xff) << 24) // + | ((long) (bytes[bytesPos++] & 0xff) << 16); + + } else if (remaining == 2) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32) // + | ((long) (bytes[bytesPos++] & 0xff) << 24); + + } else if (remaining == 1) { + + bits = ((long) (bytes[bytesPos++] & 0xff) << 32); + + } + + // always start at 40. bit + int bitPos = 39; + + // always write 40 bits (5 bytes * 8 multiples), but this will also write into the padding + // we will fix this by writing the padding as has been calculated previously + while (bitPos >= 0) { + + int index = 0; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + index |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + byte character = alphabet[index]; + txt[txtPos] = character; + txtPos++; + } + } + + // write any padding that was calculated + if (nrOfBytesPadding != 0) { + int paddingPos = txtPos - nrOfBytesPadding; + for (; paddingPos < txtLength; paddingPos++) { + txt[paddingPos] = PAD; + } + } + + return txt; + } + + private static byte[] toBase16(byte[] bytes, byte[] alphabet) { + if (bytes.length == 0) { + return new byte[0]; + } + + // calculate output text length + int nrOfInputBytes = bytes.length; + int nrOfOutputBytes = nrOfInputBytes * 2; + int txtLength = nrOfOutputBytes; + +// logger.info(String.format("Input: %d bytes, Output: %d bytes, TextLength: %d", nrOfInputBytes, nrOfOutputBytes, +// txtLength)); + + byte[] txt = new byte[txtLength]; + byte bits; + int bytesPos = 0; + int txtPos = 0; + while (bytesPos < bytes.length) { + + // get 8 bits of data (1 byte) + bits = bytes[bytesPos++]; + + // now write the 8 bits as 2 * 4 bits + + // output byte 1 + int index = (bits >>> 4) & 0xf; + byte character = alphabet[index]; + txt[txtPos] = character; + txtPos++; + + // output byte 2 + index = bits & 0xf; + character = alphabet[index]; + txt[txtPos] = character; + txtPos++; + } + + return txt; + } + // 8 7 6 5 + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 4 3 2 1 + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 0 + +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java new file mode 100644 index 000000000..c5d5483f8 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the ch.eitchnet.java.utils. + * + * ch.eitchnet.java.utils 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. + * + * ch.eitchnet.java.utils 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 ch.eitchnet.java.utils. If not, see + * . + */ +package ch.eitchnet.utils.helper; + +/** + * @author Robert von Burg + * + */ +public class ByteHelper { + + /** + * Creates a long of the given byte array. They byte array must be 8 bytes long. The byte at index 0 is the highest + * byte + * + * @param bytes + * the bytes to convert to a long + * + * @return the long created from the bytes + */ + public static long toLong(byte[] bytes) { + + if (bytes.length != 8) + throw new IllegalArgumentException("The input byte array for a long must have 8 values"); + + 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"); + + 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 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(" "); + + 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(" "); + + 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(" "); + + 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(" "); + + 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(" "); + + 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(" "); + + 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(" "); + + 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(" "); + + 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(" "); + + 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(" "); + + sb.append(((i >>> 7) & 1)); + sb.append(((i >>> 6) & 1)); + sb.append(((i >>> 5) & 1)); + sb.append(((i >>> 4) & 1)); + sb.append(((i >>> 3) & 1)); + sb.append(((i >>> 2) & 1)); + sb.append(((i >>> 1) & 1)); + sb.append(((i >>> 0) & 1)); + + return sb.toString(); + } +} diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java new file mode 100644 index 000000000..1d69c6722 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the ch.eitchnet.java.utils. + * + * ch.eitchnet.java.utils 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. + * + * ch.eitchnet.java.utils 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 ch.eitchnet.java.utils. If not, see + * . + */ +package ch.eitchnet.utils.helper; + +import static ch.eitchnet.utils.helper.BaseEncoding.toBase16; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase64; +import junit.framework.Assert; + +import org.junit.Test; + +/** + * @author Robert von Burg + * + */ +public class BaseEncodingTest { + + // private static final Logger logger = LoggerFactory.getLogger(BaseEncodingTest.class); + + @Test + public void testBase64() { + Assert.assertEquals("", new String(toBase64("".getBytes()))); + Assert.assertEquals("Zg==", new String(toBase64("f".getBytes()))); + Assert.assertEquals("Zm8=", new String(toBase64("fo".getBytes()))); + Assert.assertEquals("Zm9v", new String(toBase64("foo".getBytes()))); + Assert.assertEquals("Zm9vYg==", new String(toBase64("foob".getBytes()))); + Assert.assertEquals("Zm9vYmE=", new String(toBase64("fooba".getBytes()))); + Assert.assertEquals("Zm9vYmFy", new String(toBase64("foobar".getBytes()))); + } + + @Test + public void testBase32() { + Assert.assertEquals("", new String(toBase32("".getBytes()))); + Assert.assertEquals("MY======", new String(toBase32("f".getBytes()))); + Assert.assertEquals("MZXQ====", new String(toBase32("fo".getBytes()))); + Assert.assertEquals("MZXW6===", new String(toBase32("foo".getBytes()))); + Assert.assertEquals("MZXW6YQ=", new String(toBase32("foob".getBytes()))); + Assert.assertEquals("MZXW6YTB", new String(toBase32("fooba".getBytes()))); + Assert.assertEquals("MZXW6YTBOI======", new String(toBase32("foobar".getBytes()))); + } + + @Test + public void testBase32Hex() { + Assert.assertEquals("", new String(toBase32Hex("".getBytes()))); + Assert.assertEquals("CO======", new String(toBase32Hex("f".getBytes()))); + Assert.assertEquals("CPNG====", new String(toBase32Hex("fo".getBytes()))); + Assert.assertEquals("CPNMU===", new String(toBase32Hex("foo".getBytes()))); + Assert.assertEquals("CPNMUOG=", new String(toBase32Hex("foob".getBytes()))); + Assert.assertEquals("CPNMUOJ1", new String(toBase32Hex("fooba".getBytes()))); + Assert.assertEquals("CPNMUOJ1E8======", new String(toBase32Hex("foobar".getBytes()))); + } + + @Test + public void testBase16() { + Assert.assertEquals("", new String(toBase16("".getBytes()))); + Assert.assertEquals("66", new String(toBase16("f".getBytes()))); + Assert.assertEquals("666F", new String(toBase16("fo".getBytes()))); + Assert.assertEquals("666F6F", new String(toBase16("foo".getBytes()))); + Assert.assertEquals("666F6F62", new String(toBase16("foob".getBytes()))); + Assert.assertEquals("666F6F6261", new String(toBase16("fooba".getBytes()))); + Assert.assertEquals("666F6F626172", new String(toBase16("foobar".getBytes()))); + } + +} From b36ab20dc3163464bc2927ec4881b160a864adc9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 9 Feb 2013 16:25:36 +0100 Subject: [PATCH 128/457] [New] Added Dmedia Base32 encoding --- .../eitchnet/utils/helper/BaseEncoding.java | 81 +++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java index 3b607ed08..71187a37d 100644 --- a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java +++ b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java @@ -55,6 +55,9 @@ public class BaseEncoding { private static final byte[] BASE_32_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' }; + private static final byte[] BASE_32_DMEDIA = { '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y' }; + private static final byte[] BASE_32_CROCKFORD = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; @@ -68,30 +71,6 @@ public class BaseEncoding { 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; - static { - if (BASE_16.length != 16) { - throw new RuntimeException("BASE_16 alphabet does not have expected size 16 but is " + BASE_16.length); - } - if (BASE_32.length != 32) { - throw new RuntimeException("BASE_32 alphabet does not have expected size 32 but is " + BASE_32.length); - } - if (BASE_32_HEX.length != 32) { - throw new RuntimeException("BASE_32_HEX alphabet does not have expected size 32 but is " - + BASE_32_HEX.length); - } - if (BASE_32_CROCKFORD.length != 32) { - throw new RuntimeException("BASE_32_CROCKFORD alphabet does not have expected size 32 but is " - + BASE_32_CROCKFORD.length); - } - if (BASE_64.length != 64) { - throw new RuntimeException("BASE_64 alphabet does not have expected size 64 but is " + BASE_64.length); - } - if (BASE_64_SAFE.length != 64) { - throw new RuntimeException("BASE_64_SAFE alphabet does not have expected size 64 but is " - + BASE_64_SAFE.length); - } - } - public static byte[] toBase64(byte[] bytes) { return toBase64(BASE_64, bytes); } @@ -108,6 +87,10 @@ public class BaseEncoding { return toBase32(BASE_32_HEX, bytes); } + public static byte[] toBase32Dmedia(byte[] bytes) { + return toBase32(BASE_32_DMEDIA, bytes); + } + public static byte[] toBase32Crockford(byte[] bytes) { return toBase32(BASE_32_CROCKFORD, bytes); } @@ -116,10 +99,23 @@ public class BaseEncoding { return toBase16(bytes, BASE_16); } + /** + * Encodes the given data to a 64-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet + * + * @param bytes + * the bytes to encode + * @param alphabet + * the 64-bit alphabet to use + * + * @return the encoded data + */ private static byte[] toBase64(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) { return new byte[0]; } + if (alphabet.length != 64) { + throw new RuntimeException("Alphabet does not have expected size 64 but is " + alphabet.length); + } // 6 bits input for every 8 bits (1 byte) output // least common multiple of 6 bits input and 8 bits output = 24 @@ -213,10 +209,23 @@ public class BaseEncoding { return txt; } - private static byte[] toBase32(byte[] alphabet, byte[] bytes) { + /** + * Encodes the given data to a 32-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet + * + * @param bytes + * the bytes to encode + * @param alphabet + * the 32-bit alphabet to use + * + * @return the encoded data + */ + public static byte[] toBase32(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) { return new byte[0]; } + if (alphabet.length != 32) { + throw new RuntimeException("Alphabet does not have expected size 32 but is " + alphabet.length); + } // 5 bits input for every 8 bits (1 byte) output // least common multiple of 5 bits input and 8 bits output = 40 @@ -324,10 +333,23 @@ public class BaseEncoding { return txt; } - private static byte[] toBase16(byte[] bytes, byte[] alphabet) { + /** + * Encodes the given data to a 16-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet + * + * @param bytes + * the bytes to encode + * @param alphabet + * the 16-bit alphabet to use + * + * @return the encoded data + */ + public static byte[] toBase16(byte[] bytes, byte[] alphabet) { if (bytes.length == 0) { return new byte[0]; } + if (alphabet.length != 32) { + throw new RuntimeException("Alphabet does not have expected size 32 but is " + alphabet.length); + } // calculate output text length int nrOfInputBytes = bytes.length; @@ -363,11 +385,4 @@ public class BaseEncoding { return txt; } - // 8 7 6 5 - // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 - // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - // 4 3 2 1 - // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - // 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 0 - } \ No newline at end of file From 4d61872094581ec6e892d3f7f63428effb878b9c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 10 Feb 2013 18:38:40 +0100 Subject: [PATCH 129/457] fixed failing junit tests --- .../eitchnet/utils/helper/BaseEncoding.java | 71 +++++++++---------- .../utils/helper/BaseEncodingTest.java | 1 - 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java index 71187a37d..8ed687540 100644 --- a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java +++ b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java @@ -46,30 +46,29 @@ public class BaseEncoding { private static final byte PAD = '='; - private static final byte[] BASE_16 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', - 'F' }; + static final byte[] BASE_16 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - private static final byte[] BASE_32 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7' }; + static final byte[] BASE_32 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7' }; - private static final byte[] BASE_32_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', - 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' }; + static final byte[] BASE_32_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' }; - private static final byte[] BASE_32_DMEDIA = { '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y' }; + static final byte[] BASE_32_DMEDIA = { '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y' }; - private static final byte[] BASE_32_CROCKFORD = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', - 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; + static final byte[] BASE_32_CROCKFORD = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; - private static final byte[] BASE_64 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + static final byte[] BASE_64 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', + 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' }; + + static final byte[] BASE_64_SAFE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', - '5', '6', '7', '8', '9', '+', '/' }; - - private static final byte[] BASE_64_SAFE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', - 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', - 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '-', '_' }; + '5', '6', '7', '8', '9', '-', '_' }; public static byte[] toBase64(byte[] bytes) { return toBase64(BASE_64, bytes); @@ -96,26 +95,24 @@ public class BaseEncoding { } public static byte[] toBase16(byte[] bytes) { - return toBase16(bytes, BASE_16); + return toBase16(BASE_16, bytes); } /** * Encodes the given data to a 64-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet * - * @param bytes - * the bytes to encode * @param alphabet * the 64-bit alphabet to use + * @param bytes + * the bytes to encode * * @return the encoded data */ - private static byte[] toBase64(byte[] alphabet, byte[] bytes) { - if (bytes.length == 0) { + public static byte[] toBase64(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) return new byte[0]; - } - if (alphabet.length != 64) { + if (alphabet.length != 64) throw new RuntimeException("Alphabet does not have expected size 64 but is " + alphabet.length); - } // 6 bits input for every 8 bits (1 byte) output // least common multiple of 6 bits input and 8 bits output = 24 @@ -212,20 +209,18 @@ public class BaseEncoding { /** * Encodes the given data to a 32-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet * - * @param bytes - * the bytes to encode * @param alphabet * the 32-bit alphabet to use + * @param bytes + * the bytes to encode * * @return the encoded data */ public static byte[] toBase32(byte[] alphabet, byte[] bytes) { - if (bytes.length == 0) { + if (bytes.length == 0) return new byte[0]; - } - if (alphabet.length != 32) { + if (alphabet.length != 32) throw new RuntimeException("Alphabet does not have expected size 32 but is " + alphabet.length); - } // 5 bits input for every 8 bits (1 byte) output // least common multiple of 5 bits input and 8 bits output = 40 @@ -336,20 +331,18 @@ public class BaseEncoding { /** * Encodes the given data to a 16-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet * - * @param bytes - * the bytes to encode * @param alphabet * the 16-bit alphabet to use + * @param bytes + * the bytes to encode * * @return the encoded data */ - public static byte[] toBase16(byte[] bytes, byte[] alphabet) { - if (bytes.length == 0) { + public static byte[] toBase16( byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) return new byte[0]; - } - if (alphabet.length != 32) { - throw new RuntimeException("Alphabet does not have expected size 32 but is " + alphabet.length); - } + if (alphabet.length != 16) + throw new RuntimeException("Alphabet does not have expected size 16 but is " + alphabet.length); // calculate output text length int nrOfInputBytes = bytes.length; diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java index 1d69c6722..d6c85ef13 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -80,5 +80,4 @@ public class BaseEncodingTest { Assert.assertEquals("666F6F6261", new String(toBase16("fooba".getBytes()))); Assert.assertEquals("666F6F626172", new String(toBase16("foobar".getBytes()))); } - } From f04de1e935ee2dc4248d278816e4edcc9ddee225 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 10 Feb 2013 18:38:59 +0100 Subject: [PATCH 130/457] Minor code cleanup --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index a0f3e2b95..bbd6def33 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -39,9 +39,8 @@ public class StringHelper { /** * Hex char table for fast calculating of hex value */ - 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' }; + private static final byte[] HEX_CHAR_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', + 'f' }; /** * Converts each byte of the given byte array to a HEX value and returns the concatenation of these values From 5ddb277773e3b2bdf6639c7e84d13c1dee4f455c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Feb 2013 11:26:44 +0100 Subject: [PATCH 131/457] [New] Implemented RFC 4648 Base de/encoding Both encoding and decoding has been implemented. The specialty of this implementation is that it is possible to pass in your own alphabet thus allowing an extension without a re-implementation (again). As an addition the dbase32 alphabet was added. See http://docs.novacut.com/dbase32/dbase32.html for usage details --- .../eitchnet/utils/helper/BaseDecoding.java | 483 ++++++++++++++++++ .../eitchnet/utils/helper/BaseEncoding.java | 2 +- .../ch/eitchnet/utils/helper/ByteHelper.java | 19 + .../utils/helper/BaseDecodingTest.java | 128 +++++ .../utils/helper/BaseEncodingTest.java | 36 +- .../GenerateReverseBaseEncodingAlphabets.java | 76 +++ 6 files changed, 742 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ch/eitchnet/utils/helper/BaseDecoding.java create mode 100644 src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java create mode 100644 src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseDecoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseDecoding.java new file mode 100644 index 000000000..412486a36 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/BaseDecoding.java @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the ch.eitchnet.java.utils. + * + * ch.eitchnet.java.utils 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. + * + * ch.eitchnet.java.utils 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 ch.eitchnet.java.utils. If not, see + * . + */ +package ch.eitchnet.utils.helper; + +/** + *

    + * This class implements the decoding part of RFC 4648 https://tools.ietf.org/html/rfc4648. For the encoding see + * {@link BaseEncoding} + *

    + * + *

    + * All versions are implemented: Base64 with URL and file name safe encoding, Base32 with HEX and Base16 + *

    + * + * @author Robert von Burg + */ +public class BaseDecoding { + + // private static final Logger logger = LoggerFactory.getLogger(BaseDecoding.class); + + private static final byte PAD = '='; + + // these reverse base encoding alphabets were generated from the actual alphabet + + private static final byte[] REV_BASE_16 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + private static final byte[] REV_BASE_32 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + private static final byte[] REV_BASE_32_CROCKFORD = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, + 16, 17, -1, 18, 19, -1, 20, 21, -1, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1 }; + private static final byte[] REV_BASE_32_DMEDIA = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, -1, -1, -1, -1, -1, -1, -1, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1 }; + private static final byte[] REV_BASE_32_HEX = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1 }; + private static final byte[] REV_BASE_64 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, + -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; + private static final byte[] REV_BASE_64_SAFE = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, + -1, -1 }; + + public static byte[] fromBase64(byte[] bytes) { + return fromBase64(REV_BASE_64, bytes); + } + + public static byte[] fromBase64Safe(byte[] bytes) { + return fromBase64(REV_BASE_64_SAFE, bytes); + } + + public static byte[] fromBase32(byte[] bytes) { + return fromBase32(REV_BASE_32, bytes); + } + + public static byte[] fromBase32Hex(byte[] bytes) { + return fromBase32(REV_BASE_32_HEX, bytes); + } + + public static byte[] fromBase32Dmedia(byte[] bytes) { + return fromBase32(REV_BASE_32_DMEDIA, bytes); + } + + public static byte[] fromBase32Crockford(byte[] bytes) { + return fromBase32(REV_BASE_32_CROCKFORD, bytes); + } + + public static byte[] fromBase16(byte[] bytes) { + return fromBase16(REV_BASE_16, bytes); + } + + /** + * Decodes the given Base64 encoded data to the original data set + * + * @param alphabet + * the 64-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase64(byte[] alphabet, byte[] bytes) { + int inputLength = bytes.length; + if (inputLength == 0) + return new byte[0]; + if ((inputLength % 4) != 0) { + throw new RuntimeException("The input bytes to be decoded must be multiples of 4, but is multiple of " + + (inputLength % 4)); + } + + if (alphabet.length != 128) + throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + + // find how much padding we have + int nrOfBytesPadding = 0; + if (bytes[inputLength - 1] == PAD) { + int end = inputLength - 1; + while (bytes[end] == PAD) + end--; + if (end != inputLength - 1) + nrOfBytesPadding = inputLength - 1 - end; + } + + int inputDataLength = inputLength - nrOfBytesPadding; + int dataLengthBits = inputDataLength * 6; // 6 bits data for every 8 bits inputs + // multiples of 6 required + // truncating is no problem due to the input having padding to have multiples of 32 bits + dataLengthBits = dataLengthBits - (dataLengthBits % 8); + int dataLengthBytes = dataLengthBits / 8; + + // f => Zg== + // fo => Zm8= + // foo => Zm9v + + // we want to write as much as 24 bits in multiples of 6. + // these multiples of 6 are read from multiples of 8 + // i.e. we discard 2 bits from every 8 bits input + // thus we need to read 24 / 6 = 4 bytes + + byte[] data = new byte[dataLengthBytes]; + int dataPos = 0; + + // but we simply ignore the padding + int bytesPos = 0; + while (bytesPos < inputDataLength) { + int remaining = inputDataLength - bytesPos; + + long bits; + if (remaining >= 4) { + + // XXX check each byte value so that it is legal + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6) // + | (alphabet[bytes[bytesPos++]] & 63); + + } else if (remaining == 3) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6); + + } else if (remaining == 2) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12); +// +// long b; +// byte a; +// a = bytes[0]; +// logger.info("1 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); +// b = (byte) (alphabet[a] & 63); +// logger.info("1 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); +// a = bytes[1]; +// logger.info("2 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); +// b = (byte) (alphabet[a] & 63); +// logger.info("2 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); + + } else if (remaining == 1) { + + bits = ((alphabet[bytes[bytesPos++]] & 63) << 18); + + } else { + + bits = 0L; + } + + // we can truncate to 8 bits + int toWrite = remaining >= 4 ? 3 : remaining * 6 / 8; + // max is always 3 bytes data from 4 bytes input + +// logger.info("toWrite: " + toWrite + ", remaining: " + remaining); +// logger.info("bits: " + ByteHelper.asBinary(bits)); + + // always start at 24. bit + int bitPos = 23; + // always write 24 bits (8 bits * n bytes) + for (int i = 0; i < toWrite; i++) { + + byte value = 0; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + data[dataPos] = value; + dataPos++; + } + } + + return data; + } + +// +// /** +// * @param a +// * @return +// */ +// private static int indexOf(byte a) { +// for (int i = 0; i < BaseEncoding.BASE_64.length; i++) { +// if (BaseEncoding.BASE_64[i] == a) +// return i; +// } +// return -1; +// } +// +// private static int indexOf1(byte a) { +// for (int i = 0; i < REV_BASE_64.length; i++) { +// if (REV_BASE_64[i] == a) +// return i; +// } +// return -1; +// } + + /** + * Decodes the given Base32 encoded data to the original data set + * + * @param alphabet + * the 32-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase32(byte[] alphabet, byte[] bytes) { + int inputLength = bytes.length; + if (inputLength == 0) + return new byte[0]; + if ((inputLength % 8) != 0) { + throw new RuntimeException("The input bytes to be decoded must be multiples of 8, but is multiple of " + + (inputLength % 8)); + } + + if (alphabet.length != 128) + throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + + // find how much padding we have + int nrOfBytesPadding = 0; + if (bytes[inputLength - 1] == PAD) { + int end = inputLength - 1; + while (bytes[end] == PAD) + end--; + if (end != inputLength - 1) + nrOfBytesPadding = inputLength - 1 - end; + } + + int inputDataLength = inputLength - nrOfBytesPadding; + int dataLengthBits = inputDataLength * 5; // 5 bits data for every 8 bits inputs + // multiples of 8 required + // truncating is no problem due to the input having padding to have multiples of 40 bits + dataLengthBits = dataLengthBits - (dataLengthBits % 8); + int dataLengthBytes = dataLengthBits / 8; + +// logger.info("Input " + inputLength + " bytes, InputData " + inputDataLength + " bytes, Padding: " +// + nrOfBytesPadding + " bytes, dataLength: " + dataLengthBits + " bits, dataLengthBytes: " +// + dataLengthBytes + " bytes"); +// logger.info(ByteHelper.asBinary(bytes)); + + // we want to write as much as 40 bits in multiples of 5. + // these multiples of 5 are read from multiples of 8 + // i.e. we discard 3 bits from every 8 bits input + // thus we need to read 40 / 5 = 8 bytes + + byte[] data = new byte[dataLengthBytes]; + int dataPos = 0; + + // but we simply ignore the padding + int bytesPos = 0; + while (bytesPos < inputDataLength) { + int remaining = inputDataLength - bytesPos; + + long bits; + if (remaining >= 8) { + + // XXX check each byte value so that it is legal + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5) // + | (alphabet[bytes[bytesPos++]] & 31); + + } else if (remaining >= 7) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5); + + } else if (remaining == 6) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10); + + } else if (remaining == 5) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15); + + } else if (remaining == 4) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20); + + } else if (remaining == 3) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25); + + } else if (remaining == 2) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30); + + } else if (remaining == 1) { + + bits = ((alphabet[bytes[bytesPos++]] & 31) << 35); + + } else { + + bits = 0L; + } + + // we can truncate to 8 bits + int toRead = remaining >= 8 ? 5 : remaining * 5 / 8; + // max is always 5 bytes data from 8 bytes input + + // always start at 40. bit + int bitPos = 39; + // always write 40 bits (5 bytes * 8 bits) + for (int i = 0; i < toRead; i++) { + + byte value = 0; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + data[dataPos] = value; + dataPos++; + } + } + + return data; + } + + /** + * Decodes the given Base16 encoded data to the original data set + * + * @param alphabet + * the 16-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase16(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) + return new byte[0]; + if ((bytes.length % 2) != 0) { + throw new RuntimeException("The input bytes to be decoded must be multiples of 4, but is multiple of " + + (bytes.length % 4)); + } + + if (alphabet.length != 128) + throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + + int dataLength = bytes.length / 2; + + byte[] data = new byte[dataLength]; + for (int i = 0; i < bytes.length;) { + + byte b1 = bytes[i++]; + byte b2 = bytes[i++]; + + if (b1 < 0) { + throw new IllegalArgumentException("Value at index " + (i - 2) + " is not in range of alphabet (0-127)" + + b1); + } + if (b2 < 0) { + throw new IllegalArgumentException("Value at index " + (i - 1) + " is not in range of alphabet (0-127)" + + b2); + } + + byte c1 = alphabet[b1]; + byte c2 = alphabet[b2]; + + if (c1 == -1) { + throw new IllegalArgumentException("Value at index " + (i - 2) + + " is referencing illegal value in alphabet: " + b1); + } + if (c2 == -1) { + throw new IllegalArgumentException("Value at index " + (i - 2) + + " is referencing illegal value in alphabet: " + b2); + } + + int dataIndex = (i / 2) - 1; + int value = ((c1 << 4) & 0xff) | c2; + data[dataIndex] = (byte) value; + } + + return data; + } +} diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java index 8ed687540..36e070aa6 100644 --- a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java +++ b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java @@ -172,7 +172,7 @@ public class BaseEncoding { // always start at 24. bit int bitPos = 23; - // always write 24 bits (6 bytes * 4 multiples), but this will also write into the padding + // always write 24 bits (6 bits * 4), but this will also write into the padding // we will fix this by writing the padding as has been calculated previously while (bitPos >= 0) { diff --git a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java index c5d5483f8..9fc47cded 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java @@ -95,6 +95,25 @@ public class ByteHelper { return sb.toString(); } + /** + * Formats the given byte array to a binary string, separating each byte by a space + * + * @param b + * 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(" "); + } + + return sb.toString(); + } + /** * Formats the given integer to a binary string, each byte is separated by a space * diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java new file mode 100644 index 000000000..cebe427be --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.utils.helper; + +import static ch.eitchnet.utils.helper.BaseDecoding.fromBase16; +import static ch.eitchnet.utils.helper.BaseDecoding.fromBase32; +import static ch.eitchnet.utils.helper.BaseDecoding.fromBase32Hex; +import static ch.eitchnet.utils.helper.BaseDecoding.fromBase64; +import junit.framework.Assert; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + * + */ +public class BaseDecodingTest { + private static final Logger logger = LoggerFactory.getLogger(BaseDecodingTest.class); + + @Test + public void testBase64() { + Assert.assertEquals("", new String(fromBase64("".getBytes()))); + Assert.assertEquals("f", new String(fromBase64("Zg==".getBytes()))); + Assert.assertEquals("fo", new String(fromBase64("Zm8=".getBytes()))); + Assert.assertEquals("foo", new String(fromBase64("Zm9v".getBytes()))); + Assert.assertEquals("foob", new String(fromBase64("Zm9vYg==".getBytes()))); + Assert.assertEquals("fooba", new String(fromBase64("Zm9vYmE=".getBytes()))); + Assert.assertEquals("foobar", new String(fromBase64("Zm9vYmFy".getBytes()))); + + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = 'Z'; + } + long start = System.nanoTime(); + for (int i = 0; i < 200; i++) { + fromBase64(bytes); + } + long end = System.nanoTime(); + logger.info("Decoding 200MB Base64 took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32() { + Assert.assertEquals("", new String(fromBase32("".getBytes()))); + Assert.assertEquals("f", new String(fromBase32("MY======".getBytes()))); + Assert.assertEquals("fo", new String(fromBase32("MZXQ====".getBytes()))); + Assert.assertEquals("foo", new String(fromBase32("MZXW6===".getBytes()))); + Assert.assertEquals("foob", new String(fromBase32("MZXW6YQ=".getBytes()))); + Assert.assertEquals("fooba", new String(fromBase32("MZXW6YTB".getBytes()))); + Assert.assertEquals("foobar", new String(fromBase32("MZXW6YTBOI======".getBytes()))); + + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = 'M'; + } + long start = System.nanoTime(); + for (int i = 0; i < 200; i++) { + fromBase32(bytes); + } + long end = System.nanoTime(); + logger.info("Decoding 200MB Base32 took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32Hex() { + Assert.assertEquals("", new String(fromBase32Hex("".getBytes()))); + Assert.assertEquals("f", new String(fromBase32Hex("CO======".getBytes()))); + Assert.assertEquals("fo", new String(fromBase32Hex("CPNG====".getBytes()))); + Assert.assertEquals("foo", new String(fromBase32Hex("CPNMU===".getBytes()))); + Assert.assertEquals("foob", new String(fromBase32Hex("CPNMUOG=".getBytes()))); + Assert.assertEquals("fooba", new String(fromBase32Hex("CPNMUOJ1".getBytes()))); + Assert.assertEquals("foobar", new String(fromBase32Hex("CPNMUOJ1E8======".getBytes()))); + + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = 'C'; + } + long start = System.nanoTime(); + for (int i = 0; i < 200; i++) { + fromBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Decoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase16() { + Assert.assertEquals("", new String(fromBase16("".getBytes()))); + Assert.assertEquals("f", new String(fromBase16("66".getBytes()))); + Assert.assertEquals("fo", new String(fromBase16("666F".getBytes()))); + Assert.assertEquals("foo", new String(fromBase16("666F6F".getBytes()))); + Assert.assertEquals("foob", new String(fromBase16("666F6F62".getBytes()))); + Assert.assertEquals("fooba", new String(fromBase16("666F6F6261".getBytes()))); + Assert.assertEquals("foobar", new String(fromBase16("666F6F626172".getBytes()))); + + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = '6'; + } + long start = System.nanoTime(); + for (int i = 0; i < 200; i++) { + fromBase16(bytes); + } + long end = System.nanoTime(); + logger.info("Decoding 200MB Base16 took " + StringHelper.formatNanoDuration(end - start)); + } +} diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java index d6c85ef13..c30417014 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -28,6 +28,8 @@ import static ch.eitchnet.utils.helper.BaseEncoding.toBase64; import junit.framework.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @author Robert von Burg @@ -35,7 +37,7 @@ import org.junit.Test; */ public class BaseEncodingTest { - // private static final Logger logger = LoggerFactory.getLogger(BaseEncodingTest.class); + private static final Logger logger = LoggerFactory.getLogger(BaseEncodingTest.class); @Test public void testBase64() { @@ -46,6 +48,14 @@ public class BaseEncodingTest { Assert.assertEquals("Zm9vYg==", new String(toBase64("foob".getBytes()))); Assert.assertEquals("Zm9vYmE=", new String(toBase64("fooba".getBytes()))); Assert.assertEquals("Zm9vYmFy", new String(toBase64("foobar".getBytes()))); + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase64(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base64 took " + StringHelper.formatNanoDuration(end - start)); } @Test @@ -57,6 +67,14 @@ public class BaseEncodingTest { Assert.assertEquals("MZXW6YQ=", new String(toBase32("foob".getBytes()))); Assert.assertEquals("MZXW6YTB", new String(toBase32("fooba".getBytes()))); Assert.assertEquals("MZXW6YTBOI======", new String(toBase32("foobar".getBytes()))); + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32 took " + StringHelper.formatNanoDuration(end - start)); } @Test @@ -68,6 +86,14 @@ public class BaseEncodingTest { Assert.assertEquals("CPNMUOG=", new String(toBase32Hex("foob".getBytes()))); Assert.assertEquals("CPNMUOJ1", new String(toBase32Hex("fooba".getBytes()))); Assert.assertEquals("CPNMUOJ1E8======", new String(toBase32Hex("foobar".getBytes()))); + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); } @Test @@ -79,5 +105,13 @@ public class BaseEncodingTest { Assert.assertEquals("666F6F62", new String(toBase16("foob".getBytes()))); Assert.assertEquals("666F6F6261", new String(toBase16("fooba".getBytes()))); Assert.assertEquals("666F6F626172", new String(toBase16("foobar".getBytes()))); + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase16(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base16 took " + StringHelper.formatNanoDuration(end - start)); } } diff --git a/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java b/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java new file mode 100644 index 000000000..3e22f4331 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.utils.helper; + +import java.util.HashMap; +import java.util.Map; + +/** + * Simple helper class to generate the reverse alphabets for {@link BaseDecoding} + * + * @author Robert von Burg + */ +public class GenerateReverseBaseEncodingAlphabets { + + public static void main(String[] args) { + + System.out.println(generateReverseAlphabet("REV_BASE_16", BaseEncoding.BASE_16)); + System.out.println(generateReverseAlphabet("REV_BASE_32", BaseEncoding.BASE_32)); + System.out.println(generateReverseAlphabet("REV_BASE_32_CROCKFORD", BaseEncoding.BASE_32_CROCKFORD)); + System.out.println(generateReverseAlphabet("REV_BASE_32_DMEDIA", BaseEncoding.BASE_32_DMEDIA)); + System.out.println(generateReverseAlphabet("REV_BASE_32_HEX", BaseEncoding.BASE_32_HEX)); + System.out.println(generateReverseAlphabet("REV_BASE_64", BaseEncoding.BASE_64)); + System.out.println(generateReverseAlphabet("REV_BASE_64_SAFE", BaseEncoding.BASE_64_SAFE)); + } + + public static String generateReverseAlphabet(String name, byte[] alphabet) { + + Map valueToIndex = new HashMap(); + for (byte i = 0; i < alphabet.length; i++) { + Byte value = Byte.valueOf(i); + Byte key = Byte.valueOf(alphabet[value]); + if (valueToIndex.containsKey(key)) + throw new RuntimeException("Alphabet hast twice the same value " + key + " at index " + value); + valueToIndex.put(key, value); + } + + StringBuilder sb = new StringBuilder(); + sb.append("private static final byte[] " + name + " = { "); + + Byte minusOne = Byte.valueOf((byte) -1); + for (int i = 0; i < 128; i++) { + Byte index = Byte.valueOf((byte) i); + Byte value = valueToIndex.get(index); + if (value == null) + sb.append(minusOne.toString()); + else + sb.append(value.toString()); + + if (i < 127) + sb.append(", "); + } + + sb.append(" };"); + + return sb.toString(); + } +} From 4723608d62333249ad613d6de1c1014c7d5ffec3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Feb 2013 11:49:44 +0100 Subject: [PATCH 132/457] [Minor] fixed failing tests on Java 1.7 due to expected execution --- .../xmlpers/test/XmlPersistenceTest.java | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java index bd19e047c..12aeb4314 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java @@ -25,6 +25,7 @@ import java.util.Set; import org.junit.Assert; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,10 +77,25 @@ public class XmlPersistenceTest { } /** + * Tests the following story: + *
      + *
    • create object
    • + *
    • read object
    • + *
    • update object
    • + *
    • remove object
    • + *
    * */ @Test - public void testCreate() { + public void testPersistenceStory() { + + createObject(); + readObject(); + updateObject(); + removeObject(); + } + + private void createObject() { try { XmlPersistenceTest.logger.info("Trying to create..."); @@ -100,11 +116,7 @@ public class XmlPersistenceTest { } } - /** - * - */ - @Test - public void testRead() { + private void readObject() { try { XmlPersistenceTest.logger.info("Trying to read..."); @@ -123,11 +135,7 @@ public class XmlPersistenceTest { } } - /** - * - */ - @Test - public void testUpdate() { + private void updateObject() { try { XmlPersistenceTest.logger.info("Trying to update an object..."); @@ -152,11 +160,7 @@ public class XmlPersistenceTest { } } - /** - * - */ - @Test - public void testRemove() { + private void removeObject() { XmlPersistenceTest.logger.info("Trying to remove..."); @@ -174,7 +178,7 @@ public class XmlPersistenceTest { /** * */ - @Test(expected = XmlPersistenceExecption.class) + @Test public void testQueryFail() { try { @@ -183,6 +187,10 @@ public class XmlPersistenceTest { MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); XmlPersistenceTest.logger.info("Found MyClass: " + myClass); XmlPersistenceTest.logger.info("Done querying removed object"); + } catch (XmlPersistenceExecption e) { + Assert.assertEquals("Wrong error message. Expected error that object does not exist", + "No object exists for ch.eitchnet.xmlpers.test.impl.MyClass / @subtype / @id", + e.getLocalizedMessage()); } finally { XmlPersistenceTest.persistenceHandler.commitTx(); } @@ -213,13 +221,14 @@ public class XmlPersistenceTest { } } -// /** -// * -// */ -// @Test -// public void testQueryFromTo() { -// Assert.fail("Not yet implemented"); -// } + /** + * + */ + @Test + @Ignore + public void testQueryFromTo() { + Assert.fail("Not yet implemented"); + } /** * From 706d2413acbd6aa0099c0f5256cce100a08c9b04 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 1 Mar 2013 18:45:59 +0100 Subject: [PATCH 133/457] [New] added tests for the dmedia base32 encoding Since Jason DeRose now put up some test vectors for the dmedia base32 encoding, i thought it wise to add these in a test --- .../utils/helper/BaseDecodingTest.java | 23 +++++++++++++++++++ .../utils/helper/BaseEncodingTest.java | 22 ++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java index cebe427be..0c8cf9a19 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java @@ -23,8 +23,10 @@ package ch.eitchnet.utils.helper; import static ch.eitchnet.utils.helper.BaseDecoding.fromBase16; import static ch.eitchnet.utils.helper.BaseDecoding.fromBase32; +import static ch.eitchnet.utils.helper.BaseDecoding.fromBase32Dmedia; import static ch.eitchnet.utils.helper.BaseDecoding.fromBase32Hex; import static ch.eitchnet.utils.helper.BaseDecoding.fromBase64; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; import junit.framework.Assert; import org.junit.Test; @@ -104,6 +106,27 @@ public class BaseDecodingTest { logger.info("Decoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); } + @Test + public void testBase32Dmedia() { + + Assert.assertEquals("", new String(fromBase32Dmedia("".getBytes()))); + Assert.assertEquals("binary foo", new String(fromBase32Dmedia("FCNPVRELI7J9FUUI".getBytes()))); + Assert.assertEquals("f", new String(fromBase32Dmedia("FR======".getBytes()))); + Assert.assertEquals("fo", new String(fromBase32Dmedia("FSQJ====".getBytes()))); + Assert.assertEquals("foo", new String(fromBase32Dmedia("FSQPX===".getBytes()))); + Assert.assertEquals("foob", new String(fromBase32Dmedia("FSQPXRJ=".getBytes()))); + Assert.assertEquals("fooba", new String(fromBase32Dmedia("FSQPXRM4".getBytes()))); + Assert.assertEquals("foobar", new String(fromBase32Dmedia("FSQPXRM4HB======".getBytes()))); + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32Dmedia took " + StringHelper.formatNanoDuration(end - start)); + } + @Test public void testBase16() { Assert.assertEquals("", new String(fromBase16("".getBytes()))); diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java index c30417014..0a7042dae 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -24,6 +24,7 @@ package ch.eitchnet.utils.helper; import static ch.eitchnet.utils.helper.BaseEncoding.toBase16; import static ch.eitchnet.utils.helper.BaseEncoding.toBase32; import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Dmedia; import static ch.eitchnet.utils.helper.BaseEncoding.toBase64; import junit.framework.Assert; @@ -95,6 +96,27 @@ public class BaseEncodingTest { long end = System.nanoTime(); logger.info("Encoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); } + + @Test + public void testBase32Dmedia() { + + Assert.assertEquals("", new String(toBase32Dmedia("".getBytes()))); + Assert.assertEquals("FCNPVRELI7J9FUUI", new String(toBase32Dmedia("binary foo".getBytes()))); + Assert.assertEquals("FR======", new String(toBase32Dmedia("f".getBytes()))); + Assert.assertEquals("FSQJ====", new String(toBase32Dmedia("fo".getBytes()))); + Assert.assertEquals("FSQPX===", new String(toBase32Dmedia("foo".getBytes()))); + Assert.assertEquals("FSQPXRJ=", new String(toBase32Dmedia("foob".getBytes()))); + Assert.assertEquals("FSQPXRM4", new String(toBase32Dmedia("fooba".getBytes()))); + Assert.assertEquals("FSQPXRM4HB======", new String(toBase32Dmedia("foobar".getBytes()))); + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32Dmedia took " + StringHelper.formatNanoDuration(end - start)); + } @Test public void testBase16() { From 85bab370a57a8b9d8ac33f768dc82a69e964433b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 10 Mar 2013 22:09:43 +0100 Subject: [PATCH 134/457] [Major] Refactored the base encoding Merged the BaseEncoding and BaseDecoding classes as it seems a better fit. Added isBaseEncoded methos for checking --- .../eitchnet/utils/helper/ArraysHelper.java | 38 ++ .../eitchnet/utils/helper/BaseDecoding.java | 483 -------------- .../eitchnet/utils/helper/BaseEncoding.java | 621 +++++++++++++++++- .../utils/helper/BaseDecodingTest.java | 82 +-- .../utils/helper/BaseEncodingTest.java | 76 +-- 5 files changed, 734 insertions(+), 566 deletions(-) create mode 100644 src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java delete mode 100644 src/main/java/ch/eitchnet/utils/helper/BaseDecoding.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java b/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java new file mode 100644 index 000000000..c54ac3e58 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.utils.helper; + +/** + * @author Robert von Burg + * + */ +public class ArraysHelper { + + public static boolean contains(byte[] bytes, byte searchByte) { + for (byte b : bytes) { + if (b == searchByte) + return true; + } + + return false; + } +} diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseDecoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseDecoding.java deleted file mode 100644 index 412486a36..000000000 --- a/src/main/java/ch/eitchnet/utils/helper/BaseDecoding.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the ch.eitchnet.java.utils. - * - * ch.eitchnet.java.utils 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. - * - * ch.eitchnet.java.utils 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 ch.eitchnet.java.utils. If not, see - * . - */ -package ch.eitchnet.utils.helper; - -/** - *

    - * This class implements the decoding part of RFC 4648 https://tools.ietf.org/html/rfc4648. For the encoding see - * {@link BaseEncoding} - *

    - * - *

    - * All versions are implemented: Base64 with URL and file name safe encoding, Base32 with HEX and Base16 - *

    - * - * @author Robert von Burg - */ -public class BaseDecoding { - - // private static final Logger logger = LoggerFactory.getLogger(BaseDecoding.class); - - private static final byte PAD = '='; - - // these reverse base encoding alphabets were generated from the actual alphabet - - private static final byte[] REV_BASE_16 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - private static final byte[] REV_BASE_32 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - private static final byte[] REV_BASE_32_CROCKFORD = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, - 16, 17, -1, 18, 19, -1, 20, 21, -1, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1 }; - private static final byte[] REV_BASE_32_DMEDIA = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, -1, -1, -1, -1, -1, -1, -1, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1 }; - private static final byte[] REV_BASE_32_HEX = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, - 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1 }; - private static final byte[] REV_BASE_64 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, - -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; - private static final byte[] REV_BASE_64_SAFE = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, - 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, - -1, -1 }; - - public static byte[] fromBase64(byte[] bytes) { - return fromBase64(REV_BASE_64, bytes); - } - - public static byte[] fromBase64Safe(byte[] bytes) { - return fromBase64(REV_BASE_64_SAFE, bytes); - } - - public static byte[] fromBase32(byte[] bytes) { - return fromBase32(REV_BASE_32, bytes); - } - - public static byte[] fromBase32Hex(byte[] bytes) { - return fromBase32(REV_BASE_32_HEX, bytes); - } - - public static byte[] fromBase32Dmedia(byte[] bytes) { - return fromBase32(REV_BASE_32_DMEDIA, bytes); - } - - public static byte[] fromBase32Crockford(byte[] bytes) { - return fromBase32(REV_BASE_32_CROCKFORD, bytes); - } - - public static byte[] fromBase16(byte[] bytes) { - return fromBase16(REV_BASE_16, bytes); - } - - /** - * Decodes the given Base64 encoded data to the original data set - * - * @param alphabet - * the 64-bit alphabet to use - * @param bytes - * the bytes to decode - * - * @return the decoded data - */ - public static byte[] fromBase64(byte[] alphabet, byte[] bytes) { - int inputLength = bytes.length; - if (inputLength == 0) - return new byte[0]; - if ((inputLength % 4) != 0) { - throw new RuntimeException("The input bytes to be decoded must be multiples of 4, but is multiple of " - + (inputLength % 4)); - } - - if (alphabet.length != 128) - throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); - - // find how much padding we have - int nrOfBytesPadding = 0; - if (bytes[inputLength - 1] == PAD) { - int end = inputLength - 1; - while (bytes[end] == PAD) - end--; - if (end != inputLength - 1) - nrOfBytesPadding = inputLength - 1 - end; - } - - int inputDataLength = inputLength - nrOfBytesPadding; - int dataLengthBits = inputDataLength * 6; // 6 bits data for every 8 bits inputs - // multiples of 6 required - // truncating is no problem due to the input having padding to have multiples of 32 bits - dataLengthBits = dataLengthBits - (dataLengthBits % 8); - int dataLengthBytes = dataLengthBits / 8; - - // f => Zg== - // fo => Zm8= - // foo => Zm9v - - // we want to write as much as 24 bits in multiples of 6. - // these multiples of 6 are read from multiples of 8 - // i.e. we discard 2 bits from every 8 bits input - // thus we need to read 24 / 6 = 4 bytes - - byte[] data = new byte[dataLengthBytes]; - int dataPos = 0; - - // but we simply ignore the padding - int bytesPos = 0; - while (bytesPos < inputDataLength) { - int remaining = inputDataLength - bytesPos; - - long bits; - if (remaining >= 4) { - - // XXX check each byte value so that it is legal - - bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // - | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // - | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6) // - | (alphabet[bytes[bytesPos++]] & 63); - - } else if (remaining == 3) { - - bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // - | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // - | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6); - - } else if (remaining == 2) { - - bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // - | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12); -// -// long b; -// byte a; -// a = bytes[0]; -// logger.info("1 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); -// b = (byte) (alphabet[a] & 63); -// logger.info("1 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); -// a = bytes[1]; -// logger.info("2 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); -// b = (byte) (alphabet[a] & 63); -// logger.info("2 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); - - } else if (remaining == 1) { - - bits = ((alphabet[bytes[bytesPos++]] & 63) << 18); - - } else { - - bits = 0L; - } - - // we can truncate to 8 bits - int toWrite = remaining >= 4 ? 3 : remaining * 6 / 8; - // max is always 3 bytes data from 4 bytes input - -// logger.info("toWrite: " + toWrite + ", remaining: " + remaining); -// logger.info("bits: " + ByteHelper.asBinary(bits)); - - // always start at 24. bit - int bitPos = 23; - // always write 24 bits (8 bits * n bytes) - for (int i = 0; i < toWrite; i++) { - - byte value = 0; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; - bitPos--; - data[dataPos] = value; - dataPos++; - } - } - - return data; - } - -// -// /** -// * @param a -// * @return -// */ -// private static int indexOf(byte a) { -// for (int i = 0; i < BaseEncoding.BASE_64.length; i++) { -// if (BaseEncoding.BASE_64[i] == a) -// return i; -// } -// return -1; -// } -// -// private static int indexOf1(byte a) { -// for (int i = 0; i < REV_BASE_64.length; i++) { -// if (REV_BASE_64[i] == a) -// return i; -// } -// return -1; -// } - - /** - * Decodes the given Base32 encoded data to the original data set - * - * @param alphabet - * the 32-bit alphabet to use - * @param bytes - * the bytes to decode - * - * @return the decoded data - */ - public static byte[] fromBase32(byte[] alphabet, byte[] bytes) { - int inputLength = bytes.length; - if (inputLength == 0) - return new byte[0]; - if ((inputLength % 8) != 0) { - throw new RuntimeException("The input bytes to be decoded must be multiples of 8, but is multiple of " - + (inputLength % 8)); - } - - if (alphabet.length != 128) - throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); - - // find how much padding we have - int nrOfBytesPadding = 0; - if (bytes[inputLength - 1] == PAD) { - int end = inputLength - 1; - while (bytes[end] == PAD) - end--; - if (end != inputLength - 1) - nrOfBytesPadding = inputLength - 1 - end; - } - - int inputDataLength = inputLength - nrOfBytesPadding; - int dataLengthBits = inputDataLength * 5; // 5 bits data for every 8 bits inputs - // multiples of 8 required - // truncating is no problem due to the input having padding to have multiples of 40 bits - dataLengthBits = dataLengthBits - (dataLengthBits % 8); - int dataLengthBytes = dataLengthBits / 8; - -// logger.info("Input " + inputLength + " bytes, InputData " + inputDataLength + " bytes, Padding: " -// + nrOfBytesPadding + " bytes, dataLength: " + dataLengthBits + " bits, dataLengthBytes: " -// + dataLengthBytes + " bytes"); -// logger.info(ByteHelper.asBinary(bytes)); - - // we want to write as much as 40 bits in multiples of 5. - // these multiples of 5 are read from multiples of 8 - // i.e. we discard 3 bits from every 8 bits input - // thus we need to read 40 / 5 = 8 bytes - - byte[] data = new byte[dataLengthBytes]; - int dataPos = 0; - - // but we simply ignore the padding - int bytesPos = 0; - while (bytesPos < inputDataLength) { - int remaining = inputDataLength - bytesPos; - - long bits; - if (remaining >= 8) { - - // XXX check each byte value so that it is legal - - bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5) // - | (alphabet[bytes[bytesPos++]] & 31); - - } else if (remaining >= 7) { - - bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5); - - } else if (remaining == 6) { - - bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10); - - } else if (remaining == 5) { - - bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15); - - } else if (remaining == 4) { - - bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20); - - } else if (remaining == 3) { - - bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25); - - } else if (remaining == 2) { - - bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // - | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30); - - } else if (remaining == 1) { - - bits = ((alphabet[bytes[bytesPos++]] & 31) << 35); - - } else { - - bits = 0L; - } - - // we can truncate to 8 bits - int toRead = remaining >= 8 ? 5 : remaining * 5 / 8; - // max is always 5 bytes data from 8 bytes input - - // always start at 40. bit - int bitPos = 39; - // always write 40 bits (5 bytes * 8 bits) - for (int i = 0; i < toRead; i++) { - - byte value = 0; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; - bitPos--; - value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; - bitPos--; - data[dataPos] = value; - dataPos++; - } - } - - return data; - } - - /** - * Decodes the given Base16 encoded data to the original data set - * - * @param alphabet - * the 16-bit alphabet to use - * @param bytes - * the bytes to decode - * - * @return the decoded data - */ - public static byte[] fromBase16(byte[] alphabet, byte[] bytes) { - if (bytes.length == 0) - return new byte[0]; - if ((bytes.length % 2) != 0) { - throw new RuntimeException("The input bytes to be decoded must be multiples of 4, but is multiple of " - + (bytes.length % 4)); - } - - if (alphabet.length != 128) - throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); - - int dataLength = bytes.length / 2; - - byte[] data = new byte[dataLength]; - for (int i = 0; i < bytes.length;) { - - byte b1 = bytes[i++]; - byte b2 = bytes[i++]; - - if (b1 < 0) { - throw new IllegalArgumentException("Value at index " + (i - 2) + " is not in range of alphabet (0-127)" - + b1); - } - if (b2 < 0) { - throw new IllegalArgumentException("Value at index " + (i - 1) + " is not in range of alphabet (0-127)" - + b2); - } - - byte c1 = alphabet[b1]; - byte c2 = alphabet[b2]; - - if (c1 == -1) { - throw new IllegalArgumentException("Value at index " + (i - 2) - + " is referencing illegal value in alphabet: " + b1); - } - if (c2 == -1) { - throw new IllegalArgumentException("Value at index " + (i - 2) - + " is referencing illegal value in alphabet: " + b2); - } - - int dataIndex = (i / 2) - 1; - int value = ((c1 << 4) & 0xff) | c2; - data[dataIndex] = (byte) value; - } - - return data; - } -} diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java index 36e070aa6..49d5f0e9e 100644 --- a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java +++ b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java @@ -23,8 +23,7 @@ package ch.eitchnet.utils.helper; /** *

    - * This class implements the encoding part of RFC 4648 https://tools.ietf.org/html/rfc4648. For the decoding see - * {@link BaseDecoding}. + * This class implements the encoding and decoding of RFC 4648 https://tools.ietf.org/html/rfc4648. *

    * *

    @@ -38,13 +37,25 @@ package ch.eitchnet.utils.helper; * *

    * + *

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

    + * + *

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

    + * * @author Robert von Burg */ public class BaseEncoding { // private static final Logger logger = LoggerFactory.getLogger(BaseEncoding.class); - private static final byte PAD = '='; + private static final int PADDING_64 = 2; + private static final int PADDING_32 = 6; + + public static final byte PAD = '='; static final byte[] BASE_16 = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; @@ -70,34 +81,280 @@ public class BaseEncoding { 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; + // these reverse base encoding alphabets were generated from the actual alphabet + + static final byte[] REV_BASE_16 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_32 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_32_CROCKFORD = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, + -1, 18, 19, -1, 20, 21, -1, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1 }; + + static final byte[] REV_BASE_32_DMEDIA = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, -1, -1, -1, -1, -1, -1, -1, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_32_HEX = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_64 = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, + 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; + + static final byte[] REV_BASE_64_SAFE = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; + public static byte[] toBase64(byte[] bytes) { return toBase64(BASE_64, bytes); } + public static String toBase64(String data) { + return toBase64(BASE_64, data); + } + public static byte[] toBase64Safe(byte[] bytes) { return toBase64(BASE_64_SAFE, bytes); } + public static String toBase64Safe(String data) { + return toBase64(BASE_64_SAFE, data); + } + + public static String toBase64(byte[] alphabet, String data) { + return new String(toBase64(alphabet, data.getBytes())); + } + public static byte[] toBase32(byte[] bytes) { return toBase32(BASE_32, bytes); } + public static String toBase32(String data) { + return toBase32(BASE_32, data); + } + public static byte[] toBase32Hex(byte[] bytes) { return toBase32(BASE_32_HEX, bytes); } + public static String toBase32Hex(String data) { + return toBase32(BASE_32_HEX, data); + } + public static byte[] toBase32Dmedia(byte[] bytes) { return toBase32(BASE_32_DMEDIA, bytes); } + public static String toBase32Dmedia(String data) { + return toBase32(BASE_32_DMEDIA, data); + } + public static byte[] toBase32Crockford(byte[] bytes) { return toBase32(BASE_32_CROCKFORD, bytes); } + public static String toBase32Crockford(String data) { + return toBase32(BASE_32_CROCKFORD, data); + } + + public static String toBase32(byte[] alphabet, String data) { + return new String(toBase32(alphabet, data.getBytes())); + } + public static byte[] toBase16(byte[] bytes) { return toBase16(BASE_16, bytes); } + public static String toBase16(String data) { + return toBase16(BASE_16, data); + } + + public static String toBase16(byte[] alphabet, String data) { + return new String(toBase16(alphabet, data.getBytes())); + } + + public static byte[] fromBase64(byte[] bytes) { + return fromBase64(REV_BASE_64, bytes); + } + + public static String fromBase64(String data) { + return fromBase64(REV_BASE_64, data); + } + + public static String fromBase64Safe(String data) { + return fromBase64(REV_BASE_64_SAFE, data); + } + + public static String fromBase64(byte[] alphabet, String data) { + return new String(fromBase64(alphabet, data.getBytes())); + } + + public static byte[] fromBase32(byte[] bytes) { + return fromBase32(REV_BASE_32, bytes); + } + + public static String fromBase32(String data) { + return fromBase32(REV_BASE_32, data); + } + + public static byte[] fromBase32Hex(byte[] bytes) { + return fromBase32(REV_BASE_32_HEX, bytes); + } + + public static String fromBase32Hex(String data) { + return fromBase32(REV_BASE_32_HEX, data); + } + + public static byte[] fromBase32Dmedia(byte[] bytes) { + return fromBase32(REV_BASE_32_DMEDIA, bytes); + } + + public static String fromBase32Dmedia(String data) { + return fromBase32(REV_BASE_32_DMEDIA, data); + } + + public static byte[] fromBase32Crockford(byte[] bytes) { + return fromBase32(REV_BASE_32_CROCKFORD, bytes); + } + + public static String fromBase32Crockford(String data) { + return fromBase32(REV_BASE_32_CROCKFORD, data); + } + + public static String fromBase32(byte[] alphabet, String data) { + return new String(fromBase32(alphabet, data.getBytes())); + } + + public static byte[] fromBase16(byte[] bytes) { + return fromBase16(REV_BASE_16, bytes); + } + + public static String fromBase16(String data) { + return fromBase16(REV_BASE_16, data); + } + + public static String fromBase16(byte[] alphabet, String data) { + return new String(fromBase16(alphabet, data.getBytes())); + } + + public static boolean isBase64(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_64, bytes, PADDING_64); + } + + public static boolean isBase64(String data) { + return isEncodedByAlphabet(REV_BASE_64, data, PADDING_64); + } + + public static boolean isBase64Safe(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_64_SAFE, bytes, PADDING_64); + } + + public static boolean isBase64Safe(String data) { + return isEncodedByAlphabet(REV_BASE_64_SAFE, data, PADDING_64); + } + + public static boolean isBase32(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_32, bytes, PADDING_32); + } + + public static boolean isBase32(String data) { + return isEncodedByAlphabet(REV_BASE_32, data, PADDING_32); + } + + public static boolean isBase32Hex(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_32_HEX, bytes, PADDING_32); + } + + public static boolean isBase32Hex(String data) { + return isEncodedByAlphabet(REV_BASE_32_HEX, data, PADDING_32); + } + + public static boolean isBase32Crockford(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_32_CROCKFORD, bytes, PADDING_32); + } + + public static boolean isBase32Crockford(String data) { + return isEncodedByAlphabet(REV_BASE_32_CROCKFORD, data, PADDING_32); + } + + public static boolean isBase32Dmedia(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_32_DMEDIA, bytes, PADDING_32); + } + + public static boolean isBase32Dmedia(String data) { + return isEncodedByAlphabet(REV_BASE_32_DMEDIA, data, PADDING_32); + } + + public static boolean isBase16(byte[] bytes) { + return isEncodedByAlphabet(REV_BASE_16, bytes, 0); + } + + public static boolean isBase16(String data) { + return isEncodedByAlphabet(REV_BASE_16, data, 0); + } + + public static boolean isEncodedByAlphabet(byte[] alphabet, String data, int padding) { + return isEncodedByAlphabet(alphabet, data.getBytes(), padding); + } + + /** + * @param alphabet + * @param bytes + * @param maxPadding + * + * @return + */ + public static boolean isEncodedByAlphabet(byte[] alphabet, byte[] bytes, int maxPadding) { + if (bytes.length == 0) + return true; + + int paddingStart = 0; + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + if (b < 0 || b > alphabet.length) + return false; + + byte c = alphabet[b]; + if (c == -1) { + + if (b == PAD && maxPadding != 0) { + if (paddingStart == 0) + paddingStart = i; + + continue; + } + + return false; + } + } + + if (paddingStart != 0 && paddingStart < (bytes.length - maxPadding)) + return false; + + return true; + } + /** * Encodes the given data to a 64-bit alphabet encoding. Thus the passed alphabet can be any arbitrary alphabet * @@ -338,7 +595,7 @@ public class BaseEncoding { * * @return the encoded data */ - public static byte[] toBase16( byte[] alphabet, byte[] bytes) { + public static byte[] toBase16(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) return new byte[0]; if (alphabet.length != 16) @@ -378,4 +635,360 @@ public class BaseEncoding { return txt; } + + /** + * Decodes the given Base64 encoded data to the original data set + * + * @param alphabet + * the 64-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase64(byte[] alphabet, byte[] bytes) { + int inputLength = bytes.length; + if (inputLength == 0) + return new byte[0]; + if ((inputLength % 4) != 0) { + throw new RuntimeException("The input bytes to be decoded must be multiples of 4, but is multiple of " + + (inputLength % 4)); + } + + if (alphabet.length != 128) + throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + + if (!isEncodedByAlphabet(alphabet, bytes, PADDING_64)) + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); + + // find how much padding we have + int nrOfBytesPadding = 0; + if (bytes[inputLength - 1] == PAD) { + int end = inputLength - 1; + while (bytes[end] == PAD) + end--; + if (end != inputLength - 1) + nrOfBytesPadding = inputLength - 1 - end; + } + + int inputDataLength = inputLength - nrOfBytesPadding; + int dataLengthBits = inputDataLength * 6; // 6 bits data for every 8 bits inputs + // multiples of 6 required + // truncating is no problem due to the input having padding to have multiples of 32 bits + dataLengthBits = dataLengthBits - (dataLengthBits % 8); + int dataLengthBytes = dataLengthBits / 8; + + // f => Zg== + // fo => Zm8= + // foo => Zm9v + + // we want to write as much as 24 bits in multiples of 6. + // these multiples of 6 are read from multiples of 8 + // i.e. we discard 2 bits from every 8 bits input + // thus we need to read 24 / 6 = 4 bytes + + byte[] data = new byte[dataLengthBytes]; + int dataPos = 0; + + // but we simply ignore the padding + int bytesPos = 0; + while (bytesPos < inputDataLength) { + int remaining = inputDataLength - bytesPos; + + long bits; + if (remaining >= 4) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6) // + | (alphabet[bytes[bytesPos++]] & 63); + + } else if (remaining == 3) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 6); + + } else if (remaining == 2) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 63) << 18) // + | ((long) (alphabet[bytes[bytesPos++]] & 63) << 12); +// +// long b; +// byte a; +// a = bytes[0]; +// logger.info("1 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); +// b = (byte) (alphabet[a] & 63); +// logger.info("1 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); +// a = bytes[1]; +// logger.info("2 a: " + a + " " + ((char) a) + " - " + ByteHelper.asBinary(a) + " " + indexOf(a)); +// b = (byte) (alphabet[a] & 63); +// logger.info("2 b: " + b + " - " + ((char) b) + " - " + ByteHelper.asBinary(b)); + + } else if (remaining == 1) { + + bits = ((alphabet[bytes[bytesPos++]] & 63) << 18); + + } else { + + bits = 0L; + } + + // we can truncate to 8 bits + int toWrite = remaining >= 4 ? 3 : remaining * 6 / 8; + // max is always 3 bytes data from 4 bytes input + +// logger.info("toWrite: " + toWrite + ", remaining: " + remaining); +// logger.info("bits: " + ByteHelper.asBinary(bits)); + + // always start at 24. bit + int bitPos = 23; + // always write 24 bits (8 bits * n bytes) + for (int i = 0; i < toWrite; i++) { + + byte value = 0; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + data[dataPos] = value; + dataPos++; + } + } + + return data; + } + + /** + * Decodes the given Base32 encoded data to the original data set + * + * @param alphabet + * the 32-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase32(byte[] alphabet, byte[] bytes) { + int inputLength = bytes.length; + if (inputLength == 0) + return new byte[0]; + if ((inputLength % 8) != 0) { + throw new RuntimeException("The input bytes to be decoded must be multiples of 8, but is multiple of " + + (inputLength % 8)); + } + + if (alphabet.length != 128) + throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + + if (!isEncodedByAlphabet(alphabet, bytes, PADDING_32)) + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); + + // find how much padding we have + int nrOfBytesPadding = 0; + if (bytes[inputLength - 1] == PAD) { + int end = inputLength - 1; + while (bytes[end] == PAD) + end--; + if (end != inputLength - 1) + nrOfBytesPadding = inputLength - 1 - end; + } + + int inputDataLength = inputLength - nrOfBytesPadding; + int dataLengthBits = inputDataLength * 5; // 5 bits data for every 8 bits inputs + // multiples of 8 required + // truncating is no problem due to the input having padding to have multiples of 40 bits + dataLengthBits = dataLengthBits - (dataLengthBits % 8); + int dataLengthBytes = dataLengthBits / 8; + +// logger.info("Input " + inputLength + " bytes, InputData " + inputDataLength + " bytes, Padding: " +// + nrOfBytesPadding + " bytes, dataLength: " + dataLengthBits + " bits, dataLengthBytes: " +// + dataLengthBytes + " bytes"); +// logger.info(ByteHelper.asBinary(bytes)); + + // we want to write as much as 40 bits in multiples of 5. + // these multiples of 5 are read from multiples of 8 + // i.e. we discard 3 bits from every 8 bits input + // thus we need to read 40 / 5 = 8 bytes + + byte[] data = new byte[dataLengthBytes]; + int dataPos = 0; + + // but we simply ignore the padding + int bytesPos = 0; + while (bytesPos < inputDataLength) { + int remaining = inputDataLength - bytesPos; + + long bits; + if (remaining >= 8) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5) // + | (alphabet[bytes[bytesPos++]] & 31); + + } else if (remaining >= 7) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 5); + + } else if (remaining == 6) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 10); + + } else if (remaining == 5) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 15); + + } else if (remaining == 4) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 20); + + } else if (remaining == 3) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 25); + + } else if (remaining == 2) { + + bits = ((long) (alphabet[bytes[bytesPos++]] & 31) << 35) // + | ((long) (alphabet[bytes[bytesPos++]] & 31) << 30); + + } else if (remaining == 1) { + + bits = ((alphabet[bytes[bytesPos++]] & 31) << 35); + + } else { + + bits = 0L; + } + + // we can truncate to 8 bits + int toRead = remaining >= 8 ? 5 : remaining * 5 / 8; + // max is always 5 bytes data from 8 bytes input + + // always start at 40. bit + int bitPos = 39; + // always write 40 bits (5 bytes * 8 bits) + for (int i = 0; i < toRead; i++) { + + byte value = 0; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 128; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 64; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 32; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 16; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 8; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 4; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 2; + bitPos--; + value |= ((bits >>> bitPos) & 1) == 0 ? 0 : 1; + bitPos--; + data[dataPos] = value; + dataPos++; + } + } + + return data; + } + + /** + * Decodes the given Base16 encoded data to the original data set + * + * @param alphabet + * the 16-bit alphabet to use + * @param bytes + * the bytes to decode + * + * @return the decoded data + */ + public static byte[] fromBase16(byte[] alphabet, byte[] bytes) { + if (bytes.length == 0) + return new byte[0]; + if ((bytes.length % 2) != 0) { + throw new RuntimeException("The input bytes to be decoded must be multiples of 4, but is multiple of " + + (bytes.length % 4)); + } + + if (alphabet.length != 128) + throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + + if (!isEncodedByAlphabet(alphabet, bytes, 0)) + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); + + int dataLength = bytes.length / 2; + + byte[] data = new byte[dataLength]; + for (int i = 0; i < bytes.length;) { + + byte b1 = bytes[i++]; + byte b2 = bytes[i++]; + + if (b1 < 0) { + throw new IllegalArgumentException("Value at index " + (i - 2) + " is not in range of alphabet (0-127)" + + b1); + } + if (b2 < 0) { + throw new IllegalArgumentException("Value at index " + (i - 1) + " is not in range of alphabet (0-127)" + + b2); + } + + byte c1 = alphabet[b1]; + byte c2 = alphabet[b2]; + + if (c1 == -1) { + throw new IllegalArgumentException("Value at index " + (i - 2) + + " is referencing illegal value in alphabet: " + b1); + } + if (c2 == -1) { + throw new IllegalArgumentException("Value at index " + (i - 2) + + " is referencing illegal value in alphabet: " + b2); + } + + int dataIndex = (i / 2) - 1; + int value = ((c1 << 4) & 0xff) | c2; + data[dataIndex] = (byte) value; + } + + return data; + } } \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java index 0c8cf9a19..78a9ea566 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java @@ -21,11 +21,11 @@ */ package ch.eitchnet.utils.helper; -import static ch.eitchnet.utils.helper.BaseDecoding.fromBase16; -import static ch.eitchnet.utils.helper.BaseDecoding.fromBase32; -import static ch.eitchnet.utils.helper.BaseDecoding.fromBase32Dmedia; -import static ch.eitchnet.utils.helper.BaseDecoding.fromBase32Hex; -import static ch.eitchnet.utils.helper.BaseDecoding.fromBase64; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase16; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32Dmedia; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32Hex; +import static ch.eitchnet.utils.helper.BaseEncoding.fromBase64; import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; import junit.framework.Assert; @@ -42,13 +42,13 @@ public class BaseDecodingTest { @Test public void testBase64() { - Assert.assertEquals("", new String(fromBase64("".getBytes()))); - Assert.assertEquals("f", new String(fromBase64("Zg==".getBytes()))); - Assert.assertEquals("fo", new String(fromBase64("Zm8=".getBytes()))); - Assert.assertEquals("foo", new String(fromBase64("Zm9v".getBytes()))); - Assert.assertEquals("foob", new String(fromBase64("Zm9vYg==".getBytes()))); - Assert.assertEquals("fooba", new String(fromBase64("Zm9vYmE=".getBytes()))); - Assert.assertEquals("foobar", new String(fromBase64("Zm9vYmFy".getBytes()))); + Assert.assertEquals("", fromBase64("")); + Assert.assertEquals("f", fromBase64("Zg==")); + Assert.assertEquals("fo", fromBase64("Zm8=")); + Assert.assertEquals("foo", fromBase64("Zm9v")); + Assert.assertEquals("foob", fromBase64("Zm9vYg==")); + Assert.assertEquals("fooba", fromBase64("Zm9vYmE=")); + Assert.assertEquals("foobar", fromBase64("Zm9vYmFy")); byte[] bytes = new byte[1024 * 1024]; for (int i = 0; i < bytes.length; i++) { @@ -64,13 +64,13 @@ public class BaseDecodingTest { @Test public void testBase32() { - Assert.assertEquals("", new String(fromBase32("".getBytes()))); - Assert.assertEquals("f", new String(fromBase32("MY======".getBytes()))); - Assert.assertEquals("fo", new String(fromBase32("MZXQ====".getBytes()))); - Assert.assertEquals("foo", new String(fromBase32("MZXW6===".getBytes()))); - Assert.assertEquals("foob", new String(fromBase32("MZXW6YQ=".getBytes()))); - Assert.assertEquals("fooba", new String(fromBase32("MZXW6YTB".getBytes()))); - Assert.assertEquals("foobar", new String(fromBase32("MZXW6YTBOI======".getBytes()))); + Assert.assertEquals("", fromBase32("")); + Assert.assertEquals("f", fromBase32("MY======")); + Assert.assertEquals("fo", fromBase32("MZXQ====")); + Assert.assertEquals("foo", fromBase32("MZXW6===")); + Assert.assertEquals("foob", fromBase32("MZXW6YQ=")); + Assert.assertEquals("fooba", fromBase32("MZXW6YTB")); + Assert.assertEquals("foobar", fromBase32("MZXW6YTBOI======")); byte[] bytes = new byte[1024 * 1024]; for (int i = 0; i < bytes.length; i++) { @@ -86,13 +86,13 @@ public class BaseDecodingTest { @Test public void testBase32Hex() { - Assert.assertEquals("", new String(fromBase32Hex("".getBytes()))); - Assert.assertEquals("f", new String(fromBase32Hex("CO======".getBytes()))); - Assert.assertEquals("fo", new String(fromBase32Hex("CPNG====".getBytes()))); - Assert.assertEquals("foo", new String(fromBase32Hex("CPNMU===".getBytes()))); - Assert.assertEquals("foob", new String(fromBase32Hex("CPNMUOG=".getBytes()))); - Assert.assertEquals("fooba", new String(fromBase32Hex("CPNMUOJ1".getBytes()))); - Assert.assertEquals("foobar", new String(fromBase32Hex("CPNMUOJ1E8======".getBytes()))); + Assert.assertEquals("", fromBase32Hex("")); + Assert.assertEquals("f", fromBase32Hex("CO======")); + Assert.assertEquals("fo", fromBase32Hex("CPNG====")); + Assert.assertEquals("foo", fromBase32Hex("CPNMU===")); + Assert.assertEquals("foob", fromBase32Hex("CPNMUOG=")); + Assert.assertEquals("fooba", fromBase32Hex("CPNMUOJ1")); + Assert.assertEquals("foobar", fromBase32Hex("CPNMUOJ1E8======")); byte[] bytes = new byte[1024 * 1024]; for (int i = 0; i < bytes.length; i++) { @@ -109,14 +109,14 @@ public class BaseDecodingTest { @Test public void testBase32Dmedia() { - Assert.assertEquals("", new String(fromBase32Dmedia("".getBytes()))); - Assert.assertEquals("binary foo", new String(fromBase32Dmedia("FCNPVRELI7J9FUUI".getBytes()))); - Assert.assertEquals("f", new String(fromBase32Dmedia("FR======".getBytes()))); - Assert.assertEquals("fo", new String(fromBase32Dmedia("FSQJ====".getBytes()))); - Assert.assertEquals("foo", new String(fromBase32Dmedia("FSQPX===".getBytes()))); - Assert.assertEquals("foob", new String(fromBase32Dmedia("FSQPXRJ=".getBytes()))); - Assert.assertEquals("fooba", new String(fromBase32Dmedia("FSQPXRM4".getBytes()))); - Assert.assertEquals("foobar", new String(fromBase32Dmedia("FSQPXRM4HB======".getBytes()))); + Assert.assertEquals("", fromBase32Dmedia("")); + Assert.assertEquals("binary foo", fromBase32Dmedia("FCNPVRELI7J9FUUI")); + Assert.assertEquals("f", fromBase32Dmedia("FR======")); + Assert.assertEquals("fo", fromBase32Dmedia("FSQJ====")); + Assert.assertEquals("foo", fromBase32Dmedia("FSQPX===")); + Assert.assertEquals("foob", fromBase32Dmedia("FSQPXRJ=")); + Assert.assertEquals("fooba", fromBase32Dmedia("FSQPXRM4")); + Assert.assertEquals("foobar", fromBase32Dmedia("FSQPXRM4HB======")); long start = System.nanoTime(); byte[] bytes = new byte[1024 * 1024]; @@ -129,13 +129,13 @@ public class BaseDecodingTest { @Test public void testBase16() { - Assert.assertEquals("", new String(fromBase16("".getBytes()))); - Assert.assertEquals("f", new String(fromBase16("66".getBytes()))); - Assert.assertEquals("fo", new String(fromBase16("666F".getBytes()))); - Assert.assertEquals("foo", new String(fromBase16("666F6F".getBytes()))); - Assert.assertEquals("foob", new String(fromBase16("666F6F62".getBytes()))); - Assert.assertEquals("fooba", new String(fromBase16("666F6F6261".getBytes()))); - Assert.assertEquals("foobar", new String(fromBase16("666F6F626172".getBytes()))); + Assert.assertEquals("", fromBase16("")); + Assert.assertEquals("f", fromBase16("66")); + Assert.assertEquals("fo", fromBase16("666F")); + Assert.assertEquals("foo", fromBase16("666F6F")); + Assert.assertEquals("foob", fromBase16("666F6F62")); + Assert.assertEquals("fooba", fromBase16("666F6F6261")); + Assert.assertEquals("foobar", fromBase16("666F6F626172")); byte[] bytes = new byte[1024 * 1024]; for (int i = 0; i < bytes.length; i++) { diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java index 0a7042dae..7a2b3052f 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -42,13 +42,13 @@ public class BaseEncodingTest { @Test public void testBase64() { - Assert.assertEquals("", new String(toBase64("".getBytes()))); - Assert.assertEquals("Zg==", new String(toBase64("f".getBytes()))); - Assert.assertEquals("Zm8=", new String(toBase64("fo".getBytes()))); - Assert.assertEquals("Zm9v", new String(toBase64("foo".getBytes()))); - Assert.assertEquals("Zm9vYg==", new String(toBase64("foob".getBytes()))); - Assert.assertEquals("Zm9vYmE=", new String(toBase64("fooba".getBytes()))); - Assert.assertEquals("Zm9vYmFy", new String(toBase64("foobar".getBytes()))); + Assert.assertEquals("", toBase64("")); + Assert.assertEquals("Zg==", toBase64("f")); + Assert.assertEquals("Zm8=", toBase64("fo")); + Assert.assertEquals("Zm9v", toBase64("foo")); + Assert.assertEquals("Zm9vYg==", toBase64("foob")); + Assert.assertEquals("Zm9vYmE=", toBase64("fooba")); + Assert.assertEquals("Zm9vYmFy", toBase64("foobar")); long start = System.nanoTime(); byte[] bytes = new byte[1024 * 1024]; @@ -61,13 +61,13 @@ public class BaseEncodingTest { @Test public void testBase32() { - Assert.assertEquals("", new String(toBase32("".getBytes()))); - Assert.assertEquals("MY======", new String(toBase32("f".getBytes()))); - Assert.assertEquals("MZXQ====", new String(toBase32("fo".getBytes()))); - Assert.assertEquals("MZXW6===", new String(toBase32("foo".getBytes()))); - Assert.assertEquals("MZXW6YQ=", new String(toBase32("foob".getBytes()))); - Assert.assertEquals("MZXW6YTB", new String(toBase32("fooba".getBytes()))); - Assert.assertEquals("MZXW6YTBOI======", new String(toBase32("foobar".getBytes()))); + Assert.assertEquals("", toBase32("")); + Assert.assertEquals("MY======", toBase32("f")); + Assert.assertEquals("MZXQ====", toBase32("fo")); + Assert.assertEquals("MZXW6===", toBase32("foo")); + Assert.assertEquals("MZXW6YQ=", toBase32("foob")); + Assert.assertEquals("MZXW6YTB", toBase32("fooba")); + Assert.assertEquals("MZXW6YTBOI======", toBase32("foobar")); long start = System.nanoTime(); byte[] bytes = new byte[1024 * 1024]; @@ -80,13 +80,13 @@ public class BaseEncodingTest { @Test public void testBase32Hex() { - Assert.assertEquals("", new String(toBase32Hex("".getBytes()))); - Assert.assertEquals("CO======", new String(toBase32Hex("f".getBytes()))); - Assert.assertEquals("CPNG====", new String(toBase32Hex("fo".getBytes()))); - Assert.assertEquals("CPNMU===", new String(toBase32Hex("foo".getBytes()))); - Assert.assertEquals("CPNMUOG=", new String(toBase32Hex("foob".getBytes()))); - Assert.assertEquals("CPNMUOJ1", new String(toBase32Hex("fooba".getBytes()))); - Assert.assertEquals("CPNMUOJ1E8======", new String(toBase32Hex("foobar".getBytes()))); + Assert.assertEquals("", toBase32Hex("")); + Assert.assertEquals("CO======", toBase32Hex("f")); + Assert.assertEquals("CPNG====", toBase32Hex("fo")); + Assert.assertEquals("CPNMU===", toBase32Hex("foo")); + Assert.assertEquals("CPNMUOG=", toBase32Hex("foob")); + Assert.assertEquals("CPNMUOJ1", toBase32Hex("fooba")); + Assert.assertEquals("CPNMUOJ1E8======", toBase32Hex("foobar")); long start = System.nanoTime(); byte[] bytes = new byte[1024 * 1024]; @@ -96,18 +96,18 @@ public class BaseEncodingTest { long end = System.nanoTime(); logger.info("Encoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); } - + @Test public void testBase32Dmedia() { - - Assert.assertEquals("", new String(toBase32Dmedia("".getBytes()))); - Assert.assertEquals("FCNPVRELI7J9FUUI", new String(toBase32Dmedia("binary foo".getBytes()))); - Assert.assertEquals("FR======", new String(toBase32Dmedia("f".getBytes()))); - Assert.assertEquals("FSQJ====", new String(toBase32Dmedia("fo".getBytes()))); - Assert.assertEquals("FSQPX===", new String(toBase32Dmedia("foo".getBytes()))); - Assert.assertEquals("FSQPXRJ=", new String(toBase32Dmedia("foob".getBytes()))); - Assert.assertEquals("FSQPXRM4", new String(toBase32Dmedia("fooba".getBytes()))); - Assert.assertEquals("FSQPXRM4HB======", new String(toBase32Dmedia("foobar".getBytes()))); + + Assert.assertEquals("", toBase32Dmedia("")); + Assert.assertEquals("FCNPVRELI7J9FUUI", toBase32Dmedia("binary foo")); + Assert.assertEquals("FR======", toBase32Dmedia("f")); + Assert.assertEquals("FSQJ====", toBase32Dmedia("fo")); + Assert.assertEquals("FSQPX===", toBase32Dmedia("foo")); + Assert.assertEquals("FSQPXRJ=", toBase32Dmedia("foob")); + Assert.assertEquals("FSQPXRM4", toBase32Dmedia("fooba")); + Assert.assertEquals("FSQPXRM4HB======", toBase32Dmedia("foobar")); long start = System.nanoTime(); byte[] bytes = new byte[1024 * 1024]; @@ -120,13 +120,13 @@ public class BaseEncodingTest { @Test public void testBase16() { - Assert.assertEquals("", new String(toBase16("".getBytes()))); - Assert.assertEquals("66", new String(toBase16("f".getBytes()))); - Assert.assertEquals("666F", new String(toBase16("fo".getBytes()))); - Assert.assertEquals("666F6F", new String(toBase16("foo".getBytes()))); - Assert.assertEquals("666F6F62", new String(toBase16("foob".getBytes()))); - Assert.assertEquals("666F6F6261", new String(toBase16("fooba".getBytes()))); - Assert.assertEquals("666F6F626172", new String(toBase16("foobar".getBytes()))); + Assert.assertEquals("", toBase16("")); + Assert.assertEquals("66", toBase16("f")); + Assert.assertEquals("666F", toBase16("fo")); + Assert.assertEquals("666F6F", toBase16("foo")); + Assert.assertEquals("666F6F62", toBase16("foob")); + Assert.assertEquals("666F6F6261", toBase16("fooba")); + Assert.assertEquals("666F6F626172", toBase16("foobar")); long start = System.nanoTime(); byte[] bytes = new byte[1024 * 1024]; From e5872299145daa3cfd752872e1540b38667db658 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 10 Mar 2013 23:58:06 +0100 Subject: [PATCH 135/457] [New] added new StringHelper.hash*AsHex() methods as a nice API --- .../eitchnet/utils/helper/StringHelper.java | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index bbd6def33..e345c7157 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -39,8 +39,8 @@ public class StringHelper { /** * Hex char table for fast calculating of hex value */ - private static final byte[] HEX_CHAR_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', - 'f' }; + private static final byte[] HEX_CHAR_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', + 'd', 'e', 'f' }; /** * Converts each byte of the given byte array to a HEX value and returns the concatenation of these values @@ -94,7 +94,19 @@ public class StringHelper { } /** - * Generates the MD5 Hash of a string Use {@link StringHelper#getHexString(byte[])} to convert the byte array to a + * 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(StringHelper.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 @@ -119,6 +131,18 @@ public class StringHelper { return StringHelper.hash("MD5", bytes); } + /** + * 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(StringHelper.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 @@ -145,6 +169,18 @@ public class StringHelper { return StringHelper.hash("SHA-1", bytes); } + /** + * 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(StringHelper.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 From a94981e7419383893ed9582641ea39fabc537623 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 11 Mar 2013 21:23:50 +0100 Subject: [PATCH 136/457] [New] Added new methods to FileHelper to read and write byte arrays. Fixed missing close on existing methods --- .../ch/eitchnet/utils/helper/FileHelper.java | 89 ++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 0bcbbf1f6..a175dc4c5 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -46,13 +46,57 @@ import org.slf4j.LoggerFactory; */ 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", + humanizeFileSize(MAX_FILE_SIZE), humanizeFileSize(file.length()))); + + byte[] data = new byte[(int) file.length()]; + int pos = 0; + + BufferedInputStream in = null; + try { + 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()); + } catch (IOException e) { + throw new RuntimeException("Could not read file " + file.getAbsolutePath()); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + FileHelper.logger.error("Failed to close InputStream: " + e.getLocalizedMessage()); + } + } + } + + 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) { @@ -86,16 +130,51 @@ public class FileHelper { } } + /** + * 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) { + + BufferedOutputStream out = null; + try { + + out = new BufferedOutputStream(new FileOutputStream(dstFile)); + out.write(bytes); + + } catch (FileNotFoundException e) { + throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException("Could not write to file " + dstFile.getAbsolutePath()); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + FileHelper.logger.error("Failed to close OutputStream: " + e.getLocalizedMessage()); + } + } + } + } + /** * 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) { + + BufferedWriter bufferedwriter = null; try { - BufferedWriter bufferedwriter = new BufferedWriter(new FileWriter(dstFile)); + bufferedwriter = new BufferedWriter(new FileWriter(dstFile)); bufferedwriter.write(string); bufferedwriter.close(); @@ -104,6 +183,14 @@ public class FileHelper { throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath()); } catch (IOException e) { throw new RuntimeException("Could not write to file " + dstFile.getAbsolutePath()); + } finally { + if (bufferedwriter != null) { + try { + bufferedwriter.close(); + } catch (IOException e) { + FileHelper.logger.error("Failed to close BufferedWriter: " + e.getLocalizedMessage()); + } + } } } From cca9426358f5f25905721e349139df50d08e921e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 11 Mar 2013 22:50:05 +0100 Subject: [PATCH 137/457] [Minor] changed alphabet of StringHelper.getHexString() to have capital letters, this corresponds to RFC4648 --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index e345c7157..c6cb8841b 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -39,8 +39,8 @@ public class StringHelper { /** * Hex char table for fast calculating of hex value */ - private static final byte[] HEX_CHAR_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', - 'd', 'e', 'f' }; + private static final byte[] HEX_CHAR_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', + 'D', 'E', 'F' }; /** * Converts each byte of the given byte array to a HEX value and returns the concatenation of these values From 841fedafdfbf97633bb467a70241e1d04aade301 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 13 Mar 2013 17:13:27 +0100 Subject: [PATCH 138/457] Update README.md Added information which classes exist and what they are for. Added how to build description --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index acb872905..1ed357451 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,44 @@ ch.eitchnet.java.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 6 +* JUnit 4.10 (only during tests) +* slf4j 1.7.2 +* slf4j-log4j bindings (only during tests) + +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 From 3310a8d4711dbf9f8ad431e233f2f79779572254 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 13 Mar 2013 17:29:28 +0100 Subject: [PATCH 139/457] Update README.md Described the dependencies, features and how to build of XmlPers --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 816af7f1a..2496f583d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,35 @@ ch.eitchnet.java.xmlpers ======================== +Generic Java XML persistence layer. Implemented to be light-weight and simple to use -Generic Java XML persistence layer. Implemented to be light-weight and simple to use \ No newline at end of file +Dependencies +------------------------ +XmlPers is built by Maven3 and has very few external dependencies. The current dependencies are: +* the Java Runtime Environment 6 +* ch.eitchnet.utils +* slf4j 1.7.2 +* slf4j-log4j bindings (only during tests) +* JUnit 4.10 (only during tests) + +Features +------------------------ +The idea behind XmlPers is to have a very lightweight database where each object is saved in its own XML file. + +The model for XmlPers is that for each persistable class the following information is available: +* Type (e.g. the class name) +* Optional Sub Type (e.g. some type in your class) +* Id + +This is not forced on the model itself, but in the DAO. Persisting changes is done by delegating to XmlFilePersister and the DAO must convert the object to a XML Document. + +See the tests for a reference implementation. + +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 + * ch.eitchnet.utils is installed in your local Maven Repository +* Clone repository and change path to root +* Run maven: + * mvn clean install From 17525a41d5f7a0a44c0867d22a0ca59ef0ad410e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 17 Mar 2013 12:08:12 +0100 Subject: [PATCH 140/457] [Bugfix] fixed failing test due to uppercase/lowercase change of BASE16 --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index ec247fa45..da7e2a1b5 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -153,7 +153,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - Assert.assertEquals("22d4ba39605d49c758184d9bd63beae5ccf8786f3dabbab45cd9f59c2afbcbd0", fileHash); + Assert.assertEquals("22D4BA39605D49C758184D9BD63BEAE5CCF8786F3DABBAB45CD9F59C2AFBCBD0", fileHash); } @Test @@ -214,6 +214,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - Assert.assertEquals("8e1e82278162f21b1654c2e059570bbcb3cb63b053c1dd784bc8e225e8cfd04f", fileHash); + Assert.assertEquals("8E1E82278162F21B1654C2E059570BBCB3CB63B053C1DD784BC8E225E8CFD04F", fileHash); } } From 44cb14803602a1c4a8481c0907aae627ba6cba42 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Mar 2013 12:23:29 +0100 Subject: [PATCH 141/457] [Minor] code cleanup - cleaned up JavaDoc for some classes - changed equals() to not use instanceOf but class==class - set some fields private which were package scope --- .../privilege/model/internal/Privilege.java | 9 +---- .../internal/PrivilegeContainerModel.java | 23 ++++++++--- .../privilege/model/internal/Role.java | 8 +--- .../privilege/model/internal/Session.java | 39 ++++++++----------- .../privilege/model/internal/User.java | 8 +--- 5 files changed, 40 insertions(+), 47 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java index 55c4fe1a5..2bb456c0b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java @@ -166,6 +166,8 @@ public final class Privilege { } /** + * Returns a string representation of this object displaying its concrete type and its values + * * @see java.lang.Object#toString() */ @Override @@ -179,9 +181,6 @@ public final class Privilege { return builder.toString(); } - /** - * @see java.lang.Object#hashCode() - */ @Override public int hashCode() { final int prime = 31; @@ -190,9 +189,6 @@ public final class Privilege { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) @@ -209,5 +205,4 @@ public final class Privilege { return false; return true; } - } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index a38ba337b..c8e44343b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -29,13 +29,19 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; public class PrivilegeContainerModel { - String encryptionHandlerClassName; - Map encryptionHandlerParameterMap; - String persistenceHandlerClassName; - Map persistenceHandlerParameterMap; - Map parameterMap; + private String encryptionHandlerClassName; + private Map encryptionHandlerParameterMap; + private String persistenceHandlerClassName; + private Map persistenceHandlerParameterMap; + private Map parameterMap; + private Map> policies; - private Map> policies = new HashMap>(); + /** + * Default constructor + */ + public PrivilegeContainerModel() { + this.policies = new HashMap>(); + } /** * @return the parameterMap @@ -146,6 +152,11 @@ public class PrivilegeContainerModel { return this.policies; } + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index d6825717b..70cd2b11d 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -133,6 +133,8 @@ public final class Role { } /** + * Returns a string representation of this object displaying its concrete type and its values + * * @see java.lang.Object#toString() */ @Override @@ -146,9 +148,6 @@ public final class Role { return builder.toString(); } - /** - * @see java.lang.Object#hashCode() - */ @Override public int hashCode() { final int prime = 31; @@ -157,9 +156,6 @@ public final class Role { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java index 4abcd08d5..2ab1e45df 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java @@ -126,8 +126,23 @@ public final class Session { } /** - * @see java.lang.Object#hashCode() + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Session [sessionId="); + builder.append(this.sessionId); + builder.append(", username="); + builder.append(this.username); + builder.append(", loginTime="); + builder.append(this.loginTime); + builder.append("]"); + return builder.toString(); + } + @Override public int hashCode() { final int prime = 31; @@ -140,16 +155,13 @@ public final class Session { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; - if (!(obj instanceof Session)) + if (getClass() != obj.getClass()) return false; Session other = (Session) obj; if (this.authPassword == null) { @@ -176,21 +188,4 @@ public final class Session { return false; return true; } - - /** - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Session [sessionId="); - builder.append(this.sessionId); - builder.append(", username="); - builder.append(this.username); - builder.append(", loginTime="); - builder.append(this.loginTime); - builder.append("]"); - return builder.toString(); - } - } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 0428adf5f..eb50d4a58 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -240,6 +240,8 @@ public final class User { } /** + * Returns a string representation of this object displaying its concrete type and its values + * * @see java.lang.Object#toString() */ @Override @@ -263,9 +265,6 @@ public final class User { return builder.toString(); } - /** - * @see java.lang.Object#hashCode() - */ @Override public int hashCode() { final int prime = 31; @@ -274,9 +273,6 @@ public final class User { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) From 9261494667608d36ebae64e80d33e3fefc0d985c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 26 Mar 2013 19:42:17 +0100 Subject: [PATCH 142/457] [Minor] cleaned up doc and implemented missing toString() and equals() A number classes had JavaDocs with @see parameters on overriding methods. This was changed so that they were removed, as they are not needed. In some classes the toString() methods were missing, as well as equals() and hashcode() they were now added --- .../handler/DefaultEncryptionHandler.java | 14 -- .../handler/DefaultPrivilegeHandler.java | 121 +++++------------- .../privilege/handler/EncryptionHandler.java | 1 - .../privilege/handler/PersistenceHandler.java | 1 - .../privilege/handler/SystemUserAction.java | 12 +- .../handler/XmlPersistenceHandler.java | 37 ++---- .../helper/CertificateThreadLocal.java | 8 +- .../eitchnet/privilege/model/Certificate.java | 36 +++--- .../privilege/model/PrivilegeRep.java | 47 +++++++ .../ch/eitchnet/privilege/model/RoleRep.java | 41 +++++- .../ch/eitchnet/privilege/model/UserRep.java | 51 ++++++++ .../internal/PrivilegeContainerModel.java | 7 + .../privilege/model/internal/User.java | 1 - .../privilege/policy/DefaultPrivilege.java | 5 + .../eitchnet/privilege/xml/ElementParser.java | 1 + 15 files changed, 225 insertions(+), 158 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index fb614673e..237fee200 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -44,7 +44,6 @@ import ch.eitchnet.privilege.helper.XmlConstants; * * * @author Robert von Burg - * */ public class DefaultEncryptionHandler implements EncryptionHandler { @@ -63,9 +62,6 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ private String hashAlgorithm; - /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) - */ @Override public String convertToHash(String string) { try { @@ -79,9 +75,6 @@ public class DefaultEncryptionHandler implements EncryptionHandler { } } - /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) - */ @Override public String convertToHash(byte[] bytes) { try { @@ -95,21 +88,14 @@ public class DefaultEncryptionHandler implements EncryptionHandler { } } - /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#nextToken() - */ @Override public String nextToken() { byte[] bytes = new byte[16]; this.secureRandom.nextBytes(bytes); String randomString = new String(bytes); - //String randomString = new BigInteger(80, secureRandom).toString(32); // 80 big integer bits = 16 chars return randomString; } - /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) - */ @Override public void initialize(Map parameterMap) { diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index e7eab35f1..0235341b0 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -63,7 +63,6 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * * * @author Robert von Burg - * */ public class DefaultPrivilegeHandler implements PrivilegeHandler { @@ -117,9 +116,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private boolean autoPersistOnPasswordChange; - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) - */ @Override public RoleRep getRole(String roleName) { Role role = this.persistenceHandler.getRole(roleName); @@ -128,9 +124,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return role.asRoleRep(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getUser(java.lang.String) - */ @Override public UserRep getUser(String username) { User user = this.persistenceHandler.getUser(username); @@ -174,9 +167,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return policy; } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#queryUsers(ch.eitchnet.privilege.model.UserRep) - */ @Override public List queryUsers(UserRep selectorRep) { @@ -268,22 +258,30 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @param selPropertyMap + * Checks if the given properties contains values which are contained in the selectionMap. If the selectionMap is + * null or empty, then true is returned. If a key/value pair from the selectionMap is not in the properties, then + * false is returned + * + * @param selectionMap + * the map defining the expected properties * @param properties - * @return + * the properties which must be a sub set of selectionMap to have this method return true + * + * @return If the selectionMap is null or empty, then true is returned. If a key/value pair from the selectionMap is + * not in the properties, then false is returned */ - private boolean isSelectedByProperty(Map selPropertyMap, Map properties) { + private boolean isSelectedByProperty(Map selectionMap, Map properties) { - if (selPropertyMap == null) + if (selectionMap == null) return true; - if (selPropertyMap.isEmpty() && properties.isEmpty()) + if (selectionMap.isEmpty() && properties.isEmpty()) return true; - for (String selKey : selPropertyMap.keySet()) { + for (String selKey : selectionMap.keySet()) { String value = properties.get(selKey); - if (value == null || !value.equals(selPropertyMap.get(selKey))) + if (value == null || !value.equals(selectionMap.get(selKey))) return false; } @@ -291,22 +289,25 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @param selRoles + * Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is null or + * empty, then true is returned, otherwise false + * + * @param selectionRoles + * the required roles * @param roles - * @return + * the roles to check if they contain the selectionRoles + * + * @return Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is + * null or empty, then true is returned, otherwise false */ - private boolean isSelectedByRole(Set selRoles, Set roles) { + private boolean isSelectedByRole(Set selectionRoles, Set roles) { - if (selRoles == null) + if (selectionRoles == null) return true; - return roles.containsAll(selRoles); + return roles.containsAll(selectionRoles); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.RoleRep) - */ @Override public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { @@ -323,10 +324,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceRole(role); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep, java.lang.String) - */ @Override public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) { try { @@ -357,10 +354,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilegeOnRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, ch.eitchnet.privilege.model.PrivilegeRep) - */ @Override public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { @@ -394,10 +387,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceRole(newRole); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ @Override public void addRoleToUser(Certificate certificate, String username, String roleName) { @@ -433,10 +422,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ @Override public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { @@ -467,10 +452,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceRole(newRole); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ @Override public RoleRep removeRole(Certificate certificate, String roleName) { @@ -487,10 +468,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return removedRole.asRoleRep(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ @Override public void removeRoleFromUser(Certificate certificate, String username, String roleName) { @@ -520,10 +497,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ @Override public UserRep removeUser(Certificate certificate, String username) { @@ -542,10 +515,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocale(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Locale) - */ @Override public void setUserLocale(Certificate certificate, String username, Locale locale) { @@ -566,10 +535,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserName(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String, java.lang.String) - */ @Override public void setUserName(Certificate certificate, String username, String firstname, String surname) { @@ -590,10 +555,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ @Override public void setUserPassword(Certificate certificate, String username, byte[] password) { try { @@ -643,10 +604,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserState(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, ch.eitchnet.privilege.model.UserState) - */ @Override public void setUserState(Certificate certificate, String username, UserState state) { @@ -743,9 +700,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return certificate; } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#invalidateSession(ch.eitchnet.privilege.model.Certificate) - */ @Override public boolean invalidateSession(Certificate certificate) { @@ -852,7 +806,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} * * @param role + * the role which wants to perform the action * @param restrictable + * the {@link Restrictable} which is to be checked for authorization */ private boolean actionAllowed(Role role, Restrictable restrictable) { @@ -892,9 +848,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return true; } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) - */ @Override public void isCertificateValid(Certificate certificate) { @@ -931,9 +884,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // everything is ok } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validateIsPrivilegeAdmin(ch.eitchnet.privilege.model.Certificate) - */ @Override public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { @@ -972,9 +922,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#persist(ch.eitchnet.privilege.model.Certificate) - */ @Override public boolean persist(Certificate certificate) { @@ -1062,10 +1009,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @author Robert von Burg */ protected class CertificateSessionPair { + /** * The {@link Session} */ public final Session session; + /** * The {@link Certificate} */ @@ -1086,7 +1035,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** + * Passwords should not be kept as strings, as string are immutable, this method thus clears the byte array so that + * the password is not in memory anymore + * * @param password + * the byte array containing the passwort which is to be set to zeroes */ private void clearPassword(byte[] password) { if (password != null) { @@ -1096,10 +1049,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#runAsSystem(java.lang.String, - * ch.eitchnet.privilege.handler.SystemUserAction) - */ @Override public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java index b25dbd82a..f6b734213 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -26,7 +26,6 @@ import java.util.Map; * for certificates and so forth * * @author Robert von Burg - * */ public interface EncryptionHandler { diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index 498f6e8e7..557d1fc48 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -41,7 +41,6 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; *

    * * @author Robert von Burg - * */ public interface PersistenceHandler { diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java index 0aac16d1d..d59aba87b 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -22,10 +22,20 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.model.Certificate; /** - * @author Robert von Burg + * With this interface system actions, which are to be performed in an automated fashion, i.e. by cron jobs, can be + * implemented and then the authorized execution can be delegated to + * {@link PrivilegeHandler#runAsSystem(String, SystemUserAction)} * + * @author Robert von Burg */ public interface SystemUserAction { + /** + * This method will be called by the {@link PrivilegeHandler} when an authorized {@link Certificate} has been + * generated to allow this action to properly validate its execution + * + * @param certificate + * the {@link Certificate} which was generated for a valid system user + */ public void execute(Certificate certificate); } diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index b40820caa..84ddf5435 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -58,9 +58,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { private String modelPath; - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getAllUsers() - */ @Override public List getAllUsers() { synchronized (this.userMap) { @@ -68,9 +65,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getAllRoles() - */ @Override public List getAllRoles() { synchronized (this.roleMap) { @@ -78,25 +72,16 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getUser(java.lang.String) - */ @Override public User getUser(String username) { return this.userMap.get(username); } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getRole(java.lang.String) - */ @Override public Role getRole(String roleName) { return this.roleMap.get(roleName); } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeUser(java.lang.String) - */ @Override public User removeUser(String username) { User user = this.userMap.remove(username); @@ -104,9 +89,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { return user; } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeRole(java.lang.String) - */ @Override public Role removeRole(String roleName) { Role role = this.roleMap.remove(roleName); @@ -114,18 +96,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { return role; } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.internal.User) - */ @Override public void addOrReplaceUser(User user) { this.userMap.put(user.getUsername(), user); this.userMapDirty = true; } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.internal.Role) - */ @Override public void addOrReplaceRole(Role role) { this.roleMap.put(role.getName(), role); @@ -133,7 +109,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#initialize(java.util.Map) + * Initializes this {@link XmlPersistenceHandler} by reading the following parameters: + *
      + *
    • {@link XmlConstants#XML_PARAM_BASE_PATH}
    • + *
    • {@link XmlConstants#XML_PARAM_MODEL_FILE}
    • + *
    */ @Override public void initialize(Map paramsMap) { @@ -164,7 +144,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#reload() + * Reads the XML configuration files which contain the model. Which configuration files are parsed was defined in + * the while calling {@link #initialize(Map)} + * + * @see #initialize(Map) */ @Override public boolean reload() { @@ -223,7 +206,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() + * Writes the model to the XML files. Where the files are written to was defined in the {@link #initialize(Map)} */ @Override public boolean persist() { diff --git a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java index 161687258..fe75d24c0 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java +++ b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java @@ -22,8 +22,10 @@ package ch.eitchnet.privilege.helper; import ch.eitchnet.privilege.model.Certificate; /** - * @author Robert von Burg + * This {@link ThreadLocal} holds a reference to the {@link Certificate} which allows any code segment to perform + * further authorization before executing * + * @author Robert von Burg */ public class CertificateThreadLocal extends ThreadLocal { @@ -36,11 +38,11 @@ public class CertificateThreadLocal extends ThreadLocal { return CertificateThreadLocal.instance; } - public static Certificate getCert() { + public static Certificate getCertificate() { return CertificateThreadLocal.instance.get(); } - public static void setCert(Certificate certificate) { + public static void setCertificate(Certificate certificate) { CertificateThreadLocal.instance.set(certificate); } } diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 0d165e57c..ca7710313 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -146,8 +146,23 @@ public final class Certificate implements Serializable { } /** - * @see java.lang.Object#hashCode() + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Certificate [sessionId="); + builder.append(this.sessionId); + builder.append(", username="); + builder.append(this.username); + builder.append(", locale="); + builder.append(this.locale); + builder.append("]"); + return builder.toString(); + } + @Override public int hashCode() { final int prime = 31; @@ -160,9 +175,6 @@ public final class Certificate implements Serializable { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) @@ -199,20 +211,4 @@ public final class Certificate implements Serializable { return false; return true; } - - /** - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Certificate [sessionId="); - builder.append(this.sessionId); - builder.append(", username="); - builder.append(this.username); - builder.append(", locale="); - builder.append(this.locale); - builder.append("]"); - return builder.toString(); - } } diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index ca3b596d9..c74725a80 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -184,4 +184,51 @@ public class PrivilegeRep implements Serializable { } } } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrivilegeRep [name="); + builder.append(this.name); + builder.append(", policy="); + builder.append(this.policy); + builder.append(", allAllowed="); + builder.append(this.allAllowed); + builder.append(", denyList="); + builder.append((this.denyList == null ? "null" : this.denyList.size())); + builder.append(", allowList="); + builder.append((this.allowList == null ? "null" : this.allowList.size())); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.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; + PrivilegeRep other = (PrivilegeRep) obj; + if (this.name == null) { + if (other.name != null) + return false; + } else if (!this.name.equals(other.name)) + return false; + return true; + } } diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index f7a37a0f5..06fd4a9bb 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -74,10 +74,43 @@ public class RoleRep implements Serializable { } /** - * @param privilegeMap - * the privilegeMap to set + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() */ - public void setPrivileges(Map privilegeMap) { - this.privilegeMap = privilegeMap; + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RoleRep [name="); + builder.append(this.name); + builder.append(", privilegeMap="); + builder.append((this.privilegeMap == null ? "null" : this.privilegeMap)); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.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; + RoleRep other = (RoleRep) obj; + if (this.name == null) { + if (other.name != null) + return false; + } else if (!this.name.equals(other.name)) + return false; + return true; } } diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 66b27284c..01f0f786a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -217,4 +217,55 @@ public class UserRep implements Serializable { public Map getProperties() { return this.propertyMap; } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("UserRep [userId="); + builder.append(this.userId); + builder.append(", username="); + builder.append(this.username); + builder.append(", firstname="); + builder.append(this.firstname); + builder.append(", surname="); + builder.append(this.surname); + builder.append(", userState="); + builder.append(this.userState); + builder.append(", locale="); + builder.append(this.locale); + builder.append(", roles="); + builder.append(this.roles); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.username == null) ? 0 : this.username.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; + UserRep other = (UserRep) obj; + if (this.username == null) { + if (other.username != null) + return false; + } else if (!this.username.equals(other.username)) + return false; + return true; + } } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index c8e44343b..1b1d118cf 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -25,8 +25,15 @@ import java.util.HashMap; import java.util.Map; import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.policy.PrivilegePolicy; +/** + * This class is used during XML parsing to hold the model before it is properly validated and made accessible through + * the {@link PrivilegeHandler} + * + * @author Robert von Burg + */ public class PrivilegeContainerModel { private String encryptionHandlerClassName; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index eb50d4a58..1cd67bc2e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -40,7 +40,6 @@ import ch.eitchnet.privilege.model.UserState; *

    * * @author Robert von Burg - * */ public final class User { diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index f2e0115b0..478169cdb 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -26,11 +26,16 @@ import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; /** + * This is a simple implementation of {@link PrivilegePolicy} which uses the {@link Restrictable#getPrivilegeName()} to + * see if a given {@link Role} has the privilege required by the value from {@link Restrictable#getPrivilegeValue()} + * * @author Robert von Burg */ public class DefaultPrivilege implements PrivilegePolicy { /** + * The value of {@link Restrictable#getPrivilegeValue()} is used to check if the {@link Role} has this privilege + * * @see ch.eitchnet.privilege.policy.PrivilegePolicy#actionAllowed(ch.eitchnet.privilege.model.internal.Role, * ch.eitchnet.privilege.model.internal.Privilege, ch.eitchnet.privilege.model.Restrictable) */ diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java index 0504a64b9..82d513343 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -24,6 +24,7 @@ package ch.eitchnet.privilege.xml; import org.xml.sax.Attributes; import org.xml.sax.SAXException; +// TODO write JavaDoc... public interface ElementParser { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException; From 93022ba559be08c8463a6a8cf1c1be87ac505345 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 9 Apr 2013 07:33:32 +0200 Subject: [PATCH 143/457] [Major] major rewrite of the privilege validation. Now the PrivilegeContext object is central and once the user logged in, this object is bound to a ThreadLocal. From then there is no further need to interact with the PrivilegeHandler - this allows for authenticated users to get a remote copy of the PrivilegeContext so that on a remote client, the user can check for permissions, without having to do the round trip to the server. A code review of this change would be good, but preliminary tests show that it works. A test should now be implemented to check if getting a remote copy also allows for authorization. --- config/Model.xml | 55 +++ config/Privilege.xml | 2 +- config/PrivilegeModel.xml | 42 +- pom.xml | 2 +- .../handler/DefaultPrivilegeHandler.java | 392 ++++++--------- .../privilege/handler/PersistenceHandler.java | 4 +- .../privilege/handler/PrivilegeHandler.java | 63 +-- .../privilege/handler/SystemUserAction.java | 10 +- .../helper/CertificateThreadLocal.java | 48 -- .../privilege/i18n/PrivilegeMessages.java | 46 ++ .../i18n/PrivilegeMessages.properties | 6 + .../eitchnet/privilege/model/Certificate.java | 28 +- .../eitchnet/privilege/model/IPrivilege.java | 92 ++++ .../privilege/model/PrivilegeContext.java | 161 ++++++ .../privilege/model/PrivilegeRep.java | 77 ++- .../privilege/model/Restrictable.java | 7 +- .../ch/eitchnet/privilege/model/RoleRep.java | 13 + .../ch/eitchnet/privilege/model/UserRep.java | 28 ++ .../internal/PrivilegeContainerModel.java | 4 + .../{Privilege.java => PrivilegeImpl.java} | 123 +++-- .../privilege/model/internal/Role.java | 40 +- .../privilege/model/internal/Session.java | 191 -------- .../privilege/model/internal/User.java | 9 +- .../privilege/policy/DefaultPrivilege.java | 66 ++- .../privilege/policy/PrivilegePolicy.java | 15 +- .../xml/PrivilegeModelDomWriter.java | 5 +- .../xml/PrivilegeModelSaxReader.java | 9 +- .../privilege/test/PrivilegeTest.java | 463 +++++++++--------- .../ch/eitchnet/privilege/test/XmlTest.java | 18 +- .../test/model/TestSystemUserAction.java | 26 +- .../test/model/TestSystemUserActionDeny.java | 20 +- 31 files changed, 1061 insertions(+), 1004 deletions(-) create mode 100644 config/Model.xml delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java create mode 100644 src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java create mode 100644 src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties create mode 100644 src/main/java/ch/eitchnet/privilege/model/IPrivilege.java create mode 100644 src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java rename src/main/java/ch/eitchnet/privilege/model/internal/{Privilege.java => PrivilegeImpl.java} (63%) delete mode 100644 src/main/java/ch/eitchnet/privilege/model/internal/Session.java diff --git a/config/Model.xml b/config/Model.xml new file mode 100644 index 000000000..74dabc4f4 --- /dev/null +++ b/config/Model.xml @@ -0,0 +1,55 @@ + + + + + + + + + + true + + + + + + true + + + true + + + + + + + + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + + + + + diff --git a/config/Privilege.xml b/config/Privilege.xml index b37070a5a..20eda653e 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -39,7 +39,7 @@ along with Privilege. If not, see . - + diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 4c788b66e..a58e6830c 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -1,26 +1,10 @@ - + @@ -39,7 +23,7 @@ along with Privilege. If not, see .
    - + System User Administrator @@ -48,7 +32,7 @@ along with Privilege. If not, see . system_admin_privileges - + @@ -58,20 +42,20 @@ along with Privilege. If not, see . - + true - + - + true - + true - + hello diff --git a/pom.xml b/pom.xml index bddc78dba..2d601786a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.privilege jar - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT ch.eitchnet.privilege https://github.com/eitch/ch.eitchnet.privilege diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 0235341b0..43691ca2a 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -19,6 +19,7 @@ */ package ch.eitchnet.privilege.handler; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -35,14 +36,14 @@ import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -72,7 +73,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; /** - * log4j logger + * slf4j logger */ protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); @@ -82,14 +83,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private long lastSessionId; /** - * This map stores certificates for system users as a cache + * Map keeping a reference to all active sessions */ - private Map systemUserCertificateMap; - - /** - * Map keeping a reference to all active sessions with their certificates - */ - private Map sessionMap; + private Map privilegeContextMap; /** * Map of {@link PrivilegePolicy} classes @@ -132,41 +128,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return user.asUserRep(); } - /** - *

    - * This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy} - * is not stored in a database. The privilege name is a class name and is then used to instantiate a new - * {@link PrivilegePolicy} object - *

    - * - * @param policyName - * the class name of the {@link PrivilegePolicy} object to return - * - * @return the {@link PrivilegePolicy} object - * - * @throws PrivilegeException - * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated - */ - private PrivilegePolicy getPolicy(String policyName) { - - // get the policies class - Class policyClazz = this.policyMap.get(policyName); - if (policyClazz == null) { - return null; - } - - // instantiate the policy - PrivilegePolicy policy; - try { - policy = ClassHelper.instantiateClass(policyClazz); - } catch (Exception e) { - throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" - + policyName, e); - } - - return policy; - } - @Override public List queryUsers(UserRep selectorRep) { @@ -377,8 +338,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new role with the additional privilege - Privilege newPrivilege = new Privilege(privilegeRep); - Map privilegeMap = new HashMap(role.getPrivilegeMap()); + IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); + // copy existing privileges + Set existingPrivilegeNames = role.getPrivilegeNames(); + Map privilegeMap = new HashMap(existingPrivilegeNames.size() + 1); + for (String name : existingPrivilegeNames) { + IPrivilege privilege = role.getPrivilege(name); + privilegeMap.put(name, privilege); + } + // add new one privilegeMap.put(newPrivilege.getName(), newPrivilege); Role newRole = new Role(role.getName(), privilegeMap); @@ -438,9 +406,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (!role.hasPrivilege(privilegeName)) throw new PrivilegeException("Role " + roleName + " does not have Privilege " + privilegeName); - // create new set of privileges with out the to remove privilege - Map newPrivileges = new HashMap(role.getPrivilegeMap().size() - 1); - for (Privilege privilege : role.getPrivilegeMap().values()) { + // create new set of privileges with out the to removed privilege + Set privilegeNames = role.getPrivilegeNames(); + Map newPrivileges = new HashMap(privilegeNames.size() - 1); + for (String name : privilegeNames) { + IPrivilege privilege = role.getPrivilege(name); if (!privilege.getName().equals(privilegeName)) newPrivileges.put(privilege.getName(), privilege); } @@ -666,7 +636,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { + " and can not login!"); // validate user has at least one role - if (user.getRoles().isEmpty()) { + Set userRoles = user.getRoles(); + if (userRoles.isEmpty()) { throw new PrivilegeException("User " + username + " does not have any roles defined!"); } @@ -678,15 +649,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale(), - new HashMap(user.getProperties())); + certificate = new Certificate(sessionId, System.currentTimeMillis(), username, authToken, authPassword, + user.getLocale(), new HashMap(user.getProperties())); - // create and save a new session - Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); - this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); + this.privilegeContextMap.put(sessionId, privilegeContext); // log - DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + session); + DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + certificate); } catch (RuntimeException e) { DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " @@ -700,6 +670,56 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return certificate; } + /** + * Builds a {@link PrivilegeContext} for the given {@link User} and its {@link Certificate} + * + * @param certificate + * @param user + * + * @return + */ + private PrivilegeContext buildPrivilegeContext(Certificate certificate, User user) { + + Set userRoles = user.getRoles(); + Map privileges = new HashMap(); + Map policies = new HashMap(); + + // get a cache of the privileges and policies for this user + for (String roleName : userRoles) { + Role role = this.persistenceHandler.getRole(roleName); + Set privilegeNames = role.getPrivilegeNames(); + for (String privilegeName : privilegeNames) { + + // cache the privilege + if (privileges.containsKey(privilegeName)) + continue; + + IPrivilege privilege = role.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException(MessageFormat.format("The Privilege {0} does not exist for role {1}", + privilegeName, roleName)); + } + privileges.put(privilegeName, privilege); + + // cache the policy for the privilege + String policyName = privilege.getPolicy(); + if (policies.containsKey(policyName)) + continue; + + PrivilegePolicy policy = getPolicy(policyName); + if (policy == null) { + throw new PrivilegeException(MessageFormat.format( + "The Policy {0} does not exist for Privilege {1}", policyName, privilegeName)); + } + policies.put(policyName, policy); + } + } + + UserRep userRep = user.asUserRep(); + PrivilegeContext privilegeContext = new PrivilegeContext(userRep, certificate, privileges, policies); + return privilegeContext; + } + @Override public boolean invalidateSession(Certificate certificate) { @@ -707,10 +727,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { isCertificateValid(certificate); // remove registration - CertificateSessionPair certificateSessionPair = this.sessionMap.remove(certificate.getSessionId()); + PrivilegeContext privilegeContext = this.privilegeContextMap.remove(certificate.getSessionId()); // return true if object was really removed - boolean loggedOut = certificateSessionPair != null; + boolean loggedOut = privilegeContext != null; if (loggedOut) DefaultPrivilegeHandler.logger.info("User " + certificate.getUsername() + " logged out."); else @@ -718,136 +738,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return loggedOut; } - /** - * Checks if the action is allowed by iterating the roles of the certificates user and then delegating to - * {@link #actionAllowed(Role, Restrictable)} - * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation - * - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.Restrictable) - */ - @Override - public void actionAllowed(Certificate certificate, Restrictable restrictable) { - - // TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? - - // first validate certificate - isCertificateValid(certificate); - - // restrictable must not be null - if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); - - // get user object - User user = this.persistenceHandler.getUser(certificate.getUsername()); - if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); - } - - String privilegeName = restrictable.getPrivilegeName(); - - // now iterate roles and validate on policies - for (String roleName : user.getRoles()) { - - Role role = this.persistenceHandler.getRole(roleName); - if (role == null) { - DefaultPrivilegeHandler.logger.error("No role is defined with name " + roleName - + " which is configured for user " + user); - continue; - } - - // ignore if this role does not have the privilege - if (!role.hasPrivilege(privilegeName)) - continue; - - // if action is allowed, then break iteration as a privilege match has been made - if (actionAllowed(role, restrictable)) - return; - } - - throw new AccessDeniedException("User " + user.getUsername() + " does not have Privilege " + privilegeName - + " needed for Restrictable " + restrictable.getClass().getName()); - } - - /** - * Checks if the {@link RoleRep} has access to the {@link Restrictable} by delegating to - * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} - * - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.RoleRep, - * ch.eitchnet.privilege.model.Restrictable) - */ - @Override - public void actionAllowed(RoleRep roleRep, Restrictable restrictable) { - - // user and restrictable must not be null - if (roleRep == null) - throw new PrivilegeException("Role may not be null!"); - else if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); - - // get role for the roleRep - Role role = this.persistenceHandler.getRole(roleRep.getName()); - - // validate that the role exists - if (role == null) { - throw new PrivilegeException("No Role exists with the name " + roleRep.getName()); - } - - // delegate to method with Role - actionAllowed(role, restrictable); - } - - /** - * Checks if the {@link Role} has access to the {@link Restrictable} by delegating to - * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} - * - * @param role - * the role which wants to perform the action - * @param restrictable - * the {@link Restrictable} which is to be checked for authorization - */ - private boolean actionAllowed(Role role, Restrictable restrictable) { - - // validate PrivilegeName for this restrictable - String privilegeName = restrictable.getPrivilegeName(); - if (privilegeName == null || privilegeName.length() < 3) { - throw new PrivilegeException( - "The PrivilegeName may not be shorter than 3 characters. Invalid Restrictable " - + restrictable.getClass().getName()); - } - - // If the role does not have this privilege, then stop as another role might have this privilege - if (!role.hasPrivilege(privilegeName)) { - return false; - } - - // get the privilege for this restrictable - Privilege privilege = role.getPrivilegeMap().get(privilegeName); - - // check if all is allowed - if (privilege.isAllAllowed()) { - return true; - } - - // otherwise delegate checking to the policy configured for this privilege - PrivilegePolicy policy = getPolicy(privilege.getPolicy()); - if (policy == null) { - throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " - + privilegeName); - } - - // delegate checking to privilege policy - policy.actionAllowed(role, privilege, restrictable); - - // the policy must throw an exception if action not allowed, - // so return true if we arrive here - return true; - } - @Override public void isCertificateValid(Certificate certificate) { @@ -856,24 +746,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Certificate may not be null!"); // first see if a session exists for this certificate - CertificateSessionPair certificateSessionPair = this.sessionMap.get(certificate.getSessionId()); - if (certificateSessionPair == null) + PrivilegeContext privilegeContext = this.privilegeContextMap.get(certificate.getSessionId()); + if (privilegeContext == null) throw new AccessDeniedException("There is no session information for " + certificate.toString()); // validate certificate has not been tampered with - Certificate sessionCertificate = certificateSessionPair.certificate; + Certificate sessionCertificate = privilegeContext.getCertificate(); if (!sessionCertificate.equals(certificate)) throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - // TODO is validating authToken overkill since the two certificates have already been checked on equality? - // validate authToken from certificate using the sessions authPassword - String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); - if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) - throw new PrivilegeException("Received illegal certificate data for session id " - + certificate.getSessionId()); - // get user object - User user = this.persistenceHandler.getUser(certificateSessionPair.session.getUsername()); + User user = this.persistenceHandler.getUser(privilegeContext.getUsername()); // if user exists, then certificate is valid if (user == null) { @@ -884,6 +767,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // everything is ok } + @Override + public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException { + + // first validate certificate + isCertificateValid(certificate); + + return this.privilegeContextMap.get(certificate.getSessionId()); + } + @Override public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { @@ -975,8 +867,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } this.lastSessionId = 0l; - this.sessionMap = Collections.synchronizedMap(new HashMap()); - this.systemUserCertificateMap = Collections.synchronizedMap(new HashMap()); + this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } @@ -987,7 +878,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * the role for which the policies are to be checked */ private void validatePolicies(Role role) { - for (Privilege privilege : role.getPrivilegeMap().values()) { + for (String privilegeName : role.getPrivilegeNames()) { + IPrivilege privilege = role.getPrivilege(privilegeName); String policy = privilege.getPolicy(); if (policy != null && !this.policyMap.containsKey(policy)) { throw new PrivilegeException("Policy " + policy + " for Privilege " + privilege.getName() @@ -1003,37 +895,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return Long.toString(++this.lastSessionId % Long.MAX_VALUE); } - /** - * An internal class used to keep a record of sessions with the certificate - * - * @author Robert von Burg - */ - protected class CertificateSessionPair { - - /** - * The {@link Session} - */ - public final Session session; - - /** - * The {@link Certificate} - */ - public final Certificate certificate; - - /** - * Creates a new {@link CertificateSessionPair} with the given session and certificate - * - * @param session - * the session - * @param certificate - * the certificate - */ - public CertificateSessionPair(Session session, Certificate certificate) { - this.session = session; - this.certificate = certificate; - } - } - /** * Passwords should not be kept as strings, as string are immutable, this method thus clears the byte array so that * the password is not in memory anymore @@ -1071,10 +932,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { checkPrivilege(actionClassname, systemUser); // get certificate for this system user - Certificate systemUserCertificate = getSystemUserCertificate(systemUsername); + PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(systemUsername); // perform the action - action.execute(systemUserCertificate); + action.execute(systemUserPrivilegeContext); } /** @@ -1113,14 +974,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @return the {@link Certificate} for this system user */ - private Certificate getSystemUserCertificate(String systemUsername) { - - // see if a certificate has already been created for this system user - Certificate systemUserCertificate = this.systemUserCertificateMap.get(systemUsername); - if (systemUserCertificate != null) - return systemUserCertificate; - - // otherwise log this system user in, by performing a slightly different authentication + private PrivilegeContext getSystemUserPrivilegeContext(String systemUsername) { // we only work with hashed passwords String passwordHash = this.encryptionHandler.convertToHash(systemUsername.getBytes()); @@ -1156,17 +1010,51 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - systemUserCertificate = new Certificate(sessionId, systemUsername, authToken, authPassword, user.getLocale(), - new HashMap(user.getProperties())); + Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, + authToken, authPassword, user.getLocale(), new HashMap(user.getProperties())); - // create and save a new session - Session session = new Session(sessionId, systemUsername, authToken, authPassword, System.currentTimeMillis()); - this.sessionMap.put(sessionId, new CertificateSessionPair(session, systemUserCertificate)); + // create and save a new privilege context + PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); // log DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " - + session); + + systemUserCertificate); - return systemUserCertificate; + return privilegeContext; + } + + /** + *

    + * This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy} + * is not stored in a database. The privilege name is a class name and is then used to instantiate a new + * {@link PrivilegePolicy} object + *

    + * + * @param policyName + * the class name of the {@link PrivilegePolicy} object to return + * + * @return the {@link PrivilegePolicy} object + * + * @throws PrivilegeException + * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated + */ + private PrivilegePolicy getPolicy(String policyName) { + + // get the policies class + Class policyClazz = this.policyMap.get(policyName); + if (policyClazz == null) { + return null; + } + + // instantiate the policy + PrivilegePolicy policy; + try { + policy = ClassHelper.instantiateClass(policyClazz); + } catch (Exception e) { + throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" + + policyName, e); + } + + return policy; } } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index 557d1fc48..a7dcc8b43 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -22,8 +22,8 @@ package ch.eitchnet.privilege.handler; import java.util.List; import java.util.Map; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -37,7 +37,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; *

    * The {@link PersistenceHandler} also serves the special {@link PrivilegePolicy} objects. These policies are special * objects which implement an algorithm to define if an action is allowed on a {@link Restrictable} by a {@link Role} - * and {@link Privilege} + * and {@link IPrivilege} *

    * * @author Robert von Burg diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 7889905e7..ec0d5376a 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -25,14 +25,13 @@ import java.util.Locale; import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; /** @@ -222,7 +221,7 @@ public interface PrivilegeHandler { * @param roleName * the roleName of the {@link Role} to which the privilege should be added * @param privilegeRep - * the representation of the {@link Privilege} which should be added to the {@link Role} + * the representation of the {@link IPrivilege} which should be added to the {@link Role} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -335,49 +334,15 @@ public interface PrivilegeHandler { public Certificate authenticate(String username, byte[] password) throws AccessDeniedException; /** - * Invalidates the {@link Session} for the given {@link Certificate}, effectively logging out the user who was - * authenticated with the credentials associated to the given {@link Certificate} + * Invalidates the session for the given {@link Certificate}, effectively logging out the user who was authenticated + * with the credentials associated to the given {@link Certificate} * * @param certificate - * the {@link Certificate} for which the {@link Session} is to be invalidated - * @return true if the {@link Session} was still valid and is now invalidated, false otherwise + * the {@link Certificate} for which the session is to be invalidated + * @return true if the session was still valid and is now invalidated, false otherwise */ public boolean invalidateSession(Certificate certificate); - /** - * Checks if the {@link User} registered to the given {@link Certificate} is allowed to access the - * {@link Restrictable} - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param restrictable - * the {@link Restrictable} to which the user wants access - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action defined by the {@link Restrictable} - * implementation - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public void actionAllowed(Certificate certificate, Restrictable restrictable) throws AccessDeniedException, - PrivilegeException; - - /** - * Checks if the {@link RoleRep} is allowed to access the {@link Restrictable} - * - * @param roleRep - * the {@link RoleRep} for which access to the {@link Restrictable} is to be checked - * @param restrictable - * the {@link Restrictable} to which access is to be checked - * - * @throws PrivilegeException - * if the {@link Role} does not exist - * @throws AccessDeniedException - * if the role may not perform the action defined by the {@link Restrictable} implementation - */ - public void actionAllowed(RoleRep roleRep, Restrictable restrictable) throws PrivilegeException, - AccessDeniedException; - /** * Checks if the given {@link Certificate} is valid. This means that the certificate is for a valid session and that * the user exists for the certificate. This method checks if the {@link Certificate} has been tampered with @@ -390,6 +355,20 @@ public interface PrivilegeHandler { */ public void isCertificateValid(Certificate certificate) throws PrivilegeException; + /** + * Returns the {@link PrivilegeContext} for the given {@link Certificate}. The {@link PrivilegeContext} is an + * encapsulated state of a user's privileges so that for the duration of a user's call, the user can perform their + * actions and do not need to access the {@link PrivilegeHandler} anymore + * + * @param certificate + * a valid {@link Certificate} for which a {@link PrivilegeContext} is to be returned + * @return the {@link PrivilegeContext} for the given {@link Certificate} + * + * @throws PrivilegeException + * if there is a configuration error or the {@link Certificate} is invalid + */ + public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException; + /** *

    * Validates if this {@link Certificate} is for a {@link ch.eitchnet.privilege.model.internal.User} with diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java index d59aba87b..724d8a32d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -20,6 +20,7 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; /** * With this interface system actions, which are to be performed in an automated fashion, i.e. by cron jobs, can be @@ -34,8 +35,11 @@ public interface SystemUserAction { * This method will be called by the {@link PrivilegeHandler} when an authorized {@link Certificate} has been * generated to allow this action to properly validate its execution * - * @param certificate - * the {@link Certificate} which was generated for a valid system user + * TODO: I'm not really happy with this... we might want to pass the certificate and then force the action to + * validate it to get the {@link PrivilegeContext} - we don't want the {@link PrivilegeContext} to live forever... + * + * @param privilegeContext + * the {@link PrivilegeContext} which was generated for a valid system user */ - public void execute(Certificate certificate); + public void execute(PrivilegeContext privilegeContext); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java deleted file mode 100644 index fe75d24c0..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . - * - */ -package ch.eitchnet.privilege.helper; - -import ch.eitchnet.privilege.model.Certificate; - -/** - * This {@link ThreadLocal} holds a reference to the {@link Certificate} which allows any code segment to perform - * further authorization before executing - * - * @author Robert von Burg - */ -public class CertificateThreadLocal extends ThreadLocal { - - private static final CertificateThreadLocal instance; - static { - instance = new CertificateThreadLocal(); - } - - public static CertificateThreadLocal getInstance() { - return CertificateThreadLocal.instance; - } - - public static Certificate getCertificate() { - return CertificateThreadLocal.instance.get(); - } - - public static void setCertificate(Certificate certificate) { - CertificateThreadLocal.instance.set(certificate); - } -} diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java new file mode 100644 index 000000000..c84da0b69 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.i18n; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeMessages { + private static final String BUNDLE_NAME = "ch.eitchnet.privilege.i18n.PrivilegeMessages"; //$NON-NLS-1$ + + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); + + private PrivilegeMessages() { + } + + public static String getString(String key) { + try { + return RESOURCE_BUNDLE.getString(key); + } catch (MissingResourceException e) { + return '!' + key + '!'; + } + } +} diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties new file mode 100644 index 000000000..62f19745c --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties @@ -0,0 +1,6 @@ +Privilege.accessdenied.noprivilege=User {0} does not have Privilege {1} needed for Restrictable {2} +Privilege.illegalArgument.nonstring=\ {1} has returned a non-string privilege value\! +Privilege.illegalArgument.privilegeNameMismatch=The passed privilege has the name {0} but the restrictable is referencing privilege {1} +Privilege.privilegeNameEmpty=The PrivilegeName for the Restrictable is null or empty: {0} +Privilege.privilegeNull=Privilege may not be null\! +Privilege.restrictableNull=Restrictable may not be null\! diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index ca7710313..5b6bebfe0 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -25,7 +25,7 @@ import java.util.Map; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.internal.Session; +import ch.eitchnet.utils.helper.StringHelper; /** * The {@link Certificate} is the object a client keeps when accessing a Privilege enabled system. This object is the @@ -39,6 +39,7 @@ public final class Certificate implements Serializable { private static final long serialVersionUID = 1L; private final String sessionId; + private final long loginTime; private final String username; private final String authToken; private final String authPassword; @@ -72,15 +73,25 @@ public final class Certificate implements Serializable { * a {@link Map} containing string value pairs of properties for the logged in user. These properties can * be edited and can be used for the user to change settings of this session */ - public Certificate(String sessionId, String username, String authToken, String authPassword, Locale locale, - Map propertyMap) { + public Certificate(String sessionId, long loginTime, String username, String authToken, String authPassword, + Locale locale, Map propertyMap) { // validate arguments are not null - if (sessionId == null || username == null || authToken == null || authPassword == null) { - throw new PrivilegeException("One of the arguments is null!"); + if (StringHelper.isEmpty(sessionId)) { + throw new PrivilegeException("sessionId is null!"); + } + if (StringHelper.isEmpty(username)) { + throw new PrivilegeException("username is null!"); + } + if (StringHelper.isEmpty(authToken)) { + throw new PrivilegeException("authToken is null!"); + } + if (StringHelper.isEmpty(authPassword)) { + throw new PrivilegeException("authPassword is null!"); } this.sessionId = sessionId; + this.loginTime = loginTime; this.username = username; this.authToken = authToken; this.authPassword = authPassword; @@ -130,6 +141,13 @@ public final class Certificate implements Serializable { return this.username; } + /** + * @return the loginTime + */ + public long getLoginTime() { + return this.loginTime; + } + /** * Returns the authToken if the given authPassword is correct, null otherwise * diff --git a/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java new file mode 100644 index 000000000..f9ddbe93f --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.model; + +import java.util.Set; + +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + *

    + * {@link IPrivilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it + * which defines the privileges a logged in user with that role has. If the {@link IPrivilege} has a + * {@link PrivilegePolicy} defined, then that policy will be used for finer granularity and with the deny and allow + * lists configured which is used to evaluate if privilege is granted to a {@link Restrictable} + *

    + * + * @author Robert von Burg + * + */ +public interface IPrivilege { + + /** + * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + */ + public abstract PrivilegeRep asPrivilegeRep(); + + /** + * @return the name + */ + public abstract String getName(); + + /** + * @return the policy + */ + public abstract String getPolicy(); + + /** + * @return the allAllowed + */ + public abstract boolean isAllAllowed(); + + /** + * @return the allowList + */ + public abstract Set getAllowList(); + + /** + * @return the denyList + */ + public abstract Set getDenyList(); + + /** + * @return true if there are values in the allow list + */ + public abstract boolean hasAllowed(); + + /** + * @return if the value is in the allow list + */ + public abstract boolean isAllowed(String value); + + /** + * @return true if there are values in the deny list + */ + public abstract boolean hasDenied(); + + /** + * @return true if the value is in the deny list + */ + public abstract boolean isDenied(String value); + +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java new file mode 100644 index 000000000..661434a18 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.model; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + * This context gives access to a logged in user's privilege data e.g. the {@link UserRep}, {@link Certificate} and the + * user's list of {@link PrivilegeRep} + * + *

    + * Note: This is an internal object which is not to be serialized to clients + *

    + * + * @author Robert von Burg + */ +public class PrivilegeContext { + + // + // object state + // + + private UserRep userRep; + private Certificate certificate; + private Map privileges; + private Map policies; + + public PrivilegeContext(UserRep userRep, Certificate certificate, Map privileges, + Map policies) { + this.userRep = userRep; + this.certificate = certificate; + this.privileges = Collections.unmodifiableMap(new HashMap(privileges)); + this.policies = Collections.unmodifiableMap(new HashMap(policies)); + } + + public UserRep getUserRep() { + return this.userRep; + } + + public Certificate getCertificate() { + return this.certificate; + } + + public String getUsername() { + return this.userRep.getUsername(); + } + + public Set getPrivilegeNames() { + return this.privileges.keySet(); + } + + // + // business logic + // + + /** + * Validates if the user for this context has the privilege to access to the given {@link Restrictable}. If the user + * has the privilege, then this method returns with no exception and void, if the user does not have the privilege, + * then a {@link AccessDeniedException} is thrown. + * + * @param restrictable + * the {@link Restrictable} which the user wants to access + * + * @throws AccessDeniedException + * if the user does not have access + * @throws PrivilegeException + * if there is an internal error due to wrongly configured privileges or programming errors + */ + public void validateAction(Restrictable restrictable) throws AccessDeniedException, PrivilegeException { + + // the the privilege for the restrictable + String privilegeName = restrictable.getPrivilegeName(); + IPrivilege privilege = this.privileges.get(privilegeName); + if (privilege == null) { + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + getUsername(), privilegeName, restrictable.getClass().getName()); + throw new AccessDeniedException(msg); + } + + // get the policy referenced by the restrictable + String policyName = privilege.getPolicy(); + PrivilegePolicy policy = this.policies.get(policyName); + if (policy == null) { + String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; + throw new PrivilegeException(MessageFormat.format(msg, policyName)); + } + + // delegate to the policy + policy.validateAction(privilege, restrictable); + } + + // + // ThreadLocal binding + // + + private static final ThreadLocal threadLocal = new ThreadLocal(); + + /** + * Returns the currently {@link ThreadLocal} bound {@link PrivilegeContext} or throws a {@link PrivilegeException} + * if none is set + * + * @return the currently {@link ThreadLocal} bound {@link PrivilegeContext} or throws a {@link PrivilegeException} + * if none is set + * + * @throws PrivilegeException + * if no {@link PrivilegeContext} is set + */ + public static PrivilegeContext get() throws PrivilegeException { + PrivilegeContext privilegeContext = PrivilegeContext.threadLocal.get(); + if (privilegeContext == null) { + throw new PrivilegeException("There is no PrivilegeContext currently bound to the ThreadLocal!"); + } + return privilegeContext; + } + + /** + * Bind a {@link PrivilegeContext} to the {@link ThreadLocal} or throws a {@link PrivilegeException} if one is + * already bound + * + * @param privilegeContext + * the {@link PrivilegeContext} to bind + * + * @throws PrivilegeException + * if e {@link PrivilegeContext} is already bound + */ + public static void set(PrivilegeContext privilegeContext) throws PrivilegeException { + PrivilegeContext currentContext = PrivilegeContext.threadLocal.get(); + if (privilegeContext != null && currentContext != null) { + throw new PrivilegeException("There already is a PrivilegeContext bound to the ThreadLocal!"); + } + PrivilegeContext.threadLocal.set(privilegeContext); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index c74725a80..b2a316c5b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -24,14 +24,14 @@ import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.StringHelper; /** - * To keep certain details of the {@link Privilege} itself hidden from remote clients and make sure instances are only + * To keep certain details of the {@link IPrivilege} itself hidden from remote clients and make sure instances are only * edited by users with the correct privilege, this representational version is allowed to be viewed by remote clients - * and simply wraps all public data from the {@link Privilege} + * and simply wraps all public data from the {@link IPrivilege} * * @author Robert von Burg */ @@ -53,12 +53,12 @@ public class PrivilegeRep implements Serializable { * @param policy * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted * @param allAllowed - * a boolean defining if a {@link Role} with this {@link Privilege} has unrestricted access to a + * a boolean defining if a {@link Role} with this {@link IPrivilege} has unrestricted access to a * {@link Restrictable} * @param denyList - * a list of deny rules for this {@link Privilege} + * a list of deny rules for this {@link IPrivilege} * @param allowList - * a list of allow rules for this {@link Privilege} + * a list of allow rules for this {@link IPrivilege} */ public PrivilegeRep(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = name; @@ -66,6 +66,29 @@ public class PrivilegeRep implements Serializable { this.allAllowed = allAllowed; this.denyList = denyList; this.allowList = allowList; + + validate(); + } + + /** + * Validates that all required fields are set + */ + public void validate() { + + if (StringHelper.isEmpty(this.name)) { + throw new PrivilegeException("No name defined!"); + } + + if (StringHelper.isEmpty(this.policy)) { + throw new PrivilegeException("policy is null!"); + } + + if (this.denyList == null) { + throw new PrivilegeException("denyList is null"); + } + if (this.allowList == null) { + throw new PrivilegeException("allowList is null"); + } } /** @@ -143,48 +166,6 @@ public class PrivilegeRep implements Serializable { this.allowList = allowList; } - /** - *

    - * Validates this {@link PrivilegeRep} so that a {@link Privilege} object can later be created from it - *

    - * - * TODO write comment on how validation is done - */ - public void validate() { - - if (this.name == null || this.name.isEmpty()) { - throw new PrivilegeException("No name defined!"); - } - - // if not all allowed, then validate that deny and allow lists are defined - if (this.allAllowed) { - - // all allowed means no policy will be used - if (this.policy != null && !this.policy.isEmpty()) { - throw new PrivilegeException("All is allowed, so Policy may not be set!"); - } - - if (this.denyList != null && !this.denyList.isEmpty()) - throw new PrivilegeException("All is allowed, so deny list must be null"); - if (this.allowList != null && !this.allowList.isEmpty()) - throw new PrivilegeException("All is allowed, so allow list must be null"); - - } else { - - // not all allowed, so policy must be set - if (this.policy == null || this.policy.isEmpty()) { - throw new PrivilegeException("All is not allowed, so Policy must be defined!"); - } - - if (this.denyList == null) { - throw new PrivilegeException("All is not allowed, so deny list must be set, even if empty"); - } - if (this.allowList == null) { - throw new PrivilegeException("All is not allowed, so allow list must be set, even if empty"); - } - } - } - /** * Returns a string representation of this object displaying its concrete type and its values * diff --git a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java index 95ef2e3c4..99795bb9e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java @@ -19,14 +19,13 @@ */ package ch.eitchnet.privilege.model; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** *

    * Objects implementing this interface are used to grant/restrict privileges to them. A {@link PrivilegePolicy} * implements the logic on granting/restricting privileges for a {@link Restrictable} and the - * {@link #getPrivilegeName()} is used to find the {@link Privilege} which has the associated {@link PrivilegePolicy} + * {@link #getPrivilegeName()} is used to find the {@link IPrivilege} which has the associated {@link PrivilegePolicy} * for evaluating access *

    * @@ -36,9 +35,9 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; public interface Restrictable { /** - * Returns the name of the {@link Privilege} which is to be used to validate privileges against + * Returns the name of the {@link IPrivilege} which is to be used to validate privileges against * - * @return the name of the {@link Privilege} which is to be used to validate privileges against + * @return the name of the {@link IPrivilege} which is to be used to validate privileges against */ public String getPrivilegeName(); diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index 06fd4a9bb..dfee0572c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -22,7 +22,9 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Map; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.helper.StringHelper; /** * To keep certain details of the {@link Role} itself hidden from remote clients and make sure instances are only edited @@ -47,8 +49,19 @@ public class RoleRep implements Serializable { * the map of privileges granted to this role */ public RoleRep(String name, Map privilegeMap) { + this.name = name; this.privilegeMap = privilegeMap; + + validate(); + } + + /** + * validates that all required fields are set + */ + public void validate() { + if (StringHelper.isEmpty(this.name)) + throw new PrivilegeException("name is null"); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 01f0f786a..118cb727c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -24,8 +24,10 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.StringHelper; /** * To keep certain details of the {@link User} itself hidden from remote clients and make sure instances are only edited @@ -77,6 +79,32 @@ public class UserRep implements Serializable { this.roles = roles; this.locale = locale; this.propertyMap = propertyMap; + + validate(); + } + + /** + * Validates that all required fields are set + */ + public void validate() { + + if (StringHelper.isEmpty(this.userId)) + throw new PrivilegeException("userId is null or empty"); + + if (StringHelper.isEmpty(this.username)) + throw new PrivilegeException("username is null or empty"); + + if (StringHelper.isEmpty(this.firstname)) + throw new PrivilegeException("firstname is null or empty"); + + if (StringHelper.isEmpty(this.surname)) + throw new PrivilegeException("surname is null or empty"); + + if (this.userState == null) + throw new PrivilegeException("userState is null"); + + if (this.roles == null) + throw new PrivilegeException("roles is null"); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index 1b1d118cf..a30db711c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -32,6 +32,10 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * This class is used during XML parsing to hold the model before it is properly validated and made accessible through * the {@link PrivilegeHandler} * + *

    + * Note: This is an internal object which is not to be serialized or passed to clients + *

    + * * @author Robert von Burg */ public class PrivilegeContainerModel { diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java similarity index 63% rename from src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java rename to src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index 2bb456c0b..c32623f41 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -25,20 +25,22 @@ import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.StringHelper; /** *

    - * {@link Privilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it which - * defines the privileges a logged in user with that role has. If the {@link Privilege} has a {@link PrivilegePolicy} - * defined, then that policy will be used for finer granularity and with the deny and allow lists configured which is - * used to evaluate if privilege is granted to a {@link Restrictable} + * {@link IPrivilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it + * which defines the privileges a logged in user with that role has. If the {@link IPrivilege} has a + * {@link PrivilegePolicy} defined, then that policy will be used for finer granularity and with the deny and allow + * lists configured which is used to evaluate if privilege is granted to a {@link Restrictable} *

    * *

    - * {@link Privilege}s have allow and deny rules which the configured {@link PrivilegeHandler} uses to + * {@link IPrivilege}s have allow and deny rules which the configured {@link PrivilegeHandler} uses to *

    * *

    @@ -48,7 +50,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * * @author Robert von Burg */ -public final class Privilege { +public final class PrivilegeImpl implements IPrivilege { private final String name; private final String policy; @@ -65,66 +67,59 @@ public final class Privilege { * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted. If null, then * privilege is granted * @param allAllowed - * a boolean defining if a {@link Role} with this {@link Privilege} has unrestricted access to a + * a boolean defining if a {@link Role} with this {@link PrivilegeImpl} has unrestricted access to a * {@link Restrictable} in which case the deny and allow lists are ignored and can be null * @param denyList - * a list of deny rules for this {@link Privilege}, can be null if all allowed + * a list of deny rules for this {@link PrivilegeImpl}, can be null if all allowed * @param allowList - * a list of allow rules for this {@link Privilege}, can be null if all allowed + * a list of allow rules for this {@link PrivilegeImpl}, can be null if all allowed */ - public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + public PrivilegeImpl(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { - if (name == null || name.isEmpty()) { + if (StringHelper.isEmpty(name)) { throw new PrivilegeException("No name defined!"); } - this.name = name; - - // if not all allowed, then validate that deny and allow lists are defined - if (allAllowed) { - - this.allAllowed = true; - - // all allowed means no policy will be used - this.policy = null; - - this.denyList = Collections.emptySet(); - this.allowList = Collections.emptySet(); - } else { - - this.allAllowed = false; - - // not all allowed, so policy must be set - if (policy == null || policy.isEmpty()) { - throw new PrivilegeException("All is not allowed and no Policy defined!"); - } - this.policy = policy; - - if (denyList == null) { - throw new PrivilegeException("All is not allowed and no denyList defined!"); - } - this.denyList = Collections.unmodifiableSet(denyList); - - if (allowList == null) { - throw new PrivilegeException("All is not allowed and no allowList defined!"); - } - this.allowList = Collections.unmodifiableSet(allowList); + if (StringHelper.isEmpty(policy)) { + throw new PrivilegeException("Policy may not be empty!"); } + if (denyList == null) { + throw new PrivilegeException("denyList is null!"); + } + if (allowList == null) { + throw new PrivilegeException("allowList is null!"); + } + + this.name = name; + this.allAllowed = allAllowed; + this.policy = policy; + this.denyList = Collections.unmodifiableSet(denyList); + this.allowList = Collections.unmodifiableSet(allowList); } /** - * Constructs a {@link Privilege} from the {@link PrivilegeRep} + * Constructs a {@link PrivilegeImpl} from the {@link PrivilegeRep} * * @param privilegeRep - * the {@link PrivilegeRep} from which to create the {@link Privilege} + * the {@link PrivilegeRep} from which to create the {@link PrivilegeImpl} */ - public Privilege(PrivilegeRep privilegeRep) { + public PrivilegeImpl(PrivilegeRep privilegeRep) { this(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getDenyList()); } + /** + * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + */ + @Override + public PrivilegeRep asPrivilegeRep() { + return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet(this.denyList), + new HashSet(this.allowList)); + } + /** * @return the name */ + @Override public String getName() { return this.name; } @@ -132,6 +127,7 @@ public final class Privilege { /** * @return the policy */ + @Override public String getPolicy() { return this.policy; } @@ -139,6 +135,7 @@ public final class Privilege { /** * @return the allAllowed */ + @Override public boolean isAllAllowed() { return this.allAllowed; } @@ -146,6 +143,7 @@ public final class Privilege { /** * @return the allowList */ + @Override public Set getAllowList() { return this.allowList; } @@ -153,16 +151,41 @@ public final class Privilege { /** * @return the denyList */ + @Override public Set getDenyList() { return this.denyList; } /** - * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + * @return true if there are values in the allow list */ - public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet(this.denyList), - new HashSet(this.allowList)); + @Override + public boolean hasAllowed() { + return !this.allowList.isEmpty(); + } + + /** + * @return if the value is in the allow list + */ + @Override + public boolean isAllowed(String value) { + return this.allowList.contains(value); + } + + /** + * @return true if there are values in the deny list + */ + @Override + public boolean hasDenied() { + return !this.allowList.isEmpty(); + } + + /** + * @return true if the value is in the deny list + */ + @Override + public boolean isDenied(String value) { + return this.denyList.contains(value); } /** @@ -197,7 +220,7 @@ public final class Privilege { return false; if (getClass() != obj.getClass()) return false; - Privilege other = (Privilege) obj; + PrivilegeImpl other = (PrivilegeImpl) obj; if (this.name == null) { if (other.name != null) return false; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index 70cd2b11d..9e85710de 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -22,10 +22,13 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.utils.helper.StringHelper; /** *

    @@ -43,7 +46,7 @@ import ch.eitchnet.privilege.model.RoleRep; public final class Role { private final String name; - private final Map privilegeMap; + private final Map privilegeMap; /** * Default constructor @@ -51,11 +54,11 @@ public final class Role { * @param name * the name of the role * @param privilegeMap - * a map of {@link Privilege}s granted to this role + * a map of {@link IPrivilege}s granted to this role */ - public Role(String name, Map privilegeMap) { + public Role(String name, Map privilegeMap) { - if (name == null || name.isEmpty()) { + if (StringHelper.isEmpty(name)) { throw new PrivilegeException("No name defined!"); } if (privilegeMap == null) { @@ -75,7 +78,7 @@ public final class Role { public Role(RoleRep roleRep) { String name = roleRep.getName(); - if (name == null || name.isEmpty()) { + if (StringHelper.isEmpty(name)) { throw new PrivilegeException("No name defined!"); } @@ -84,9 +87,9 @@ public final class Role { } // build privileges from reps - Map privilegeMap = new HashMap(roleRep.getPrivilegeMap().size()); + Map privilegeMap = new HashMap(roleRep.getPrivilegeMap().size()); for (String privilegeName : roleRep.getPrivilegeMap().keySet()) { - privilegeMap.put(privilegeName, new Privilege(roleRep.getPrivilegeMap().get(privilegeName))); + privilegeMap.put(privilegeName, new PrivilegeImpl(roleRep.getPrivilegeMap().get(privilegeName))); } this.name = name; @@ -101,21 +104,30 @@ public final class Role { } /** - * Returns the {@link Map} of {@link Privilege}s which are granted to this {@link Role} + * Returns the {@link Set} of names for the currently stored {@link IPrivilege Privileges} * - * @return the {@link Map} of {@link Privilege}s which are granted to this {@link Role} + * @return the {@link Set} of names for the currently stored {@link IPrivilege Privileges} */ - public Map getPrivilegeMap() { - return this.privilegeMap; + public Set getPrivilegeNames() { + return this.privilegeMap.keySet(); } /** - * Determines if this {@link Role} has the {@link Privilege} with the given name + * Returns the {@link IPrivilege} for the given name, null if it does not exist + * + * @return the {@link IPrivilege} for the given name, null if it does not exist + */ + public IPrivilege getPrivilege(String name) { + return this.privilegeMap.get(name); + } + + /** + * Determines if this {@link Role} has the {@link IPrivilege} with the given name * * @param name - * the name of the {@link Privilege} + * the name of the {@link IPrivilege} * - * @return true if this {@link Role} has the {@link Privilege} with the given name + * @return true if this {@link Role} has the {@link IPrivilege} with the given name */ public boolean hasPrivilege(String name) { return this.privilegeMap.containsKey(name); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java deleted file mode 100644 index 2ab1e45df..000000000 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2010 - 2012 - * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . - * - */ -package ch.eitchnet.privilege.model.internal; - -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; - -/** - *

    - * A {@link Session} is linked to currently logged in user. The {@link PrivilegeHandler} creates an instance of this - * class once a {@link User} has successfully logged in and keeps this object private but hands out a - * {@link Certificate} which the user must use every time a privilege needs to be granted - *

    - * - *

    - * Note: This is an internal object which is not to be serialized or passed to clients, the client must keep his - * {@link Certificate} which is used for accessing privileges - *

    - * - * @author Robert von Burg - */ -public final class Session { - - private final String sessionId; - private final String username; - private final long loginTime; - - private final String authToken; - private final String authPassword; - - /** - * Default constructor - * - *

    - * Note, both the authentication token and password are private fields which are generated on login and only known - * by the {@link PrivilegeHandler} - *

    - * - * @param sessionId - * the users session id - * @param username - * the users login name - * @param authToken - * the authentication token defining the users unique session and is a private field of this session. It - * corresponds with the authentication token on the {@link Certificate} - * @param authPassword - * the password to access the authentication token, this is not known to the client but set by the - * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the - * {@link Certificate} - * @param loginTime - * the time the user logged in - */ - public Session(String sessionId, String username, String authToken, String authPassword, long loginTime) { - - if (sessionId == null || sessionId.isEmpty()) { - throw new PrivilegeException("No sessionId defined!"); - } - if (username == null || username.isEmpty()) { - throw new PrivilegeException("No username defined!"); - } - if (authToken == null || authToken.isEmpty()) { - throw new PrivilegeException("No authToken defined!"); - } - if (authPassword == null || authPassword.isEmpty()) { - throw new PrivilegeException("No authPassword defined!"); - } - - this.sessionId = sessionId; - this.username = username; - this.authToken = authToken; - this.authPassword = authPassword; - this.loginTime = loginTime; - } - - /** - * @return the sessionId - */ - public String getSessionId() { - return this.sessionId; - } - - /** - * @return the authToken - */ - public String getAuthToken() { - return this.authToken; - } - - /** - * @return the authPassword - */ - public String getAuthPassword() { - return this.authPassword; - } - - /** - * @return the username - */ - public String getUsername() { - return this.username; - } - - /** - * @return the loginTime - */ - public long getLoginTime() { - return this.loginTime; - } - - /** - * Returns a string representation of this object displaying its concrete type and its values - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Session [sessionId="); - builder.append(this.sessionId); - builder.append(", username="); - builder.append(this.username); - builder.append(", loginTime="); - builder.append(this.loginTime); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.authPassword == null) ? 0 : this.authPassword.hashCode()); - result = prime * result + ((this.authToken == null) ? 0 : this.authToken.hashCode()); - result = prime * result + (int) (this.loginTime ^ (this.loginTime >>> 32)); - result = prime * result + ((this.sessionId == null) ? 0 : this.sessionId.hashCode()); - result = prime * result + ((this.username == null) ? 0 : this.username.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; - Session other = (Session) obj; - if (this.authPassword == null) { - if (other.authPassword != null) - return false; - } else if (!this.authPassword.equals(other.authPassword)) - return false; - if (this.authToken == null) { - if (other.authToken != null) - return false; - } else if (!this.authToken.equals(other.authToken)) - return false; - if (this.loginTime != other.loginTime) - return false; - if (this.sessionId == null) { - if (other.sessionId != null) - return false; - } else if (!this.sessionId.equals(other.sessionId)) - return false; - if (this.username == null) { - if (other.username != null) - return false; - } else if (!this.username.equals(other.username)) - return false; - return true; - } -} diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 1cd67bc2e..7e50da571 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -29,6 +29,7 @@ import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.utils.helper.StringHelper; /** * This class defines the actual login information for a given user which can be granted privileges. Every user is @@ -84,16 +85,16 @@ public final class User { public User(String userId, String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale, Map propertyMap) { - if (userId == null || userId.isEmpty()) { + if (StringHelper.isEmpty(userId)) { throw new PrivilegeException("No UserId defined!"); } - if (username == null || username.isEmpty()) { + if (StringHelper.isEmpty(username)) { throw new PrivilegeException("No username defined!"); } - if (firstname == null || firstname.isEmpty()) { + if (StringHelper.isEmpty(firstname)) { throw new PrivilegeException("No firstname defined!"); } - if (surname == null || surname.isEmpty()) { + if (StringHelper.isEmpty(surname)) { throw new PrivilegeException("No surname defined!"); } if (userState == null) { diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 478169cdb..3a360be94 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -19,11 +19,16 @@ */ package ch.eitchnet.privilege.policy; +import java.text.MessageFormat; + import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.helper.StringHelper; /** * This is a simple implementation of {@link PrivilegePolicy} which uses the {@link Restrictable#getPrivilegeName()} to @@ -36,53 +41,62 @@ public class DefaultPrivilege implements PrivilegePolicy { /** * The value of {@link Restrictable#getPrivilegeValue()} is used to check if the {@link Role} has this privilege * - * @see ch.eitchnet.privilege.policy.PrivilegePolicy#actionAllowed(ch.eitchnet.privilege.model.internal.Role, - * ch.eitchnet.privilege.model.internal.Privilege, ch.eitchnet.privilege.model.Restrictable) + * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, Restrictable) */ @Override - public void actionAllowed(Role role, Privilege privilege, Restrictable restrictable) { + public void validateAction(IPrivilege privilege, Restrictable restrictable) { - // validate user is not null - if (role == null) - throw new PrivilegeException("Role may not be null!"); + if (privilege == null) + throw new PrivilegeException(PrivilegeMessages.getString("Privilege.privilegeNull")); //$NON-NLS-1$ + if (restrictable == null) + throw new PrivilegeException(PrivilegeMessages.getString("Privilege.restrictableNull")); //$NON-NLS-1$ // get the PrivilegeName String privilegeName = restrictable.getPrivilegeName(); - if (privilegeName == null || privilegeName.isEmpty()) { - throw new PrivilegeException("The PrivilegeName for the Restrictable is null or empty: " + restrictable); + if (StringHelper.isEmpty(privilegeName)) { + String msg = PrivilegeMessages.getString("Privilege.privilegeNameEmpty"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format(msg, restrictable)); } + // we want the privileges names to match + if (!privilege.getName().equals(privilegeName)) { + throw new PrivilegeException(MessageFormat.format( + PrivilegeMessages.getString("Privilege.illegalArgument.privilegeNameMismatch"), //$NON-NLS-1$ + privilege.getName(), privilegeName)); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + // get the value on which the action is to be performed Object object = restrictable.getPrivilegeValue(); // DefaultPrivilege policy expects the privilege value to be a string if (!(object instanceof String)) { - throw new PrivilegeException(Restrictable.class.getName() + " " + restrictable.getClass().getSimpleName() - + " has returned a non-string privilege value!"); + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nonstring"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); } String privilegeValue = (String) object; // first check values not allowed - for (String denied : privilege.getDenyList()) { - - // if value in deny list - if (denied.equals(privilegeValue)) { - - // then throw access denied - throw new AccessDeniedException("Role " + role.getName() + " does not have Privilege " + privilegeName - + " needed for Restrictable " + restrictable.getClass().getName()); - } + if (privilege.isDenied(privilegeValue)) { + // then throw access denied + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); + throw new AccessDeniedException(msg); } // now check values allowed - for (String allowed : privilege.getAllowList()) { - if (allowed.equals(privilegeValue)) - return; - } + if (privilege.isAllowed(privilegeValue)) + return; // default is not allowed - throw new AccessDeniedException("Role " + role.getName() + " does not have Privilege " + privilegeName - + " needed for Restrictable " + restrictable.getClass().getName()); + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); + throw new AccessDeniedException(msg); } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 62041e636..4f0372c25 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -20,20 +20,19 @@ package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; /** *

    * {@link PrivilegePolicy} implements logic to determine if a {@link User} which has the given {@link Role} and the - * given {@link Privilege} has access to the given {@link Restrictable} + * given {@link IPrivilege} has access to the given {@link Restrictable} *

    * *

    - * Re-think this interface and especially the {@link #actionAllowed(Role, Privilege, Restrictable)}-method... maybe we - * need one with out the {@link Privilege} in its signature? + * TODO *

    * * @author Robert von Burg @@ -41,17 +40,15 @@ import ch.eitchnet.privilege.model.internal.User; public interface PrivilegePolicy { /** - * Checks if the given {@link Role} and the given {@link Privilege} has access to the given {@link Restrictable} + * Checks if the given {@link Role} and the given {@link IPrivilege} has access to the given {@link Restrictable} * - * @param role - * the {@link Role} trying to access the {@link Restrictable} * @param privilege - * the {@link Privilege} to check with + * the {@link IPrivilege} containing the permissions * @param restrictable * the {@link Restrictable} to which the user wants access * * @throws AccessDeniedException * if action not allowed */ - public void actionAllowed(Role role, Privilege privilege, Restrictable restrictable) throws AccessDeniedException; + public void validateAction(IPrivilege privilege, Restrictable restrictable) throws AccessDeniedException; } diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 18b0e7de7..03ef69f77 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -29,7 +29,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.utils.helper.XmlHelper; @@ -124,7 +124,8 @@ public class PrivilegeModelDomWriter { roleElement.setAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - for (Privilege privilege : role.getPrivilegeMap().values()) { + for (String privilegeName : role.getPrivilegeNames()) { + IPrivilege privilege = role.getPrivilege(privilegeName); // create the privilege element Element privilegeElement = doc.createElement(XmlConstants.XML_PRIVILEGE); diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index fa85e9843..edc0490a2 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -36,8 +36,9 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.utils.helper.StringHelper; @@ -149,7 +150,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { private Set denyList; private Set allowList; - private Map privileges; + private Map privileges; /** * @@ -162,7 +163,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { * */ private void init() { - this.privileges = new HashMap(); + this.privileges = new HashMap(); this.text = null; @@ -204,7 +205,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.denyList.add(this.text.toString().trim()); } else if (qName.equals("Privilege")) { - Privilege privilege = new Privilege(this.privilegeName, this.privilegePolicy, this.allAllowed, + IPrivilege privilege = new PrivilegeImpl(this.privilegeName, this.privilegePolicy, this.allAllowed, this.denyList, this.allowList); this.privileges.put(this.privilegeName, privilege); diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index f1f9406af..06ee3ca96 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -36,8 +36,8 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.helper.CertificateThreadLocal; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; @@ -47,6 +47,7 @@ import ch.eitchnet.privilege.test.model.TestRestrictable; import ch.eitchnet.privilege.test.model.TestSystemUserAction; import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; import ch.eitchnet.privilege.xml.InitializationHelper; +import ch.eitchnet.utils.helper.ArraysHelper; import ch.eitchnet.utils.helper.FileHelper; /** @@ -82,11 +83,13 @@ public class PrivilegeTest { @BeforeClass public static void init() throws Exception { try { + destroy(); + // copy configuration to tmp String pwd = System.getProperty("user.dir"); File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModel.xml"); - File tmpPrivilegeModelFile = new File(pwd + "/target/test/PrivilegeModel.xml"); + File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModel.xml"); if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + tmpPrivilegeModelFile.getAbsolutePath()); @@ -102,7 +105,7 @@ public class PrivilegeTest { throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); } catch (Exception e) { - PrivilegeTest.logger.error(e.getMessage(), e); + logger.error(e.getMessage(), e); throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); } @@ -114,7 +117,7 @@ public class PrivilegeTest { // delete temporary file String pwd = System.getProperty("user.dir"); - File tmpPrivilegeModelFile = new File(pwd + "/target/test/PrivilegeModel.xml"); + File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModel.xml"); if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + tmpPrivilegeModelFile.getAbsolutePath()); @@ -136,154 +139,139 @@ public class PrivilegeTest { File privilegeConfigFile = new File(pwd + "/config/Privilege.xml"); // initialize privilege - PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeConfigFile); + privilegeHandler = InitializationHelper.initializeFromXml(privilegeConfigFile); } catch (Exception e) { - PrivilegeTest.logger.error(e.getMessage(), e); + logger.error(e.getMessage(), e); throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); } } - private byte[] copyBytes(byte[] bytes) { - byte[] copy = new byte[bytes.length]; - System.arraycopy(bytes, 0, copy, 0, bytes.length); - return copy; + private void login(String username, byte[] password) { + Certificate certificate = privilegeHandler.authenticate(username, password); + Assert.assertTrue("Certificate is null!", certificate != null); + PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); + PrivilegeContext.set(privilegeContext); + } + + private void logout() { + try { + PrivilegeContext privilegeContext = PrivilegeContext.get(); + privilegeHandler.invalidateSession(privilegeContext.getCertificate()); + } catch (PrivilegeException e) { + String msg = "There is no PrivilegeContext currently bound to the ThreadLocal!"; + if (!e.getMessage().equals(msg)) + throw e; + } finally { + PrivilegeContext.set(null); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test public void testAuthenticationOk() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - Assert.assertTrue("Certificate is null!", certificate != null); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + } finally { + logout(); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_BAD)); - Assert.assertTrue("Certificate is null!", certificate != null); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_BAD)); + } finally { + logout(); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, null); - Assert.assertTrue("Certificate is null!", certificate != null); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + login(ADMIN, null); + } finally { + logout(); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test public void testAddRoleTemp() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_TEMP, privilegeMap); + Map privilegeMap = new HashMap(); + RoleRep roleRep = new RoleRep(ROLE_TEMP, privilegeMap); - PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test public void testPerformRestrictableAsAdmin() throws Exception { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - Assert.assertTrue("Certificate is null!", certificate != null); + // see if admin can perform restrictable + Restrictable restrictable = new TestRestrictable(); + PrivilegeContext.get().validateAction(restrictable); - // see if eitch can perform restrictable - Restrictable restrictable = new TestRestrictable(); - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } finally { + logout(); + } } /** * Tests if an action can be performed as a system user - * - * @throws Exception - * if something goes wrong */ @Test public void testPerformSystemRestrictable() throws Exception { - // create the action to be performed as a system user - TestSystemUserAction action = new TestSystemUserAction(PrivilegeTest.privilegeHandler); - - // and then perform the action - PrivilegeTest.privilegeHandler.runAsSystem(PrivilegeTest.SYSTEM_USER_ADMIN, action); + // create the action to be performed as a system user and then perform the action + TestSystemUserAction action = new TestSystemUserAction(); + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); } /** * Checks that the system user can not perform a valid action, but illegal privilege - * - * @throws Exception - * if something goes wrong */ @Test(expected = PrivilegeException.class) public void testPerformSystemRestrictableFailPrivilege() throws Exception { + try { + // create the action to be performed as a system user + TestSystemUserActionDeny action = new TestSystemUserActionDeny(); - // create the action to be performed as a system user - TestSystemUserActionDeny action = new TestSystemUserActionDeny(PrivilegeTest.privilegeHandler); - - // and then perform the action - PrivilegeTest.privilegeHandler.runAsSystem(PrivilegeTest.SYSTEM_USER_ADMIN, action); + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + } finally { + logout(); + } } /** * System user may not login - * - * @throws Exception - * if something goes wrong */ @Test(expected = AccessDeniedException.class) public void testLoginSystemUser() throws Exception { - - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.SYSTEM_USER_ADMIN, - PrivilegeTest.SYSTEM_USER_ADMIN.getBytes()); + try { + login(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); + } finally { + logout(); + } } @Test - public void testCertificateThreadLocal() { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // set certificate into thread local - CertificateThreadLocal.getInstance().set(certificate); - - // see if bob can perform restrictable by returning certificate from CertificateThreadLocal - Restrictable restrictable = new TestRestrictable(); + public void testPrivilegeContext() { try { - PrivilegeTest.privilegeHandler.actionAllowed(CertificateThreadLocal.getInstance().get(), restrictable); + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Restrictable restrictable = new TestRestrictable(); + PrivilegeContext.get().validateAction(restrictable); } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + logout(); } } @@ -308,8 +296,6 @@ public class PrivilegeTest { *
  • perform restrictable as bob
  • * * - * @throws Exception - * if something goes wrong */ @Test public void testUserStory() throws Exception { @@ -334,216 +320,243 @@ public class PrivilegeTest { } private void performRestrictableAsBob() { - Certificate certificate; - // testPerformRestrictableAsBob - // Tests if the user bob, who now has AppUser role can perform restrictable - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - // see if bob can perform restrictable - Restrictable restrictable = new TestRestrictable(); - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testPerformRestrictableAsBob + // Tests if the user bob, who now has AppUser role can perform restrictable + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + PrivilegeContext.get().validateAction(restrictable); + } finally { + logout(); + } } private void addRoleAppToBob() { - Certificate certificate; - // testAddAppRoleToBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); - PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAddAppRoleToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); + logger.info("Added " + ROLE_APP_USER + " to " + BOB); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failPerformRestrictableAsBobNoRoleApp() { - Certificate certificate; - // testFailPerformRestrictableAsBob - // Tests if the user bob, who does not have AppUser role can perform restrictable - // this will fail as bob does not have role app - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - // see if bob can perform restrictable - Restrictable restrictable = new TestRestrictable(); try { - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); + // testFailPerformRestrictableAsBob + // Tests if the user bob, who does not have AppUser role can perform restrictable + // this will fail as bob does not have role app + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + PrivilegeContext.get().validateAction(restrictable); Assert.fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; Assert.assertEquals(msg, e.getLocalizedMessage()); } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + logout(); } } private void authAsTed() { - Certificate certificate; - // testAuthAsTed - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAuthAsTed + login(TED, ArraysHelper.copyOf(PASS_TED)); + } finally { + logout(); + } } private void tedChangesOwnPass() { - Certificate certificate; - // testTedChangesOwnPwd - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_TED)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testTedChangesOwnPwd + login(TED, ArraysHelper.copyOf(PASS_DEF)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_TED)); + } finally { + logout(); + } } private void setPassForTedAsBob() { - Certificate certificate; - // testSetTedPwdAsBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - // set ted's password to default - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_DEF)); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testSetTedPwdAsBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // set ted's password to default + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_DEF)); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failAuthAsTedNoPass() { - // testFailAuthAsTedNoPass - // Will fail because user ted has no password try { - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + // testFailAuthAsTedNoPass + // Will fail because user ted has no password + login(TED, ArraysHelper.copyOf(PASS_TED)); org.junit.Assert.fail("User Ted may not authenticate because the user has no password!"); } catch (PrivilegeException e) { String msg = "User ted has no password and may not login!"; Assert.assertEquals(msg, e.getMessage()); + } finally { + logout(); } } private void addTedAsBob() { - Certificate certificate; - UserRep userRep; - // testAddUserTedAsBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - // let's add a new user ted - HashSet roles = new HashSet(); - roles.add(PrivilegeTest.ROLE_USER); - userRep = new UserRep("2", PrivilegeTest.TED, "Ted", "Newman", UserState.ENABLED, roles, null, - new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + UserRep userRep; + // testAddUserTedAsBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // let's add a new user ted + HashSet roles = new HashSet(); + roles.add(ROLE_USER); + userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, + new HashMap()); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); + logger.info("Added user " + TED); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void addRoleAdminToBob() { - Certificate certificate; - // testAddAdminRoleToBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, - PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAddAdminRoleToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failAddTedAsBobNotAdmin() { - Certificate certificate; - UserRep userRep; - // testFailAddUserTedAsBob - // Will fail because user bob does not have admin rights - // auth as Bob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - // let's add a new user Ted - userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, new HashSet(), - null, new HashMap()); + Certificate certificate = null; try { - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + UserRep userRep; + // testFailAddUserTedAsBob + // Will fail because user bob does not have admin rights + // auth as Bob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // let's add a new user Ted + userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, + new HashMap()); + certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); Assert.fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { - String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate.toString(); + String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate; Assert.assertEquals(msg, e.getMessage()); } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + logout(); } } private void authAsBob() { - Certificate certificate; - // testAuthAsBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAuthAsBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + } finally { + logout(); + } } private void addRoleUserToBob() { - Certificate certificate; - // testAddRoleUserToBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAddRoleUserToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); + privilegeHandler.persist(certificate); + logout(); + } finally { + logout(); + } } private void addRoleUser() { - Certificate certificate; - // add role user - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); - PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // add role user + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Map privilegeMap = new HashMap(); + RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failAuthAsBobNoRole() { - // testFailAuthUserBob - // Will fail as user bob has no role try { - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + // testFailAuthUserBob + // Will fail as user bob has no role + privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); org.junit.Assert.fail("User Bob may not authenticate because the user has no role"); } catch (PrivilegeException e) { String msg = "User bob does not have any roles defined!"; Assert.assertEquals(msg, e.getMessage()); + } finally { + logout(); } } private void enableBob() { - Certificate certificate; - // testEnableUserBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testEnableUserBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failAuthAsBobNotEnabled() { - // testFailAuthAsBob - // Will fail because user bob is not yet enabled try { - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + // testFailAuthAsBob + // Will fail because user bob is not yet enabled + privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); org.junit.Assert.fail("User Bob may not authenticate because the user is not yet enabled!"); } catch (PrivilegeException e) { String msg = "User bob does not have state ENABLED and can not login!"; Assert.assertEquals(msg, e.getMessage()); + } finally { + logout(); } } private void addBobAsAdmin() { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - // let's add a new user bob - UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), - null, new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); + // let's add a new user bob + UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, + new HashMap()); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); + logger.info("Added user " + BOB); - // set bob's password - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.logger.info("Set Bob's password"); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + // set bob's password + privilegeHandler.setUserPassword(certificate, BOB, ArraysHelper.copyOf(PASS_BOB)); + logger.info("Set Bob's password"); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } } diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index da7e2a1b5..23db59a65 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -23,6 +23,7 @@ package ch.eitchnet.privilege.test; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -40,9 +41,10 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.DefaultEncryptionHandler; import ch.eitchnet.privilege.handler.XmlPersistenceHandler; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; @@ -71,6 +73,7 @@ public class XmlTest { */ @BeforeClass public static void init() throws Exception { + destroy(); File tmpDir = new File("target/test"); if (tmpDir.exists()) @@ -179,7 +182,7 @@ public class XmlTest { Map propertyMap; Set userRoles; - Map privilegeMap; + Map privilegeMap; List users = new ArrayList(); propertyMap = new HashMap(); @@ -197,16 +200,17 @@ public class XmlTest { propertyMap)); List roles = new ArrayList(); - privilegeMap = new HashMap(); - privilegeMap.put("priv1", new Privilege("priv1", "DefaultPrivilege", true, null, null)); + Set list = Collections.emptySet(); + privilegeMap = new HashMap(); + privilegeMap.put("priv1", new PrivilegeImpl("priv1", "DefaultPrivilege", true, list, list)); roles.add(new Role("role1", privilegeMap)); - privilegeMap = new HashMap(); + privilegeMap = new HashMap(); Set denyList = new HashSet(); denyList.add("myself"); Set allowList = new HashSet(); allowList.add("other"); - privilegeMap.put("priv2", new Privilege("priv2", "DefaultPrivilege", false, denyList, allowList)); + privilegeMap.put("priv2", new PrivilegeImpl("priv2", "DefaultPrivilege", false, denyList, allowList)); roles.add(new Role("role2", privilegeMap)); File modelFile = new File("./target/test/PrivilegeModelTest.xml"); @@ -214,6 +218,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - Assert.assertEquals("8E1E82278162F21B1654C2E059570BBCB3CB63B053C1DD784BC8E225E8CFD04F", fileHash); + Assert.assertEquals("9007F172BBD7BA51BA3E67199CE0AFCBC8645AF0AC02028ABE54BA6A2FC134B0", fileHash); } } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index f247d680b..c86a5fdb0 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -19,34 +19,20 @@ */ package ch.eitchnet.privilege.test.model; -import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; /** * @author Robert von Burg - * + * */ public class TestSystemUserAction implements SystemUserAction { - private PrivilegeHandler handler; - - /** - * - */ - public TestSystemUserAction(PrivilegeHandler handler) { - this.handler = handler; - } - - /** - * @see ch.eitchnet.privilege.handler.SystemUserAction#execute(ch.eitchnet.privilege.model.Certificate) - */ @Override - public void execute(Certificate certificate) { - + public void execute(PrivilegeContext context) { TestSystemRestrictable restrictable = new TestSystemRestrictable(); - - this.handler.actionAllowed(certificate, restrictable); + PrivilegeContext.set(context); + context.validateAction(restrictable); + PrivilegeContext.set(null); } - } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java index 14f6e566a..d8ed59616 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -19,9 +19,8 @@ */ package ch.eitchnet.privilege.test.model; -import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; /** * @author Robert von Burg @@ -29,22 +28,9 @@ import ch.eitchnet.privilege.model.Certificate; */ public class TestSystemUserActionDeny implements SystemUserAction { - private PrivilegeHandler handler; - - /** - * - */ - public TestSystemUserActionDeny(PrivilegeHandler handler) { - this.handler = handler; - } - - /** - * @see ch.eitchnet.privilege.handler.SystemUserAction#execute(ch.eitchnet.privilege.model.Certificate) - */ @Override - public void execute(Certificate certificate) { - + public void execute(PrivilegeContext privilegeContext) { TestRestrictable restrictable = new TestRestrictable(); - this.handler.actionAllowed(certificate, restrictable); + privilegeContext.validateAction(restrictable); } } From 7700694a84284f6cdd301d69d0e8dbca3a31753c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 9 Apr 2013 19:25:07 +0200 Subject: [PATCH 144/457] [New] add new method ArraysHelper.copyOf(byte[]) --- .../eitchnet/utils/helper/ArraysHelper.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java b/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java index c54ac3e58..74a22a8cf 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java @@ -21,18 +21,41 @@ */ package ch.eitchnet.utils.helper; +import java.util.Arrays; + /** * @author Robert von Burg * */ public class ArraysHelper { + /** + * Returns true if the byte array contains the given byte value + * + * @param bytes + * the array to search in + * @param searchByte + * the value to search for + * + * @return true if found, false if not + */ public static boolean contains(byte[] bytes, byte searchByte) { for (byte b : bytes) { if (b == searchByte) return true; } - return false; } + + /** + * Creates a simple copy of the given array + * + * @param bytes + * the array to copy + * + * @return the copy + */ + public static byte[] copyOf(byte[] bytes) { + return Arrays.copyOf(bytes, bytes.length); + } } From 6eb34ff532957f159e4fb1452aacf35098b989c6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 19 Apr 2013 21:33:18 +0200 Subject: [PATCH 145/457] [Minor] fixed problem with build on mvn cli The problem was due to a naming problem with the PrivilegeMessages class and property bundle having the same name and being in the same package. Moved the properties file now to the resources directory --- src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java | 2 +- .../privilege/i18n => resources}/PrivilegeMessages.properties | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/main/{java/ch/eitchnet/privilege/i18n => resources}/PrivilegeMessages.properties (100%) diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java index c84da0b69..da80d9f63 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java @@ -29,7 +29,7 @@ import java.util.ResourceBundle; * */ public class PrivilegeMessages { - private static final String BUNDLE_NAME = "ch.eitchnet.privilege.i18n.PrivilegeMessages"; //$NON-NLS-1$ + private static final String BUNDLE_NAME = "PrivilegeMessages"; //$NON-NLS-1$ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties b/src/main/resources/PrivilegeMessages.properties similarity index 100% rename from src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties rename to src/main/resources/PrivilegeMessages.properties From 0605dd784f843c48da1eadfa9c40ca2a1d9f1195 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 7 Aug 2013 22:55:18 +0200 Subject: [PATCH 146/457] [Devel] reimplementing using visitor pattern Changing to the visitor pattern will make it easier to implement a SAX reader and writer --- pom.xml | 332 ++++++-------- .../xmlpers/FormattingXmlStreamWriter.java | 432 ++++++++++++++++++ src/main/java/ch/eitchnet/xmlpers/Main.java | 64 +++ .../ch/eitchnet/xmlpers/XmlSaxWriter.java | 31 ++ 4 files changed, 672 insertions(+), 187 deletions(-) create mode 100644 src/main/java/ch/eitchnet/xmlpers/FormattingXmlStreamWriter.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/Main.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/XmlSaxWriter.java diff --git a/pom.xml b/pom.xml index 9343932a7..5394db648 100644 --- a/pom.xml +++ b/pom.xml @@ -1,115 +1,73 @@ - 4.0.0 - ch.eitchnet - ch.eitchnet.xmlpers - jar - 0.1.0-SNAPSHOT - ch.eitchnet.xmlpers - https://github.com/eitch/ch.eitchnet.xmlpers + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + ch.eitchnet + ch.eitchnet.xmlpers + jar + 0.1.0-SNAPSHOT + ch.eitchnet.xmlpers + https://github.com/eitch/ch.eitchnet.xmlpers - - UTF-8 - + + UTF-8 + - + - 2011 - - - GNU Lesser General Public License - http://www.gnu.org/licenses/lgpl.html - repo - - - - eitchnet.ch - http://blog.eitchnet.ch - - - - eitch - Robert von Vurg - eitch@eitchnet.ch - http://blog.eitchnet.ch - eitchnet.ch - http://blog.eitchnet.ch - - architect - developer - - +1 - - http://localhost - - - + 2011 + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl.html + repo + + + + eitchnet.ch + http://blog.eitchnet.ch + + + + eitch + Robert von Vurg + eitch@eitchnet.ch + http://blog.eitchnet.ch + eitchnet.ch + http://blog.eitchnet.ch + + architect + developer + + +1 + + http://localhost + + + - - Github Issues - https://github.com/eitch/ch.eitchnet.xmlpers/issues - + + Github Issues + https://github.com/eitch/ch.eitchnet.xmlpers/issues + - - - - User List - user-subscribe@127.0.0.1 - user-unsubscribe@127.0.0.1 - user@127.0.0.1 - http://127.0.0.1/user/ - - http://base.google.com/base/1/127.0.0.1 - - - - --> + + scm:git:https://github.com/eitch/ch.eitchnet.xmlpers.git + scm:git:git@github.com:eitch/ch.eitchnet.xmlpers.git + https://github.com/eitch/ch.eitchnet.xmlpers + - - scm:git:https://github.com/eitch/ch.eitchnet.xmlpers.git - scm:git:git@github.com:eitch/ch.eitchnet.xmlpers.git - https://github.com/eitch/ch.eitchnet.xmlpers - + - - deployment @@ -123,93 +81,93 @@ - - - junit - junit - 4.10 - test - - - ch.eitchnet - ch.eitchnet.utils - 0.1.0-SNAPSHOT - - - org.slf4j - slf4j-api - 1.7.2 - - - org.slf4j - slf4j-log4j12 - 1.7.2 - test - - + + + junit + junit + 4.10 + test + + + ch.eitchnet + ch.eitchnet.utils + 0.1.0-SNAPSHOT + + + org.slf4j + slf4j-api + 1.7.2 + + + org.slf4j + slf4j-log4j12 + 1.7.2 + test + + - - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - true - true - - + + - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar-no-fork - - - - + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - true - true - - - - - + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + - - org.apache.maven.plugins - maven-site-plugin - 2.3 - - UTF-8 - - + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + - - + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 2.3 + + UTF-8 + + + + + diff --git a/src/main/java/ch/eitchnet/xmlpers/FormattingXmlStreamWriter.java b/src/main/java/ch/eitchnet/xmlpers/FormattingXmlStreamWriter.java new file mode 100644 index 000000000..b3ae34e91 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/FormattingXmlStreamWriter.java @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * @author Robert von Burg + */ +public class FormattingXmlStreamWriter implements XMLStreamWriter { + + private final XMLStreamWriter writer; + + /** + * + * @param writer + */ + public FormattingXmlStreamWriter(XMLStreamWriter writer) { + this.writer = writer; + } + + // + // Start of elements + // + + /** + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeStartDocument() + */ + @Override + public void writeStartDocument() throws XMLStreamException { + preStart(); + this.writer.writeStartDocument(); + postStart(); + } + + private void preStart() { + + } + + private void postStart() throws XMLStreamException { + //this.writer.writeCharacters(new char[] { '\n' }, 0, 1); + } + + private void preEnd() { + } + + private void postEnd() { + } + + /** + * @param version + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeStartDocument(java.lang.String) + */ + @Override + public void writeStartDocument(String version) throws XMLStreamException { + preStart(); + this.writer.writeStartDocument(version); + postStart(); + } + + /** + * @param encoding + * @param version + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeStartDocument(java.lang.String, java.lang.String) + */ + @Override + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + preStart(); + this.writer.writeStartDocument(encoding, version); + postStart(); + } + + /** + * @param localName + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeStartElement(java.lang.String) + */ + @Override + public void writeStartElement(String localName) throws XMLStreamException { + preStart(); + this.writer.writeStartElement(localName); + postStart(); + } + + /** + * @param namespaceURI + * @param localName + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeStartElement(java.lang.String, java.lang.String) + */ + @Override + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + preStart(); + this.writer.writeStartElement(namespaceURI, localName); + postStart(); + } + + /** + * @param prefix + * @param localName + * @param namespaceURI + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeStartElement(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + preStart(); + this.writer.writeStartElement(prefix, localName, namespaceURI); + postStart(); + } + + // + // End of elements + // + + /** + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeEndElement() + */ + @Override + public void writeEndElement() throws XMLStreamException { + preStart(); + this.writer.writeEndElement(); + postStart(); + } + + /** + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeEndDocument() + */ + @Override + public void writeEndDocument() throws XMLStreamException { + preStart(); + this.writer.writeEndDocument(); + postStart(); + } + + // + // Empty elements + // + + /** + * @param namespaceURI + * @param localName + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeEmptyElement(java.lang.String, java.lang.String) + */ + @Override + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + preEnd(); + this.writer.writeEmptyElement(namespaceURI, localName); + postEnd(); + } + + /** + * @param prefix + * @param localName + * @param namespaceURI + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeEmptyElement(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + preEnd(); + this.writer.writeEmptyElement(prefix, localName, namespaceURI); + postEnd(); + } + + /** + * @param localName + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeEmptyElement(java.lang.String) + */ + @Override + public void writeEmptyElement(String localName) throws XMLStreamException { + this.writer.writeEmptyElement(localName); + } + + // + // attributes + // + + /** + * @param localName + * @param value + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeAttribute(java.lang.String, java.lang.String) + */ + @Override + public void writeAttribute(String localName, String value) throws XMLStreamException { + this.writer.writeAttribute(localName, value); + } + + /** + * @param prefix + * @param namespaceURI + * @param localName + * @param value + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeAttribute(java.lang.String, java.lang.String, java.lang.String, + * java.lang.String) + */ + @Override + public void writeAttribute(String prefix, String namespaceURI, String localName, String value) + throws XMLStreamException { + this.writer.writeAttribute(prefix, namespaceURI, localName, value); + } + + /** + * @param namespaceURI + * @param localName + * @param value + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeAttribute(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { + this.writer.writeAttribute(namespaceURI, localName, value); + } + + // + // other + // + + /** + * @param data + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeComment(java.lang.String) + */ + @Override + public void writeComment(String data) throws XMLStreamException { + this.writer.writeComment(data); + } + + /** + * @param target + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeProcessingInstruction(java.lang.String) + */ + @Override + public void writeProcessingInstruction(String target) throws XMLStreamException { + this.writer.writeProcessingInstruction(target); + } + + /** + * @param target + * @param data + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeProcessingInstruction(java.lang.String, java.lang.String) + */ + @Override + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + this.writer.writeProcessingInstruction(target, data); + } + + /** + * @param data + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeCData(java.lang.String) + */ + @Override + public void writeCData(String data) throws XMLStreamException { + this.writer.writeCData(data); + } + + /** + * @param dtd + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeDTD(java.lang.String) + */ + @Override + public void writeDTD(String dtd) throws XMLStreamException { + this.writer.writeDTD(dtd); + } + + /** + * @param prefix + * @param namespaceURI + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeNamespace(java.lang.String, java.lang.String) + */ + @Override + public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { + this.writer.writeNamespace(prefix, namespaceURI); + } + + /** + * @param namespaceURI + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeDefaultNamespace(java.lang.String) + */ + @Override + public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { + this.writer.writeDefaultNamespace(namespaceURI); + } + + /** + * @param name + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeEntityRef(java.lang.String) + */ + @Override + public void writeEntityRef(String name) throws XMLStreamException { + this.writer.writeEntityRef(name); + } + + /** + * @param text + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeCharacters(java.lang.String) + */ + @Override + public void writeCharacters(String text) throws XMLStreamException { + this.writer.writeCharacters(text); + } + + /** + * @param text + * @param start + * @param len + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeCharacters(char[], int, int) + */ + @Override + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + this.writer.writeCharacters(text, start, len); + } + + /** + * @param uri + * @return + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#getPrefix(java.lang.String) + */ + @Override + public String getPrefix(String uri) throws XMLStreamException { + return this.writer.getPrefix(uri); + } + + /** + * @param prefix + * @param uri + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#setPrefix(java.lang.String, java.lang.String) + */ + @Override + public void setPrefix(String prefix, String uri) throws XMLStreamException { + this.writer.setPrefix(prefix, uri); + } + + /** + * @param uri + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#setDefaultNamespace(java.lang.String) + */ + @Override + public void setDefaultNamespace(String uri) throws XMLStreamException { + this.writer.setDefaultNamespace(uri); + } + + /** + * @param context + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#setNamespaceContext(javax.xml.namespace.NamespaceContext) + */ + @Override + public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { + this.writer.setNamespaceContext(context); + } + + /** + * @return + * @see javax.xml.stream.XMLStreamWriter#getNamespaceContext() + */ + @Override + public NamespaceContext getNamespaceContext() { + return this.writer.getNamespaceContext(); + } + + /** + * @param name + * @return + * @throws IllegalArgumentException + * @see javax.xml.stream.XMLStreamWriter#getProperty(java.lang.String) + */ + @Override + public Object getProperty(String name) throws IllegalArgumentException { + return this.writer.getProperty(name); + } + + /** + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#close() + */ + @Override + public void close() throws XMLStreamException { + this.writer.close(); + } + + /** + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#flush() + */ + @Override + public void flush() throws XMLStreamException { + this.writer.flush(); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/Main.java b/src/main/java/ch/eitchnet/xmlpers/Main.java new file mode 100644 index 000000000..b9717ab6f --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/Main.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers; + +import java.io.File; +import java.io.FileWriter; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamWriter; + +/** + * @author Robert von Burg + * + */ +public class Main { + + public static void main(String[] args) throws Exception { + + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + + File file = new File("output.xml"); + XMLStreamWriter writer = factory.createXMLStreamWriter(new FileWriter(file)); + System.out.println("writer: " + writer.getClass().getName()); + + //writer = new com.sun.xml.internal.txw2.output.IndentingXMLStreamWriter(writer); + writer = new FormattingXmlStreamWriter(writer); + + writer.writeStartDocument(); + writer.writeStartElement("document"); + writer.writeStartElement("data"); + writer.writeAttribute("name", "value"); + writer.writeEndElement(); + writer.writeEndElement(); + writer.writeEndDocument(); + + writer.flush(); + writer.close(); + System.out.println("Wrote to " + file.getAbsolutePath()); + + //Transformer transformer = TransformerFactory.newInstance().newTransformer(); + //Result outputTarget = new StaxR; + //Source xmlSource; + //transformer.transform(xmlSource, outputTarget); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlSaxWriter.java b/src/main/java/ch/eitchnet/xmlpers/XmlSaxWriter.java new file mode 100644 index 000000000..8a8a80609 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/XmlSaxWriter.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers; + + +/** + * @author Robert von Burg + * + */ +public interface XmlSaxWriter { + +} From d2ee280dbe3dda20edfbbdfc20df7ca011e61348 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Aug 2013 00:00:18 +0200 Subject: [PATCH 147/457] [New] Refactored the ObjectCache to not used a generic T in the class There was no use and it only made the code unreadable. --- .../utils/objectfilter/ObjectCache.java | 12 +- .../utils/objectfilter/ObjectFilter.java | 135 ++++++++---------- 2 files changed, 67 insertions(+), 80 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java index 57e6a0132..dfa3ea8ac 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -36,10 +36,8 @@ import org.slf4j.LoggerFactory; *

    * * @author Michael Gatto - * - * @param */ -public class ObjectCache { +public class ObjectCache { private final static Logger logger = LoggerFactory.getLogger(ObjectCache.class); @@ -54,7 +52,7 @@ public class ObjectCache { /** * object The object that shall be cached */ - private T object; + private ITransactionObject object; /** * operation The operation that has occurred on this object. */ @@ -65,7 +63,7 @@ public class ObjectCache { * @param object * @param operation */ - public ObjectCache(String key, T object, Operation operation) { + public ObjectCache(String key, ITransactionObject object, Operation operation) { this.id = object.getTransactionID(); this.key = key; @@ -83,7 +81,7 @@ public class ObjectCache { * * @param object */ - public void setObject(T object) { + public void setObject(ITransactionObject object) { if (ObjectCache.logger.isDebugEnabled()) { ObjectCache.logger.debug("Updating ID " + this.id + " to value " + object.toString()); } @@ -120,7 +118,7 @@ public class ObjectCache { /** * @return the object */ - public T getObject() { + public ITransactionObject getObject() { return this.object; } diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index f7b93124b..dc1f7194f 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -19,6 +19,7 @@ */ package ch.eitchnet.utils.objectfilter; +import java.text.MessageFormat; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -76,16 +77,12 @@ import org.slf4j.LoggerFactory; * * @author Michael Gatto (initial version) * @author Robert von Burg - * - * @param */ -public class ObjectFilter { - - // XXX think about removing the generic T, as there is no sense in it +public class ObjectFilter { private final static Logger logger = LoggerFactory.getLogger(ObjectFilter.class); - private HashMap> cache = new HashMap>(); + private HashMap cache = new HashMap(); private HashSet keySet = new HashSet(); private static long id = ITransactionObject.UNSET; @@ -116,7 +113,7 @@ public class ObjectFilter { * @param objectToAdd * The object for which addition shall be registered. */ - public void add(String key, T objectToAdd) { + public void add(String key, ITransactionObject objectToAdd) { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("add object " + objectToAdd + " with key " + key); @@ -131,29 +128,21 @@ public class ObjectFilter { // run. Hence, we create an ID and add it to the cache. id = dispenseID(); objectToAdd.setTransactionID(id); - ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); + ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); this.cache.put(id, cacheObj); } else { - ObjectCache cached = this.cache.get(Long.valueOf(objectToAdd.getTransactionID())); + ObjectCache cached = this.cache.get(Long.valueOf(objectToAdd.getTransactionID())); if (cached == null) { // The object got an ID during this run, but was not added to the cache. // Hence, we add it now, with the current operation. - ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); + ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); this.cache.put(id, cacheObj); } else { String existingKey = cached.getKey(); if (!existingKey.equals(key)) { - throw new RuntimeException( - "Invalid key provided for object with transaction ID " - + Long.toString(id) - + " and operation " - + Operation.ADD.toString() - + ": existing key is " - + existingKey - + ", new key is " - + key - + ". Object may be present in the same filter instance only once, registered using one key only. Object:" - + objectToAdd.toString()); + 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}"; + throw new RuntimeException(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... @@ -196,7 +185,7 @@ public class ObjectFilter { * @param objectToUpdate * The object for which update shall be registered. */ - public void update(String key, T objectToUpdate) { + public void update(String key, ITransactionObject objectToUpdate) { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("update object " + objectToUpdate + " with key " + key); @@ -209,14 +198,14 @@ public class ObjectFilter { if (id == ITransactionObject.UNSET) { id = dispenseID(); objectToUpdate.setTransactionID(id); - ObjectCache cacheObj = new ObjectCache(key, objectToUpdate, Operation.MODIFY); + ObjectCache cacheObj = new ObjectCache(key, objectToUpdate, Operation.MODIFY); this.cache.put(id, cacheObj); } else { - ObjectCache cached = this.cache.get(Long.valueOf(objectToUpdate.getTransactionID())); + ObjectCache cached = this.cache.get(Long.valueOf(objectToUpdate.getTransactionID())); 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(key, objectToUpdate, Operation.MODIFY); + ObjectCache cacheObj = new ObjectCache(key, objectToUpdate, Operation.MODIFY); this.cache.put(id, cacheObj); } else { String existingKey = cached.getKey(); @@ -274,7 +263,7 @@ public class ObjectFilter { * @param objectToRemove * The object for which removal shall be registered. */ - public void remove(String key, T objectToRemove) { + public void remove(String key, ITransactionObject objectToRemove) { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("remove object " + objectToRemove + " with key " + key); @@ -286,14 +275,14 @@ public class ObjectFilter { if (id == ITransactionObject.UNSET) { id = dispenseID(); objectToRemove.setTransactionID(id); - ObjectCache cacheObj = new ObjectCache(key, objectToRemove, Operation.REMOVE); + ObjectCache cacheObj = new ObjectCache(key, objectToRemove, Operation.REMOVE); this.cache.put(id, cacheObj); } else { - ObjectCache cached = this.cache.get(Long.valueOf(id)); + ObjectCache cached = this.cache.get(Long.valueOf(id)); 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(key, objectToRemove, Operation.REMOVE); + ObjectCache cacheObj = new ObjectCache(key, objectToRemove, Operation.REMOVE); this.cache.put(id, cacheObj); } else { String existingKey = cached.getKey(); @@ -337,8 +326,8 @@ public class ObjectFilter { * @param addedObjects * The objects for which addition shall be registered. */ - public void addAll(String key, Collection addedObjects) { - for (T addObj : addedObjects) { + public void addAll(String key, Collection addedObjects) { + for (ITransactionObject addObj : addedObjects) { add(key, addObj); } } @@ -351,8 +340,8 @@ public class ObjectFilter { * @param updatedObjects * The objects for which update shall be registered. */ - public void updateAll(String key, Collection updatedObjects) { - for (T update : updatedObjects) { + public void updateAll(String key, Collection updatedObjects) { + for (ITransactionObject update : updatedObjects) { update(key, update); } } @@ -365,8 +354,8 @@ public class ObjectFilter { * @param removedObjects * The objects for which removal shall be registered. */ - public void removeAll(String key, Collection removedObjects) { - for (T removed : removedObjects) { + public void removeAll(String key, Collection removedObjects) { + for (ITransactionObject removed : removedObjects) { remove(key, removed); } } @@ -377,7 +366,7 @@ public class ObjectFilter { * @param object * The object that shall be registered for addition */ - public void add(T object) { + public void add(ITransactionObject object) { add(object.getClass().getName(), object); } @@ -387,7 +376,7 @@ public class ObjectFilter { * @param object * The object that shall be registered for updating */ - public void update(T object) { + public void update(ITransactionObject object) { update(object.getClass().getName(), object); } @@ -397,7 +386,7 @@ public class ObjectFilter { * @param object * The object that shall be registered for removal */ - public void remove(T object) { + public void remove(ITransactionObject object) { remove(object.getClass().getName(), object); } @@ -408,8 +397,8 @@ public class ObjectFilter { * @param objects * The objects that shall be registered for addition */ - public void addAll(List objects) { - for (T addedObj : objects) { + public void addAll(List objects) { + for (ITransactionObject addedObj : objects) { add(addedObj.getClass().getName(), addedObj); } } @@ -421,8 +410,8 @@ public class ObjectFilter { * @param updateObjects * The objects that shall be registered for updating */ - public void updateAll(List updateObjects) { - for (T update : updateObjects) { + public void updateAll(List updateObjects) { + for (ITransactionObject update : updateObjects) { update(update.getClass().getName(), update); } } @@ -434,8 +423,8 @@ public class ObjectFilter { * @param removedObjects * The objects that shall be registered for removal */ - public void removeAll(List removedObjects) { - for (T removed : removedObjects) { + public void removeAll(List removedObjects) { + for (ITransactionObject removed : removedObjects) { remove(removed.getClass().getName(), removed); } } @@ -447,10 +436,10 @@ public class ObjectFilter { * The registration key of the objects to match * @return The list of all objects registered under the given key and that need to be added. */ - public List getAdded(String key) { - List addedObjects = new LinkedList(); - Collection> allObjs = this.cache.values(); - for (ObjectCache objectCache : allObjs) { + public List getAdded(String key) { + List addedObjects = new LinkedList(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { addedObjects.add(objectCache.getObject()); } @@ -467,10 +456,10 @@ public class ObjectFilter { * The registration key of the objects to match * @return The list of all objects registered under the given key and that need to be added. */ - public List getAdded(Class clazz, String key) { + public List getAdded(Class clazz, String key) { List addedObjects = new LinkedList(); - Collection> allObjs = this.cache.values(); - for (ObjectCache objectCache : allObjs) { + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { if (objectCache.getObject().getClass() == clazz) { @SuppressWarnings("unchecked") @@ -489,10 +478,10 @@ public class ObjectFilter { * registration key of the objects to match * @return The list of all objects registered under the given key and that need to be updated. */ - public List getUpdated(String key) { - List updatedObjects = new LinkedList(); - Collection> allObjs = this.cache.values(); - for (ObjectCache objectCache : allObjs) { + public List getUpdated(String key) { + List updatedObjects = new LinkedList(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { updatedObjects.add(objectCache.getObject()); } @@ -507,10 +496,10 @@ public class ObjectFilter { * registration key of the objects to match * @return The list of all objects registered under the given key and that need to be updated. */ - public List getUpdated(Class clazz, String key) { + public List getUpdated(Class clazz, String key) { List updatedObjects = new LinkedList(); - Collection> allObjs = this.cache.values(); - for (ObjectCache objectCache : allObjs) { + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { if (objectCache.getObject().getClass() == clazz) { @SuppressWarnings("unchecked") @@ -529,10 +518,10 @@ public class ObjectFilter { * The registration key of the objects to match * @return The list of object registered under the given key that have, as a final action, removal. */ - public List getRemoved(String key) { - List removedObjects = new LinkedList(); - Collection> allObjs = this.cache.values(); - for (ObjectCache objectCache : allObjs) { + public List getRemoved(String key) { + List removedObjects = new LinkedList(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { removedObjects.add(objectCache.getObject()); } @@ -547,10 +536,10 @@ public class ObjectFilter { * The registration key of the objects to match * @return The list of object registered under the given key that have, as a final action, removal. */ - public List getRemoved(Class clazz, String key) { + public List getRemoved(Class clazz, String key) { List removedObjects = new LinkedList(); - Collection> allObjs = this.cache.values(); - for (ObjectCache objectCache : allObjs) { + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { if (objectCache.getObject().getClass() == clazz) { @SuppressWarnings("unchecked") @@ -570,10 +559,10 @@ public class ObjectFilter { * The registration key for which the objects shall be retrieved * @return The list of objects matching the given key. */ - public List getAll(String key) { - List allObjects = new LinkedList(); - Collection> allObjs = this.cache.values(); - for (ObjectCache objectCache : allObjs) { + public List getAll(String key) { + List allObjects = new LinkedList(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { if (objectCache.getKey().equals(key)) { allObjects.add(objectCache.getObject()); } @@ -589,10 +578,10 @@ public class ObjectFilter { * The registration key for which the objects shall be retrieved * @return The list of objects matching the given key. */ - public List> getCache(String key) { - List> allCache = new LinkedList>(); - Collection> allObjs = this.cache.values(); - for (ObjectCache objectCache : allObjs) { + public List getCache(String key) { + List allCache = new LinkedList(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { if (objectCache.getKey().equals(key)) { allCache.add(objectCache); } From 2bc24c68c8942d517fe5b4aed62b30cdd0fb403c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Aug 2013 00:00:47 +0200 Subject: [PATCH 148/457] [New] set new version to 0.2.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ddd624b1d..ed75a6af3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.utils jar - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT ch.eitchnet.utils These utils contain project independent helper classes and utilities for reuse https://github.com/eitch/ch.eitchnet.utils From d7f10a731f9b2a95cecb32baccf1a7b6e4b088dd Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Aug 2013 00:32:39 +0200 Subject: [PATCH 149/457] [Minor] fixed compiler warnings fixed multiple compiler warnings about broken JavaDocs and pom which was still set to Java 1.6, now changed to 1.7 --- pom.xml | 6 +++--- .../privilege/handler/DefaultPrivilegeHandler.java | 14 +++++++++++--- .../privilege/handler/PrivilegeHandler.java | 10 +++++----- .../ch/eitchnet/privilege/model/Certificate.java | 6 ++---- .../model/internal/PrivilegeContainerModel.java | 6 +++--- .../privilege/policy/DefaultPrivilege.java | 2 +- .../privilege/xml/PrivilegeModelSaxReader.java | 4 ---- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 2d601786a..fdef06dff 100644 --- a/pom.xml +++ b/pom.xml @@ -97,7 +97,7 @@ ch.eitchnet ch.eitchnet.utils - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT org.slf4j @@ -129,8 +129,8 @@ maven-compiler-plugin 3.0 - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 43691ca2a..8f1a7aeb2 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -78,7 +78,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); /** - * last assigned id for the {@link Session}s + * last assigned id for the {@link Certificate}s */ private long lastSessionId; @@ -285,6 +285,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceRole(role); } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.UserRep, byte[]) + */ @Override public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) { try { @@ -525,6 +529,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, byte[]) + */ @Override public void setUserPassword(Certificate certificate, String username, byte[] password) { try { @@ -595,7 +603,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, byte[]) * * @throws AccessDeniedException * if the user credentials are not valid @@ -800,7 +808,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * This simple implementation validates that the password is not null, and that the password string is not empty * - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(byte[]) */ @Override public void validatePassword(byte[] password) throws PrivilegeException { diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index ec0d5376a..5c0e397bd 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -159,7 +159,7 @@ public interface PrivilegeHandler { * *

    * If the password given is null, then the user is created, but can not not login! Otherwise the password must meet - * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(String)} + * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(byte[])} *

    * * @param certificate @@ -169,7 +169,7 @@ public interface PrivilegeHandler { * @param password * the password of the new user. If the password is null, then this is accepted but the user can not * login, otherwise the password must be validated against - * {@link PrivilegeHandler#validatePassword(String)} + * {@link PrivilegeHandler#validatePassword(byte[])} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -235,7 +235,7 @@ public interface PrivilegeHandler { *

    * Changes the password for the {@link User} with the given username. If the password is null, then the {@link User} * can not login anymore. Otherwise the password must meet the requirements of the implementation under - * {@link PrivilegeHandler#validatePassword(String)} + * {@link PrivilegeHandler#validatePassword(byte[])} *

    * *

    @@ -249,7 +249,7 @@ public interface PrivilegeHandler { * @param password * the new password for this user. If the password is null, then the {@link User} can not login anymore. * Otherwise the password must meet the requirements of the implementation under - * {@link PrivilegeHandler#validatePassword(String)} + * {@link PrivilegeHandler#validatePassword(byte[])} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -324,7 +324,7 @@ public interface PrivilegeHandler { * the username of the {@link User} which is registered in the {@link PersistenceHandler} * @param password * the password with which this user is to be authenticated. Null passwords are not accepted and they - * must meet the requirements of the {@link #validatePassword(String)}-method + * must meet the requirements of the {@link #validatePassword(byte[])}-method * * @return a {@link Certificate} with which this user may then perform actions * diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 5b6bebfe0..afff0b2eb 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -30,7 +30,7 @@ import ch.eitchnet.utils.helper.StringHelper; /** * The {@link Certificate} is the object a client keeps when accessing a Privilege enabled system. This object is the * instance which is always used when performing an access and is returned when a user performs a login through - * {@link PrivilegeHandler#authenticate(String, String)} + * {@link PrivilegeHandler#authenticate(String, byte[])} * * @author Robert von Burg */ @@ -62,11 +62,9 @@ public final class Certificate implements Serializable { * the users login name * @param authToken * the authentication token defining the users unique session and is a private field of this certificate. - * It corresponds with the authentication token on the {@link Session} * @param authPassword * the password to access the authentication token, this is not known to the client but set by the - * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the - * {@link Session} + * {@link PrivilegeHandler} on authentication. * @param locale * the users {@link Locale} * @param propertyMap diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index a30db711c..b4fb6e929 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -130,8 +130,8 @@ public class PrivilegeContainerModel { } /** - * @param name - * @param policyClass + * @param privilegeName + * @param policyClassName */ public void addPolicy(String privilegeName, String policyClassName) { @@ -186,4 +186,4 @@ public class PrivilegeContainerModel { builder.append("]"); return builder.toString(); } -} \ No newline at end of file +} diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 3a360be94..818d1e4c6 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -41,7 +41,7 @@ public class DefaultPrivilege implements PrivilegePolicy { /** * The value of {@link Restrictable#getPrivilegeValue()} is used to check if the {@link Role} has this privilege * - * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, Restrictable) + * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(IPrivilege, Restrictable) */ @Override public void validateAction(IPrivilege privilege, Restrictable restrictable) { diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index edc0490a2..ea2bb5058 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -45,7 +45,6 @@ import ch.eitchnet.utils.helper.StringHelper; /** * @author Robert von Burg - * */ public class PrivilegeModelSaxReader extends DefaultHandler { @@ -58,9 +57,6 @@ public class PrivilegeModelSaxReader extends DefaultHandler { private boolean insideUser; - /** - * - */ public PrivilegeModelSaxReader() { this.users = new ArrayList(); this.roles = new ArrayList(); From 3b2a3970632a82148bd789686d8797ede2b547ea Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Aug 2013 00:34:49 +0200 Subject: [PATCH 150/457] [Devel] still implementing new visitor pattern --- pom.xml | 2 +- .../xmlpers/FormattingXmlStreamWriter.java | 432 ------------------ src/main/java/ch/eitchnet/xmlpers/Main.java | 13 +- .../xmlpers/XmlPersistenceHandler.java | 26 +- .../xmlpers/XmlPersistencePathBuilder.java | 7 - .../xmlpers/XmlPersistenceTransaction.java | 5 +- .../java/javanet/staxutils/Indentation.java | 38 ++ .../staxutils/IndentingXMLStreamWriter.java | 352 ++++++++++++++ .../helpers/StreamWriterDelegate.java | 185 ++++++++ 9 files changed, 587 insertions(+), 473 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/xmlpers/FormattingXmlStreamWriter.java create mode 100644 src/main/java/javanet/staxutils/Indentation.java create mode 100644 src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java create mode 100644 src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java diff --git a/pom.xml b/pom.xml index 5394db648..6f22a98db 100644 --- a/pom.xml +++ b/pom.xml @@ -91,7 +91,7 @@ ch.eitchnet ch.eitchnet.utils - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT org.slf4j diff --git a/src/main/java/ch/eitchnet/xmlpers/FormattingXmlStreamWriter.java b/src/main/java/ch/eitchnet/xmlpers/FormattingXmlStreamWriter.java deleted file mode 100644 index b3ae34e91..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/FormattingXmlStreamWriter.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers; - -import javax.xml.namespace.NamespaceContext; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; - -/** - * @author Robert von Burg - */ -public class FormattingXmlStreamWriter implements XMLStreamWriter { - - private final XMLStreamWriter writer; - - /** - * - * @param writer - */ - public FormattingXmlStreamWriter(XMLStreamWriter writer) { - this.writer = writer; - } - - // - // Start of elements - // - - /** - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeStartDocument() - */ - @Override - public void writeStartDocument() throws XMLStreamException { - preStart(); - this.writer.writeStartDocument(); - postStart(); - } - - private void preStart() { - - } - - private void postStart() throws XMLStreamException { - //this.writer.writeCharacters(new char[] { '\n' }, 0, 1); - } - - private void preEnd() { - } - - private void postEnd() { - } - - /** - * @param version - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeStartDocument(java.lang.String) - */ - @Override - public void writeStartDocument(String version) throws XMLStreamException { - preStart(); - this.writer.writeStartDocument(version); - postStart(); - } - - /** - * @param encoding - * @param version - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeStartDocument(java.lang.String, java.lang.String) - */ - @Override - public void writeStartDocument(String encoding, String version) throws XMLStreamException { - preStart(); - this.writer.writeStartDocument(encoding, version); - postStart(); - } - - /** - * @param localName - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeStartElement(java.lang.String) - */ - @Override - public void writeStartElement(String localName) throws XMLStreamException { - preStart(); - this.writer.writeStartElement(localName); - postStart(); - } - - /** - * @param namespaceURI - * @param localName - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeStartElement(java.lang.String, java.lang.String) - */ - @Override - public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { - preStart(); - this.writer.writeStartElement(namespaceURI, localName); - postStart(); - } - - /** - * @param prefix - * @param localName - * @param namespaceURI - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeStartElement(java.lang.String, java.lang.String, java.lang.String) - */ - @Override - public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { - preStart(); - this.writer.writeStartElement(prefix, localName, namespaceURI); - postStart(); - } - - // - // End of elements - // - - /** - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeEndElement() - */ - @Override - public void writeEndElement() throws XMLStreamException { - preStart(); - this.writer.writeEndElement(); - postStart(); - } - - /** - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeEndDocument() - */ - @Override - public void writeEndDocument() throws XMLStreamException { - preStart(); - this.writer.writeEndDocument(); - postStart(); - } - - // - // Empty elements - // - - /** - * @param namespaceURI - * @param localName - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeEmptyElement(java.lang.String, java.lang.String) - */ - @Override - public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { - preEnd(); - this.writer.writeEmptyElement(namespaceURI, localName); - postEnd(); - } - - /** - * @param prefix - * @param localName - * @param namespaceURI - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeEmptyElement(java.lang.String, java.lang.String, java.lang.String) - */ - @Override - public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { - preEnd(); - this.writer.writeEmptyElement(prefix, localName, namespaceURI); - postEnd(); - } - - /** - * @param localName - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeEmptyElement(java.lang.String) - */ - @Override - public void writeEmptyElement(String localName) throws XMLStreamException { - this.writer.writeEmptyElement(localName); - } - - // - // attributes - // - - /** - * @param localName - * @param value - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeAttribute(java.lang.String, java.lang.String) - */ - @Override - public void writeAttribute(String localName, String value) throws XMLStreamException { - this.writer.writeAttribute(localName, value); - } - - /** - * @param prefix - * @param namespaceURI - * @param localName - * @param value - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeAttribute(java.lang.String, java.lang.String, java.lang.String, - * java.lang.String) - */ - @Override - public void writeAttribute(String prefix, String namespaceURI, String localName, String value) - throws XMLStreamException { - this.writer.writeAttribute(prefix, namespaceURI, localName, value); - } - - /** - * @param namespaceURI - * @param localName - * @param value - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeAttribute(java.lang.String, java.lang.String, java.lang.String) - */ - @Override - public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { - this.writer.writeAttribute(namespaceURI, localName, value); - } - - // - // other - // - - /** - * @param data - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeComment(java.lang.String) - */ - @Override - public void writeComment(String data) throws XMLStreamException { - this.writer.writeComment(data); - } - - /** - * @param target - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeProcessingInstruction(java.lang.String) - */ - @Override - public void writeProcessingInstruction(String target) throws XMLStreamException { - this.writer.writeProcessingInstruction(target); - } - - /** - * @param target - * @param data - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeProcessingInstruction(java.lang.String, java.lang.String) - */ - @Override - public void writeProcessingInstruction(String target, String data) throws XMLStreamException { - this.writer.writeProcessingInstruction(target, data); - } - - /** - * @param data - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeCData(java.lang.String) - */ - @Override - public void writeCData(String data) throws XMLStreamException { - this.writer.writeCData(data); - } - - /** - * @param dtd - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeDTD(java.lang.String) - */ - @Override - public void writeDTD(String dtd) throws XMLStreamException { - this.writer.writeDTD(dtd); - } - - /** - * @param prefix - * @param namespaceURI - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeNamespace(java.lang.String, java.lang.String) - */ - @Override - public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { - this.writer.writeNamespace(prefix, namespaceURI); - } - - /** - * @param namespaceURI - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeDefaultNamespace(java.lang.String) - */ - @Override - public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { - this.writer.writeDefaultNamespace(namespaceURI); - } - - /** - * @param name - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeEntityRef(java.lang.String) - */ - @Override - public void writeEntityRef(String name) throws XMLStreamException { - this.writer.writeEntityRef(name); - } - - /** - * @param text - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeCharacters(java.lang.String) - */ - @Override - public void writeCharacters(String text) throws XMLStreamException { - this.writer.writeCharacters(text); - } - - /** - * @param text - * @param start - * @param len - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeCharacters(char[], int, int) - */ - @Override - public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { - this.writer.writeCharacters(text, start, len); - } - - /** - * @param uri - * @return - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#getPrefix(java.lang.String) - */ - @Override - public String getPrefix(String uri) throws XMLStreamException { - return this.writer.getPrefix(uri); - } - - /** - * @param prefix - * @param uri - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#setPrefix(java.lang.String, java.lang.String) - */ - @Override - public void setPrefix(String prefix, String uri) throws XMLStreamException { - this.writer.setPrefix(prefix, uri); - } - - /** - * @param uri - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#setDefaultNamespace(java.lang.String) - */ - @Override - public void setDefaultNamespace(String uri) throws XMLStreamException { - this.writer.setDefaultNamespace(uri); - } - - /** - * @param context - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#setNamespaceContext(javax.xml.namespace.NamespaceContext) - */ - @Override - public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { - this.writer.setNamespaceContext(context); - } - - /** - * @return - * @see javax.xml.stream.XMLStreamWriter#getNamespaceContext() - */ - @Override - public NamespaceContext getNamespaceContext() { - return this.writer.getNamespaceContext(); - } - - /** - * @param name - * @return - * @throws IllegalArgumentException - * @see javax.xml.stream.XMLStreamWriter#getProperty(java.lang.String) - */ - @Override - public Object getProperty(String name) throws IllegalArgumentException { - return this.writer.getProperty(name); - } - - /** - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#close() - */ - @Override - public void close() throws XMLStreamException { - this.writer.close(); - } - - /** - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#flush() - */ - @Override - public void flush() throws XMLStreamException { - this.writer.flush(); - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/Main.java b/src/main/java/ch/eitchnet/xmlpers/Main.java index b9717ab6f..64ebde257 100644 --- a/src/main/java/ch/eitchnet/xmlpers/Main.java +++ b/src/main/java/ch/eitchnet/xmlpers/Main.java @@ -24,6 +24,8 @@ package ch.eitchnet.xmlpers; import java.io.File; import java.io.FileWriter; +import javanet.staxutils.IndentingXMLStreamWriter; + import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; @@ -41,15 +43,16 @@ public class Main { XMLStreamWriter writer = factory.createXMLStreamWriter(new FileWriter(file)); System.out.println("writer: " + writer.getClass().getName()); - //writer = new com.sun.xml.internal.txw2.output.IndentingXMLStreamWriter(writer); - writer = new FormattingXmlStreamWriter(writer); + writer = new IndentingXMLStreamWriter(writer); writer.writeStartDocument(); writer.writeStartElement("document"); - writer.writeStartElement("data"); + writer.writeEmptyElement("data"); writer.writeAttribute("name", "value"); - writer.writeEndElement(); - writer.writeEndElement(); + //writer.writeEndElement(); + writer.writeEmptyElement("stuff"); + writer.writeAttribute("attr", "attrVal"); + //writer.writeEndElement(); writer.writeEndDocument(); writer.flush(); diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java index c615795ac..a393e72b9 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java @@ -23,7 +23,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.SystemHelper; -import ch.eitchnet.utils.objectfilter.ITransactionObject; import ch.eitchnet.utils.objectfilter.ObjectFilter; /** @@ -32,19 +31,8 @@ import ch.eitchnet.utils.objectfilter.ObjectFilter; */ public class XmlPersistenceHandler { - /** - * - */ public static final String CONFIG_VERBOSE = "ch.eitchnet.xmlpers.config.verbose"; - - /** - * - */ public static final String CONFIG_BASEPATH = "ch.eitchnet.xmlpers.config.basepath"; - - /** - * - */ public static final String CONFIG_DAO_FACTORY_CLASS = "ch.eitchnet.xmlpers.config.daoFactoryClass"; protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); @@ -54,9 +42,6 @@ public class XmlPersistenceHandler { protected XmlFilePersister persister; protected XmlDaoFactory xmlDaoFactory; - /** - * - */ public void initialize() { String basePath = SystemHelper.getProperty(XmlPersistenceHandler.class.getSimpleName(), XmlPersistenceHandler.CONFIG_BASEPATH, null); @@ -85,9 +70,6 @@ public class XmlPersistenceHandler { this.xmlPersistenceTxThreadLocal = new ThreadLocal(); } - /** - * - */ public XmlPersistenceTransaction openTx() { if (this.verbose) @@ -99,7 +81,7 @@ public class XmlPersistenceHandler { throw new XmlPersistenceExecption("Previous transaction not properly closed"); // set a new persistence transaction object - ObjectFilter objectFilter = new ObjectFilter(); + ObjectFilter objectFilter = new ObjectFilter(); xmlPersistenceTx = new XmlPersistenceTransaction(); xmlPersistenceTx.initialize(this.persister, this.xmlDaoFactory, objectFilter, this.verbose); @@ -108,9 +90,6 @@ public class XmlPersistenceHandler { return xmlPersistenceTx; } - /** - * - */ public XmlPersistenceTransaction getTx() { XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); if (xmlPersistenceTx == null) @@ -119,9 +98,6 @@ public class XmlPersistenceHandler { return xmlPersistenceTx; } - /** - * - */ public void commitTx() { if (this.verbose) diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java index b92107cfc..baa894740 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java @@ -32,14 +32,7 @@ import org.slf4j.LoggerFactory; public class XmlPersistencePathBuilder { private static final Logger logger = LoggerFactory.getLogger(XmlPersistencePathBuilder.class); - /** - * - */ public static final String FILE_EXT = ".xml"; - - /** - * - */ public static final int EXT_LENGTH = XmlPersistencePathBuilder.FILE_EXT.length(); private String basePath; diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java index f3641a817..9d2f3f2b0 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java @@ -39,7 +39,6 @@ import ch.eitchnet.utils.objectfilter.ObjectFilter; /** * @author Robert von Burg - * */ public class XmlPersistenceTransaction { @@ -48,7 +47,7 @@ public class XmlPersistenceTransaction { private boolean verbose; private XmlFilePersister persister; private XmlDaoFactory xmlDaoFactory; - private ObjectFilter objectFilter; + private ObjectFilter objectFilter; private DocumentBuilder docBuilder; private DOMImplementation domImplementation; @@ -58,7 +57,7 @@ public class XmlPersistenceTransaction { * @param objectFilter */ public void initialize(XmlFilePersister persister, XmlDaoFactory xmlDaoFactory, - ObjectFilter objectFilter, boolean verbose) { + ObjectFilter objectFilter, boolean verbose) { this.persister = persister; this.xmlDaoFactory = xmlDaoFactory; this.objectFilter = objectFilter; diff --git a/src/main/java/javanet/staxutils/Indentation.java b/src/main/java/javanet/staxutils/Indentation.java new file mode 100644 index 000000000..4dfa7b48e --- /dev/null +++ b/src/main/java/javanet/staxutils/Indentation.java @@ -0,0 +1,38 @@ +package javanet.staxutils; + +/** + * Characters that represent line breaks and indentation. These are represented + * as String-valued JavaBean properties. + */ +public interface Indentation { + + /** Two spaces; the default indentation. */ + public static final String DEFAULT_INDENT = " "; + + /** + * Set the characters used for one level of indentation. The default is + * {@link #DEFAULT_INDENT}. "\t" is a popular alternative. + */ + void setIndent(String indent); + + /** The characters used for one level of indentation. */ + String getIndent(); + + /** + * "\n"; the normalized representation of end-of-line in XML. + */ + public static final String NORMAL_END_OF_LINE = "\n"; + + /** + * Set the characters that introduce a new line. The default is + * {@link #NORMAL_END_OF_LINE}. + * {@link IndentingXMLStreamWriter#getLineSeparator}() is a popular + * alternative. + */ + public void setNewLine(String newLine); + + /** The characters that introduce a new line. */ + String getNewLine(); + +} diff --git a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java new file mode 100644 index 000000000..9e09a7be0 --- /dev/null +++ b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2006, John Kristian + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of StAX-Utils nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package javanet.staxutils; + +import javanet.staxutils.helpers.StreamWriterDelegate; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * A filter that indents an XML stream. To apply it, construct a filter that + * contains another {@link XMLStreamWriter}, which you pass to the constructor. + * Then call methods of the filter instead of the contained stream. For example: + * + *

    + * {@link XMLStreamWriter} stream = ...
    + * stream = new {@link IndentingXMLStreamWriter}(stream);
    + * stream.writeStartDocument();
    + * ...
    + * 
    + * + *

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

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

    + * Indentation can be adjusted by setting the newLine and indent properties. But + * set them to whitespace only, for best results. Non-whitespace is apt to cause + * problems, for example when this class attempts to insert newLine before the + * root element. + * + * @author John Kristian + */ +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; + + public void setIndent(String indent) { + if (!indent.equals(this.indent)) { + this.indent = indent; + linePrefix = null; + } + } + + public String getIndent() { + return indent; + } + + public void setNewLine(String newLine) { + if (!newLine.equals(this.newLine)) { + this.newLine = newLine; + 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; + } + + public String getNewLine() { + return newLine; + } + + public void writeStartDocument() throws XMLStreamException { + beforeMarkup(); + out.writeStartDocument(); + afterMarkup(); + } + + public void writeStartDocument(String version) throws XMLStreamException { + beforeMarkup(); + out.writeStartDocument(version); + afterMarkup(); + } + + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + beforeMarkup(); + out.writeStartDocument(encoding, version); + afterMarkup(); + } + + public void writeDTD(String dtd) throws XMLStreamException { + beforeMarkup(); + out.writeDTD(dtd); + afterMarkup(); + } + + public void writeProcessingInstruction(String target) throws XMLStreamException { + beforeMarkup(); + out.writeProcessingInstruction(target); + afterMarkup(); + } + + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + beforeMarkup(); + out.writeProcessingInstruction(target, data); + afterMarkup(); + } + + public void writeComment(String data) throws XMLStreamException { + beforeMarkup(); + out.writeComment(data); + afterMarkup(); + } + + public void writeEmptyElement(String localName) throws XMLStreamException { + beforeMarkup(); + out.writeEmptyElement(localName); + afterMarkup(); + } + + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + beforeMarkup(); + out.writeEmptyElement(namespaceURI, localName); + afterMarkup(); + } + + public void writeEmptyElement(String prefix, String localName, String namespaceURI) + throws XMLStreamException { + beforeMarkup(); + out.writeEmptyElement(prefix, localName, namespaceURI); + afterMarkup(); + } + + public void writeStartElement(String localName) throws XMLStreamException { + beforeStartElement(); + out.writeStartElement(localName); + afterStartElement(); + } + + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + beforeStartElement(); + out.writeStartElement(namespaceURI, localName); + afterStartElement(); + } + + public void writeStartElement(String prefix, String localName, String namespaceURI) + throws XMLStreamException { + beforeStartElement(); + out.writeStartElement(prefix, localName, namespaceURI); + afterStartElement(); + } + + public void writeCharacters(String text) throws XMLStreamException { + out.writeCharacters(text); + afterData(); + } + + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + out.writeCharacters(text, start, len); + afterData(); + } + + public void writeCData(String data) throws XMLStreamException { + out.writeCData(data); + afterData(); + } + + public void writeEntityRef(String name) throws XMLStreamException { + out.writeEntityRef(name); + afterData(); + } + + public void writeEndElement() throws XMLStreamException { + beforeEndElement(); + out.writeEndElement(); + afterEndElement(); + } + + public void writeEndDocument() throws XMLStreamException { + try { + while (depth > 0) { + writeEndElement(); // indented + } + } catch (Exception ignored) { + ignored.printStackTrace(); + } + out.writeEndDocument(); + afterEndDocument(); + } + + /** Prepare to write markup, by writing a new line and indentation. */ + protected void beforeMarkup() { + int soFar = stack[depth]; + if ((soFar & WROTE_DATA) == 0 // no data in this scope + && (depth > 0 || soFar != 0)) // not the first line + { + try { + writeNewLine(depth); + if (depth > 0 && getIndent().length() > 0) { + afterMarkup(); // indentation was written + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** Note that markup or indentation was written. */ + protected void afterMarkup() { + stack[depth] |= WROTE_MARKUP; + } + + /** Note that data were written. */ + protected void afterData() { + stack[depth] |= WROTE_DATA; + } + + /** Prepare to start an element, by allocating stack space. */ + protected void beforeStartElement() { + beforeMarkup(); + if (stack.length <= depth + 1) { + // Allocate more space for the stack: + int[] newStack = new int[stack.length * 2]; + System.arraycopy(stack, 0, newStack, 0, stack.length); + stack = newStack; + } + stack[depth + 1] = 0; // nothing written yet + } + + /** Note that an element was started. */ + protected void afterStartElement() { + afterMarkup(); + ++depth; + } + + /** Prepare to end an element, by writing a new line and indentation. */ + protected void beforeEndElement() { + if (depth > 0 && stack[depth] == WROTE_MARKUP) { // but not data + try { + writeNewLine(depth - 1); + } catch (Exception ignored) { + ignored.printStackTrace(); + } + } + } + + /** Note that an element was ended. */ + protected void afterEndElement() { + if (depth > 0) { + --depth; + } + } + + /** Note that a document was ended. */ + protected void afterEndDocument() { + if (stack[depth = 0] == WROTE_MARKUP) { // but not data + try { + writeNewLine(0); + } catch (Exception ignored) { + ignored.printStackTrace(); + } + } + stack[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 (linePrefix == null) { + linePrefix = (getNewLine() + getIndent()).toCharArray(); + } + while (prefixLength > linePrefix.length) { + // make linePrefix longer: + char[] newPrefix = new char[newLineLength + + ((linePrefix.length - newLineLength) * 2)]; + System.arraycopy(linePrefix, 0, newPrefix, 0, linePrefix.length); + System.arraycopy(linePrefix, newLineLength, newPrefix, linePrefix.length, + linePrefix.length - newLineLength); + linePrefix = newPrefix; + } + out.writeCharacters(linePrefix, 0, prefixLength); + } + } + +} diff --git a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java new file mode 100644 index 000000000..edba1f7f5 --- /dev/null +++ b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2006, John Kristian + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of StAX-Utils nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package javanet.staxutils.helpers; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * Abstract class for writing filtered XML streams. This class provides methods + * that merely delegate to the contained stream. Subclasses should override some + * of these methods, and may also provide additional methods and fields. + * + * @author John Kristian + */ +public abstract class StreamWriterDelegate implements XMLStreamWriter { + + protected StreamWriterDelegate(XMLStreamWriter out) { + this.out = out; + } + + protected XMLStreamWriter out; + + public Object getProperty(String name) throws IllegalArgumentException { + return out.getProperty(name); + } + + public NamespaceContext getNamespaceContext() { + return out.getNamespaceContext(); + } + + public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { + out.setNamespaceContext(context); + } + + public void setDefaultNamespace(String uri) throws XMLStreamException { + out.setDefaultNamespace(uri); + } + + public void writeStartDocument() throws XMLStreamException { + out.writeStartDocument(); + } + + public void writeStartDocument(String version) throws XMLStreamException { + out.writeStartDocument(version); + } + + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + out.writeStartDocument(encoding, version); + } + + public void writeDTD(String dtd) throws XMLStreamException { + out.writeDTD(dtd); + } + + public void writeProcessingInstruction(String target) throws XMLStreamException { + out.writeProcessingInstruction(target); + } + + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + out.writeProcessingInstruction(target, data); + } + + public void writeComment(String data) throws XMLStreamException { + out.writeComment(data); + } + + public void writeEmptyElement(String localName) throws XMLStreamException { + out.writeEmptyElement(localName); + } + + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + out.writeEmptyElement(namespaceURI, localName); + } + + public void writeEmptyElement(String prefix, String localName, String namespaceURI) + throws XMLStreamException { + out.writeEmptyElement(prefix, localName, namespaceURI); + } + + public void writeStartElement(String localName) throws XMLStreamException { + out.writeStartElement(localName); + } + + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + out.writeStartElement(namespaceURI, localName); + } + + public void writeStartElement(String prefix, String localName, String namespaceURI) + throws XMLStreamException { + out.writeStartElement(prefix, localName, namespaceURI); + } + + public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { + out.writeDefaultNamespace(namespaceURI); + } + + public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { + out.writeNamespace(prefix, namespaceURI); + } + + public String getPrefix(String uri) throws XMLStreamException { + return out.getPrefix(uri); + } + + public void setPrefix(String prefix, String uri) throws XMLStreamException { + out.setPrefix(prefix, uri); + } + + public void writeAttribute(String localName, String value) throws XMLStreamException { + out.writeAttribute(localName, value); + } + + public void writeAttribute(String namespaceURI, String localName, String value) + throws XMLStreamException { + out.writeAttribute(namespaceURI, localName, value); + } + + public void writeAttribute(String prefix, String namespaceURI, String localName, String value) + throws XMLStreamException { + out.writeAttribute(prefix, namespaceURI, localName, value); + } + + public void writeCharacters(String text) throws XMLStreamException { + out.writeCharacters(text); + } + + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + out.writeCharacters(text, start, len); + } + + public void writeCData(String data) throws XMLStreamException { + out.writeCData(data); + } + + public void writeEntityRef(String name) throws XMLStreamException { + out.writeEntityRef(name); + } + + public void writeEndElement() throws XMLStreamException { + out.writeEndElement(); + } + + public void writeEndDocument() throws XMLStreamException { + out.writeEndDocument(); + } + + public void flush() throws XMLStreamException { + out.flush(); + } + + public void close() throws XMLStreamException { + out.close(); + } + +} From 4263f4693488057d0837b3ccad4b34c720e38159 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 10 Aug 2013 00:37:21 +0200 Subject: [PATCH 151/457] [Major] Refactoring the ObjectFilter to no longer rely on an interface Now the objects put into the cache no longer need to implement an interface, this makes it easier to adopt the filter in different scenarios. Tests written to validate that the filter works as expected. Still some outstanding tests which currently fail --- .../objectfilter/ITransactionObject.java | 56 ---- .../utils/objectfilter/ObjectCache.java | 48 +-- .../utils/objectfilter/ObjectFilter.java | 278 ++++++++---------- .../utils/objectfilter/ObjectFilterTest.java | 247 ++++++++++++++++ 4 files changed, 403 insertions(+), 226 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java create mode 100644 src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java b/src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java deleted file mode 100644 index c9bfc3efe..000000000 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ITransactionObject.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . - * - */ -package ch.eitchnet.utils.objectfilter; - -/** - * This interface serves for objects which are required, at some point, to have a unique ID within a transaction. - * - * @author Michael Gatto - */ -public interface ITransactionObject { - - /** - * UNSET Marker to determine if ids have not been set. - *

    - * Beware: this is set to 0 due to transient field in the {@link ITransactionObject} implementations that store the - * ID, which are set to zero when de-serialized, and that are not allowed to be serialized. - *

    - */ - public static final long UNSET = 0; - - /** - * Set the ID of this object. This ID is unique for this object within the transaction. - * - * @param id - * The ID to set. - */ - public void setTransactionID(long id); - - /** - * @return The ID of this object, as set within the transaction. This ID shall guarantee that it is unique within - * this transaction. - */ - public long getTransactionID(); - - /** - * Reset / anul the transaction ID of this object - */ - public void resetTransactionID(); -} diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java index dfa3ea8ac..382baffc8 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -36,42 +36,52 @@ import org.slf4j.LoggerFactory; *

    * * @author Michael Gatto + * @author Robert von Burg */ public class ObjectCache { private final static Logger logger = LoggerFactory.getLogger(ObjectCache.class); + /** + * UNSET Marker to determine if ids have not been set. + */ + public static final long UNSET = 0; + /** * id The unique ID of this object in this session */ private final long id; + /** * key The key defining who's registered for this object's state */ private final String key; - /** - * object The object that shall be cached - */ - private ITransactionObject object; + /** * 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 */ - public ObjectCache(String key, ITransactionObject object, Operation operation) { + public ObjectCache(long id, String key, Object object, Operation operation) { - this.id = object.getTransactionID(); + this.id = id; this.key = key; this.object = object; this.operation = operation; - if (ObjectCache.logger.isDebugEnabled()) { - ObjectCache.logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation + if (logger.isDebugEnabled()) { + logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation + " / " + object.toString()); } } @@ -81,9 +91,9 @@ public class ObjectCache { * * @param object */ - public void setObject(ITransactionObject object) { - if (ObjectCache.logger.isDebugEnabled()) { - ObjectCache.logger.debug("Updating ID " + this.id + " to value " + object.toString()); + public void setObject(Object object) { + if (logger.isDebugEnabled()) { + logger.debug("Updating ID " + this.id + " to value " + object.toString()); } this.object = object; } @@ -114,18 +124,18 @@ public class ObjectCache { public String getKey() { return this.key; } - - /** - * @return the object - */ - public ITransactionObject getObject() { - return this.object; - } - + /** * @return the operation */ public Operation getOperation() { return this.operation; } + + /** + * @return the object + */ + public Object getObject() { + return this.object; + } } diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index dc1f7194f..7ee255595 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -76,16 +76,16 @@ import org.slf4j.LoggerFactory; * * * @author Michael Gatto (initial version) - * @author Robert von Burg + * @author Robert von Burg (minor modifications, refactorings) */ public class ObjectFilter { private final static Logger logger = LoggerFactory.getLogger(ObjectFilter.class); - private HashMap cache = new HashMap(); + private HashMap cache = new HashMap(); private HashSet keySet = new HashSet(); - private static long id = ITransactionObject.UNSET; + private static long id = ObjectCache.UNSET; /** * Register, under the given key, the addition of the given object. @@ -113,7 +113,7 @@ public class ObjectFilter { * @param objectToAdd * The object for which addition shall be registered. */ - public void add(String key, ITransactionObject objectToAdd) { + public void add(String key, Object objectToAdd) { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("add object " + objectToAdd + " with key " + key); @@ -122,43 +122,37 @@ public class ObjectFilter { this.keySet.add(key); // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. - long id = objectToAdd.getTransactionID(); - if (id == ITransactionObject.UNSET) { - // The ID of the object has not been set, so it has not been in the cache during this - // run. Hence, we create an ID and add it to the cache. - id = dispenseID(); - objectToAdd.setTransactionID(id); - ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); - this.cache.put(id, cacheObj); + 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 { - ObjectCache cached = this.cache.get(Long.valueOf(objectToAdd.getTransactionID())); - if (cached == null) { - // The object got an ID during this run, but was not added to the cache. - // Hence, we add it now, with the current operation. - ObjectCache cacheObj = new ObjectCache(key, objectToAdd, Operation.ADD); - this.cache.put(id, 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}"; - throw new RuntimeException(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 RuntimeException("Stale State exception. Invalid + after +"); - case MODIFY: - throw new RuntimeException("Stale State exception. Invalid + after +="); - case REMOVE: - cached.setObject(objectToAdd); - cached.setOperation(Operation.MODIFY); - break; - } // switch - }// else of object not in cache - }// else of ID not set + + 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}"; + 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 +"); + case MODIFY: + throw new IllegalStateException("Stale State exception: Invalid + after +="); + case REMOVE: + cached.setObject(objectToAdd); + cached.setOperation(Operation.MODIFY); + break; + } // switch + }// else of object not in cache } /** @@ -185,58 +179,45 @@ public class ObjectFilter { * @param objectToUpdate * The object for which update shall be registered. */ - public void update(String key, ITransactionObject objectToUpdate) { + public void update(String key, Object objectToUpdate) { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("update object " + objectToUpdate + " with key " + key); // add the key to the keyset this.keySet.add(key); + // 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); - long id = objectToUpdate.getTransactionID(); - if (id == ITransactionObject.UNSET) { - id = dispenseID(); - objectToUpdate.setTransactionID(id); - ObjectCache cacheObj = new ObjectCache(key, objectToUpdate, Operation.MODIFY); - this.cache.put(id, cacheObj); } else { - ObjectCache cached = this.cache.get(Long.valueOf(objectToUpdate.getTransactionID())); - 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(key, objectToUpdate, Operation.MODIFY); - this.cache.put(id, cacheObj); - } else { - String existingKey = cached.getKey(); - if (!existingKey.equals(key)) { - throw new RuntimeException( - "Invalid key provided for object with transaction ID " - + Long.toString(id) - + " and operation " - + Operation.MODIFY.toString() - + ": existing key is " - + existingKey - + ", new key is " - + key - + ". Object may be present in the same filter instance only once, registered using one key only. Object:" - + objectToUpdate.toString()); - } - // The object is in cache: update the version as required. - Operation op = cached.getOperation(); - switch (op) { - case ADD: - cached.setObject(objectToUpdate); - break; - case MODIFY: - cached.setObject(objectToUpdate); - break; - case REMOVE: - throw new RuntimeException("Stale State exception: Invalid += after -"); - } // switch - }// else of object not in cache - }// else of ID not set + 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}"; + 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: + cached.setObject(objectToUpdate); + break; + case MODIFY: + cached.setObject(objectToUpdate); + break; + case REMOVE: + throw new IllegalStateException("Stale State exception: Invalid += after -"); + } // switch + }// else of object not in cache } /** @@ -263,58 +244,45 @@ public class ObjectFilter { * @param objectToRemove * The object for which removal shall be registered. */ - public void remove(String key, ITransactionObject objectToRemove) { + public void remove(String key, Object objectToRemove) { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("remove object " + objectToRemove + " with key " + key); // add the key to the keyset this.keySet.add(key); + // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. - long id = objectToRemove.getTransactionID(); - if (id == ITransactionObject.UNSET) { - id = dispenseID(); - objectToRemove.setTransactionID(id); - ObjectCache cacheObj = new ObjectCache(key, objectToRemove, Operation.REMOVE); - this.cache.put(id, cacheObj); + 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 { - ObjectCache cached = this.cache.get(Long.valueOf(id)); - 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(key, objectToRemove, Operation.REMOVE); - this.cache.put(id, cacheObj); - } else { - String existingKey = cached.getKey(); - if (!existingKey.equals(key)) { - throw new RuntimeException( - "Invalid key provided for object with transaction ID " - + Long.toString(id) - + " and operation " - + Operation.REMOVE.toString() - + ": existing key is " - + existingKey - + ", new key is " - + key - + ". Object may be present in the same filter instance only once, registered using one key only. Object:" - + 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(Long.valueOf(id)); - break; - case MODIFY: - cached.setObject(objectToRemove); - cached.setOperation(Operation.REMOVE); - break; - case REMOVE: - throw new RuntimeException("Stale State exception. Invalid - after -"); - } // switch + + 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}"; + 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: + cached.setObject(objectToRemove); + cached.setOperation(Operation.REMOVE); + break; + case REMOVE: + throw new IllegalStateException("Stale State exception: Invalid - after -"); + } // switch } } @@ -326,8 +294,8 @@ public class ObjectFilter { * @param addedObjects * The objects for which addition shall be registered. */ - public void addAll(String key, Collection addedObjects) { - for (ITransactionObject addObj : addedObjects) { + public void addAll(String key, Collection addedObjects) { + for (Object addObj : addedObjects) { add(key, addObj); } } @@ -340,8 +308,8 @@ public class ObjectFilter { * @param updatedObjects * The objects for which update shall be registered. */ - public void updateAll(String key, Collection updatedObjects) { - for (ITransactionObject update : updatedObjects) { + public void updateAll(String key, Collection updatedObjects) { + for (Object update : updatedObjects) { update(key, update); } } @@ -354,8 +322,8 @@ public class ObjectFilter { * @param removedObjects * The objects for which removal shall be registered. */ - public void removeAll(String key, Collection removedObjects) { - for (ITransactionObject removed : removedObjects) { + public void removeAll(String key, Collection removedObjects) { + for (Object removed : removedObjects) { remove(key, removed); } } @@ -366,7 +334,7 @@ public class ObjectFilter { * @param object * The object that shall be registered for addition */ - public void add(ITransactionObject object) { + public void add(Object object) { add(object.getClass().getName(), object); } @@ -376,7 +344,7 @@ public class ObjectFilter { * @param object * The object that shall be registered for updating */ - public void update(ITransactionObject object) { + public void update(Object object) { update(object.getClass().getName(), object); } @@ -386,7 +354,7 @@ public class ObjectFilter { * @param object * The object that shall be registered for removal */ - public void remove(ITransactionObject object) { + public void remove(Object object) { remove(object.getClass().getName(), object); } @@ -397,8 +365,8 @@ public class ObjectFilter { * @param objects * The objects that shall be registered for addition */ - public void addAll(List objects) { - for (ITransactionObject addedObj : objects) { + public void addAll(List objects) { + for (Object addedObj : objects) { add(addedObj.getClass().getName(), addedObj); } } @@ -410,8 +378,8 @@ public class ObjectFilter { * @param updateObjects * The objects that shall be registered for updating */ - public void updateAll(List updateObjects) { - for (ITransactionObject update : updateObjects) { + public void updateAll(List updateObjects) { + for (Object update : updateObjects) { update(update.getClass().getName(), update); } } @@ -423,8 +391,8 @@ public class ObjectFilter { * @param removedObjects * The objects that shall be registered for removal */ - public void removeAll(List removedObjects) { - for (ITransactionObject removed : removedObjects) { + public void removeAll(List removedObjects) { + for (Object removed : removedObjects) { remove(removed.getClass().getName(), removed); } } @@ -436,8 +404,8 @@ public class ObjectFilter { * The registration key of the objects to match * @return The list of all objects registered under the given key and that need to be added. */ - public List getAdded(String key) { - List addedObjects = new LinkedList(); + public List getAdded(String key) { + List addedObjects = new LinkedList(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { @@ -456,7 +424,7 @@ public class ObjectFilter { * The registration key of the objects to match * @return The list of all objects registered under the given key and that need to be added. */ - public List getAdded(Class clazz, String key) { + public List getAdded(Class clazz, String key) { List addedObjects = new LinkedList(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { @@ -478,8 +446,8 @@ public class ObjectFilter { * registration key of the objects to match * @return The list of all objects registered under the given key and that need to be updated. */ - public List getUpdated(String key) { - List updatedObjects = new LinkedList(); + public List getUpdated(String key) { + List updatedObjects = new LinkedList(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { @@ -496,7 +464,7 @@ public class ObjectFilter { * registration key of the objects to match * @return The list of all objects registered under the given key and that need to be updated. */ - public List getUpdated(Class clazz, String key) { + public List getUpdated(Class clazz, String key) { List updatedObjects = new LinkedList(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { @@ -518,8 +486,8 @@ public class ObjectFilter { * The registration key of the objects to match * @return The list of object registered under the given key that have, as a final action, removal. */ - public List getRemoved(String key) { - List removedObjects = new LinkedList(); + public List getRemoved(String key) { + List removedObjects = new LinkedList(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { @@ -536,7 +504,7 @@ public class ObjectFilter { * The registration key of the objects to match * @return The list of object registered under the given key that have, as a final action, removal. */ - public List getRemoved(Class clazz, String key) { + public List getRemoved(Class clazz, String key) { List removedObjects = new LinkedList(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { @@ -559,8 +527,8 @@ public class ObjectFilter { * The registration key for which the objects shall be retrieved * @return The list of objects matching the given key. */ - public List getAll(String key) { - List allObjects = new LinkedList(); + public List getAll(String key) { + List allObjects = new LinkedList(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getKey().equals(key)) { @@ -606,6 +574,14 @@ public class ObjectFilter { this.keySet.clear(); } + public int sizeKeySet() { + return this.keySet.size(); + } + + public int sizeCache() { + return this.cache.size(); + } + /** * @return get a unique transaction ID */ diff --git a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java new file mode 100644 index 000000000..5ae6b64b4 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.utils.objectfilter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +/** + * @author Robert von Burg + * + */ +public class ObjectFilterTest { + + @Test + public void shouldAdd() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + + testAssertions(filter, 1, 1, 1, 0, 0); + } + + @Test + public void shouldUpdate() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.update(myObj); + + testAssertions(filter, 1, 1, 0, 1, 0); + } + + @Test + public void shouldRemove() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj); + + testAssertions(filter, 1, 1, 0, 0, 1); + } + + @Test + public void shouldAddUpdateRemoveDifferentObjects() { + + Object objToAdd = new Object(); + Object objToUpdate = new Object(); + Object objToRemove = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(objToAdd); + filter.update(objToUpdate); + filter.remove(objToRemove); + + testAssertions(filter, 3, 1, 1, 1, 1); + } + + @Test + public void shouldAddUpdateRemoveSameObject() { + + Object objToAddUpdateRemove = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(objToAddUpdateRemove); + filter.update(objToAddUpdateRemove); + filter.remove(objToAddUpdateRemove); + + testAssertions(filter, 0, 1, 0, 0, 0); + } + + @Test + public void shouldNotAddTwice() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + + try { + filter.add(myObj); + fail("Should have failed adding twice!"); + } catch (RuntimeException e) { + assertEquals("Stale State exception: Invalid + after +", e.getMessage()); + } + + testAssertions(filter, 1, 1, 1, 0, 0); + } + + @Test + public void shouldNotRemoveTwice() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj); + + try { + filter.remove(myObj); + fail("Should have failed removing twice!"); + } catch (RuntimeException e) { + assertEquals("Stale State exception: Invalid - after -", e.getMessage()); + } + + testAssertions(filter, 1, 1, 0, 0, 1); + } + + @Test + public void shouldAcceptUpdateTwice() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.update(myObj); + filter.update(myObj); + testAssertions(filter, 1, 1, 0, 1, 0); + } + + @Test + public void shouldStillBeAddWithUpdate() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + filter.update(myObj); + filter.update(myObj); + testAssertions(filter, 1, 1, 1, 0, 0); + } + + @Test + public void shouldNotAcceptAddAfterModify() { + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.update(myObj); + + try { + filter.add(myObj); + fail("Should have failed add after modify"); + } catch (RuntimeException e) { + assertEquals("Stale State exception: Invalid + after +=", e.getMessage()); + } + + testAssertions(filter, 1, 1, 0, 1, 0); + } + + @Test + public void shouldAcceptAddAfterRemove() { + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj); + filter.add(myObj); + + testAssertions(filter, 1, 1, 0, 1, 0); + } + + @Test + public void shouldNotAcceptModifyAfterRemove() { + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj); + + try { + filter.update(myObj); + fail("Should have failed modify after remove"); + } catch (RuntimeException e) { + assertEquals("Stale State exception: Invalid += after -", e.getMessage()); + } + + testAssertions(filter, 1, 1, 0, 0, 1); + } + + @Test + public void shouldNotAcceptDifferentKeyForSameObject() { + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + + try { + filter.update("different_key", myObj); + fail("Should have failed because of different key for already registered object"); + } catch (RuntimeException e) { + String msg = "Invalid key provided for object with transaction ID -1 and operation MODIFY: existing key is java.lang.Object, new key is different_key. Object may be present in the same filter instance only once, registered using one key only. Object"; + assertTrue("Encountered exception: " + e.getMessage(), e.getMessage().contains(msg)); + } + + testAssertions(filter, 1, 1, 1, 0, 0); + } + + @Test + public void shouldReplaceInstanceIfObjectIsEqual() { + fail("Not yet implemented"); + } + + @Test + public void shouldRemoveAfterAddAndRemove() { + + Object myObj = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj); + filter.remove(myObj); + testAssertions(filter, 0, 1, 0, 0, 0); + } + + private void testAssertions(ObjectFilter filter, int size, int sizeKeySet, int added, int updated, int removed) { + assertEquals(size, filter.sizeCache()); + assertEquals(sizeKeySet, filter.sizeKeySet()); + + List addedList = filter.getAdded(Object.class.getName()); + assertEquals(added, addedList.size()); + + List updatedList = filter.getUpdated(Object.class.getName()); + assertEquals(updated, updatedList.size()); + + List removedList = filter.getRemoved(Object.class.getName()); + assertEquals(removed, removedList.size()); + } +} From f53c20d5151a3739ceede44b3641c92349b1baf2 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 12 Aug 2013 11:02:02 +0200 Subject: [PATCH 152/457] extended XmlHelper to take InputStream and always use \n as line sep --- .../ch/eitchnet/utils/helper/XmlHelper.java | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java index d9a0c3af9..211004c3e 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -20,7 +20,10 @@ package ch.eitchnet.utils.helper; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -51,6 +54,16 @@ import ch.eitchnet.utils.exceptions.XmlException; */ 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"; + + /** + * UNIX_LINE_SEP = "\n" : mostly we want this line separator, instead of the windows version + */ + public static final String UNIX_LINE_SEP = "\n"; + /** * DEFAULT_ENCODING = "UTF-8" : defines the default UTF-8 encoding expected of XML files */ @@ -63,25 +76,38 @@ public class XmlHelper { * * @param xmlFile * the {@link File} which has the path to the XML file to read - * - * @return a {@link Document} object containing the {@link Element}s of the XML file */ public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { + try { + XmlHelper.logger.info("Parsing XML document " + xmlFile.getAbsolutePath()); + parseDocument(new FileInputStream(xmlFile), xmlHandler); + } catch (FileNotFoundException e) { + throw new XmlException("The XML file could not be read: " + xmlFile.getAbsolutePath(), 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(); - XmlHelper.logger.info("Parsing XML document " + xmlFile.getAbsolutePath()); - sp.parse(xmlFile, xmlHandler); + sp.parse(xmlFileInputStream, xmlHandler); } catch (ParserConfigurationException e) { - throw new XmlException("Failed to initialize a SAX Parser: " + e.getLocalizedMessage(), e); + throw new XmlException("Failed to initialize a SAX Parser: " + e.getMessage(), e); } catch (SAXException e) { - throw new XmlException("The XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + throw new XmlException("The XML stream is not parseable: " + e.getMessage(), e); } catch (IOException e) { - throw new XmlException("The XML could not be read: " + xmlFile.getAbsolutePath()); + throw new XmlException("The XML stream not be read: " + e.getMessage(), e); } } @@ -100,13 +126,20 @@ public class XmlHelper { XmlHelper.logger.info("Exporting document element " + document.getNodeName() + " to " + file.getAbsolutePath()); + String lineSep = System.getProperty(PROP_LINE_SEPARATOR); try { String encoding = document.getInputEncoding(); if (encoding == null || encoding.isEmpty()) { + XmlHelper.logger.info("No encoding passed. Using default encoding " + XmlHelper.DEFAULT_ENCODING); encoding = XmlHelper.DEFAULT_ENCODING; } + if (!lineSep.equals("\n")) { + XmlHelper.logger.info("Overriding line separator to \\n"); + System.setProperty(PROP_LINE_SEPARATOR, UNIX_LINE_SEP); + } + // Set up a transformer TransformerFactory transfac = TransformerFactory.newInstance(); Transformer transformer = transfac.newTransformer(); @@ -115,7 +148,7 @@ public class XmlHelper { transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.ENCODING, encoding); transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); - //transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); // Transform to file StreamResult result = new StreamResult(file); @@ -126,6 +159,9 @@ public class XmlHelper { throw new XmlException("Exception while exporting to file: " + e, e); + } finally { + + System.setProperty(PROP_LINE_SEPARATOR, lineSep); } } From c4e6c5e65d4ac1d1bd63a096a01a56ae528f0272 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 12 Aug 2013 11:04:21 +0200 Subject: [PATCH 153/457] Refactored InitializationHelper so it can take an InputStream Also renamed it to PrivilegeInitializationHelper --- .../PrivilegeInitializationHelper.java} | 42 +++++++++++++++---- .../privilege/test/PrivilegeTest.java | 4 +- .../ch/eitchnet/privilege/test/XmlTest.java | 3 +- 3 files changed, 36 insertions(+), 13 deletions(-) rename src/main/java/ch/eitchnet/privilege/{xml/InitializationHelper.java => helper/PrivilegeInitializationHelper.java} (70%) diff --git a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java similarity index 70% rename from src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index e5d1a1a24..35529dde9 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -17,9 +17,11 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.privilege.xml; +package ch.eitchnet.privilege.helper; import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; import java.util.Map; import org.slf4j.Logger; @@ -30,9 +32,9 @@ import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; import ch.eitchnet.privilege.handler.EncryptionHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; import ch.eitchnet.utils.helper.XmlHelper; /** @@ -41,9 +43,9 @@ import ch.eitchnet.utils.helper.XmlHelper; * * @author Robert von Burg */ -public class InitializationHelper { +public class PrivilegeInitializationHelper { - private static final Logger logger = LoggerFactory.getLogger(InitializationHelper.class); + private static final Logger logger = LoggerFactory.getLogger(PrivilegeInitializationHelper.class); /** * Initializes the {@link DefaultPrivilegeHandler} from the configuration file @@ -51,7 +53,8 @@ public class InitializationHelper { * @param privilegeXmlFile * a {@link File} reference to the XML file containing the configuration for Privilege * - * @return the {@link PrivilegeHandler} instance loaded from the configuration file + * @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and + * {@link PersistenceHandler} are set and initialized as well */ public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { @@ -60,10 +63,31 @@ public class InitializationHelper { throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); } + // delegate using input stream + try { + return initializeFromXml(new FileInputStream(privilegeXmlFile)); + } catch (Exception e) { + PrivilegeInitializationHelper.logger.error(e.getMessage(), e); + throw new PrivilegeException("Failed to load configuration from " + privilegeXmlFile.getAbsolutePath()); + } + } + + /** + * Initializes the {@link PrivilegeHandler} by loading from the given input stream. This stream must be a valid XML + * source + * + * @param privilegeConfigInputStream + * the XML stream containing the privilege configuration + * + * @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and + * {@link PersistenceHandler} are set and initialized as well + */ + public static PrivilegeHandler initializeFromXml(InputStream privilegeConfigInputStream) { + // parse configuration file PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); PrivilegeConfigSaxReader xmlHandler = new PrivilegeConfigSaxReader(containerModel); - XmlHelper.parseDocument(privilegeXmlFile, xmlHandler); + XmlHelper.parseDocument(privilegeConfigInputStream, xmlHandler); // initialize encryption handler String encryptionHandlerClassName = containerModel.getEncryptionHandlerClassName(); @@ -72,7 +96,7 @@ public class InitializationHelper { try { encryptionHandler.initialize(parameterMap); } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); + PrivilegeInitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + " could not be initialized"); } @@ -84,7 +108,7 @@ public class InitializationHelper { try { persistenceHandler.initialize(parameterMap); } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); + PrivilegeInitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerClassName + " could not be initialized"); } @@ -96,7 +120,7 @@ public class InitializationHelper { try { privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); + PrivilegeInitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + " could not be initialized"); } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 06ee3ca96..d3e58bb41 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -46,7 +47,6 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.test.model.TestRestrictable; import ch.eitchnet.privilege.test.model.TestSystemUserAction; import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; -import ch.eitchnet.privilege.xml.InitializationHelper; import ch.eitchnet.utils.helper.ArraysHelper; import ch.eitchnet.utils.helper.FileHelper; @@ -139,7 +139,7 @@ public class PrivilegeTest { File privilegeConfigFile = new File(pwd + "/config/Privilege.xml"); // initialize privilege - privilegeHandler = InitializationHelper.initializeFromXml(privilegeConfigFile); + privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 23db59a65..13c684834 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -33,7 +33,6 @@ import java.util.Set; import junit.framework.Assert; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; @@ -81,7 +80,7 @@ public class XmlTest { tmpDir.mkdirs(); } - @AfterClass + //@AfterClass public static void destroy() throws Exception { File tmpDir = new File("target/test"); From 1430c9217b215a64a70cae4c2e5f4f86ec532d68 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 12 Aug 2013 11:54:57 +0200 Subject: [PATCH 154/457] Fixed remaining failing tests by implementing the replacing of objects in the filter as required --- .../utils/objectfilter/ObjectFilter.java | 44 +++- .../utils/objectfilter/ObjectFilterTest.java | 204 +++++++++++++++++- 2 files changed, 231 insertions(+), 17 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index 7ee255595..def9671a8 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -118,9 +118,6 @@ public class ObjectFilter { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("add object " + objectToAdd + " with key " + key); - // add the key to the set - this.keySet.add(key); - // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. ObjectCache cached = this.cache.get(objectToAdd); if (cached == null) { @@ -148,11 +145,30 @@ public class ObjectFilter { case MODIFY: throw new IllegalStateException("Stale State exception: Invalid + after +="); case REMOVE: + // replace key if necessary + replaceKey(cached.getObject(), objectToAdd); + + // update operation's object cached.setObject(objectToAdd); cached.setOperation(Operation.MODIFY); break; } // 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}"; + msg = MessageFormat.format(msg, oldObject, newObject); + ObjectFilter.logger.warn(msg); + } + ObjectCache objectCache = this.cache.remove(oldObject); + this.cache.put(newObject, objectCache); + } } /** @@ -184,9 +200,6 @@ public class ObjectFilter { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("update object " + objectToUpdate + " with key " + key); - // add the key to the keyset - this.keySet.add(key); - // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. ObjectCache cached = this.cache.get(objectToUpdate); if (cached == null) { @@ -209,15 +222,20 @@ public class ObjectFilter { Operation op = cached.getOperation(); switch (op) { case ADD: - cached.setObject(objectToUpdate); - break; 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 -"); } // switch }// else of object not in cache + + // register the key + this.keySet.add(key); } /** @@ -249,9 +267,6 @@ public class ObjectFilter { if (ObjectFilter.logger.isDebugEnabled()) ObjectFilter.logger.debug("remove object " + objectToRemove + " with key " + key); - // add the key to the keyset - this.keySet.add(key); - // BEWARE: you fix a bug here, be sure to update BOTH tables on the logic. ObjectCache cached = this.cache.get(objectToRemove); if (cached == null) { @@ -277,6 +292,10 @@ public class ObjectFilter { 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; @@ -284,6 +303,9 @@ public class ObjectFilter { throw new IllegalStateException("Stale State exception: Invalid - after -"); } // switch } + + // register the key + this.keySet.add(key); } /** diff --git a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java index 5ae6b64b4..ef30ddee4 100644 --- a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java +++ b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java @@ -19,13 +19,14 @@ */ package ch.eitchnet.utils.objectfilter; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.*; - import java.util.List; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * @author Robert von Burg * @@ -208,7 +209,7 @@ public class ObjectFilterTest { filter.update("different_key", myObj); fail("Should have failed because of different key for already registered object"); } catch (RuntimeException e) { - String msg = "Invalid key provided for object with transaction ID -1 and operation MODIFY: existing key is java.lang.Object, new key is different_key. Object may be present in the same filter instance only once, registered using one key only. Object"; + String msg = "Object may be present in the same filter instance only once, registered using one key only"; assertTrue("Encountered exception: " + e.getMessage(), e.getMessage().contains(msg)); } @@ -216,8 +217,79 @@ public class ObjectFilterTest { } @Test - public void shouldReplaceInstanceIfObjectIsEqual() { - fail("Not yet implemented"); + public void shouldReplaceOnAddAfterRemove() { + + TestObject obj1 = new TestObject(1); + TestObject obj2 = new TestObject(1); + assertEquals("Test objects are not equal!", obj1, obj2); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(Object.class.getName(), obj1); + filter.add(Object.class.getName(), obj2); + + testAssertions(filter, 1, 1, 0, 1, 0); + + List updated = filter.getUpdated(Object.class.getName()); + Object updatedObj = updated.get(0); + String msg = "registered object is not the last operation's object"; + assertTrue(msg, obj2 == updatedObj); + } + + @Test + public void shouldReplaceOnUpdateAfterAdd() { + + TestObject obj1 = new TestObject(1); + TestObject obj2 = new TestObject(1); + assertEquals("Test objects are not equal!", obj1, obj2); + + ObjectFilter filter = new ObjectFilter(); + filter.add(Object.class.getName(), obj1); + filter.update(Object.class.getName(), obj2); + + testAssertions(filter, 1, 1, 1, 0, 0); + + List added = filter.getAdded(Object.class.getName()); + Object addedObj = added.get(0); + String msg = "registered object is not the last operation's object"; + assertTrue(msg, obj2 == addedObj); + } + + @Test + public void shouldReplaceOnUpdateAfterUpdate() { + + TestObject obj1 = new TestObject(1); + TestObject obj2 = new TestObject(1); + assertEquals("Test objects are not equal!", obj1, obj2); + + ObjectFilter filter = new ObjectFilter(); + filter.update(Object.class.getName(), obj1); + filter.update(Object.class.getName(), obj2); + + testAssertions(filter, 1, 1, 0, 1, 0); + + List updated = filter.getUpdated(Object.class.getName()); + Object updatedObj = updated.get(0); + String msg = "registered object is not the last operation's object"; + assertTrue(msg, obj2 == updatedObj); + } + + @Test + public void shouldReplaceOnRemoveAfterModify() { + + TestObject obj1 = new TestObject(1); + TestObject obj2 = new TestObject(1); + assertEquals("Test objects are not equal!", obj1, obj2); + + ObjectFilter filter = new ObjectFilter(); + filter.update(Object.class.getName(), obj1); + filter.remove(Object.class.getName(), obj2); + + testAssertions(filter, 1, 1, 0, 0, 1); + + List removed = filter.getRemoved(Object.class.getName()); + Object removedObj = removed.get(0); + String msg = "registered object is not the last operation's object"; + assertTrue(msg, obj2 == removedObj); } @Test @@ -231,6 +303,89 @@ public class ObjectFilterTest { testAssertions(filter, 0, 1, 0, 0, 0); } + @Test + public void shouldClear() { + + Object myObj1 = new Object(); + Object myObj2 = new Object(); + Object myObj3 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj1); + filter.update(myObj2); + filter.remove(myObj3); + + filter.clearCache(); + + testAssertions(filter, 0, 0, 0, 0, 0); + } + + @Test + public void shouldGetAll() { + + Object myObj1 = new Object(); + Object myObj2 = new Object(); + Object myObj3 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj1); + filter.update(myObj2); + filter.remove(myObj3); + + testAssertions(filter, 3, 1, 1, 1, 1); + + List all = filter.getAll(Object.class.getName()); + assertEquals(3, all.size()); + assertTrue(all.contains(myObj1)); + assertTrue(all.contains(myObj2)); + assertTrue(all.contains(myObj3)); + } + + @Test + public void shouldGetAdded() { + + Object myObj1 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.add(myObj1); + + testAssertions(filter, 1, 1, 1, 0, 0); + + List list = filter.getAdded(Object.class.getName()); + assertEquals(1, list.size()); + assertTrue(list.contains(myObj1)); + } + + @Test + public void shouldGetUpdated() { + + Object myObj1 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.update(myObj1); + + testAssertions(filter, 1, 1, 0, 1, 0); + + List list = filter.getUpdated(Object.class.getName()); + assertEquals(1, list.size()); + assertTrue(list.contains(myObj1)); + } + + @Test + public void shouldGetRemoved() { + + Object myObj1 = new Object(); + + ObjectFilter filter = new ObjectFilter(); + filter.remove(myObj1); + + testAssertions(filter, 1, 1, 0, 0, 1); + + List list = filter.getRemoved(Object.class.getName()); + assertEquals(1, list.size()); + assertTrue(list.contains(myObj1)); + } + private void testAssertions(ObjectFilter filter, int size, int sizeKeySet, int added, int updated, int removed) { assertEquals(size, filter.sizeCache()); assertEquals(sizeKeySet, filter.sizeKeySet()); @@ -244,4 +399,41 @@ public class ObjectFilterTest { List removedList = filter.getRemoved(Object.class.getName()); assertEquals(removed, removedList.size()); } + + private class TestObject { + private int id; + + public TestObject(int id) { + this.id = id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getOuterType().hashCode(); + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TestObject other = (TestObject) obj; + if (!getOuterType().equals(other.getOuterType())) + return false; + if (id != other.id) + return false; + return true; + } + + private ObjectFilterTest getOuterType() { + return ObjectFilterTest.this; + } + } } From 874acc582824e6eb8d52c0059dfb4df8fa05b1ea Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 12 Aug 2013 17:00:05 +0200 Subject: [PATCH 155/457] fixed wrong namespace in PrivilegeModel.xsd --- src/main/resources/PrivilegeModel.xsd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/PrivilegeModel.xsd b/src/main/resources/PrivilegeModel.xsd index cab4d1d3b..5ec6fd17c 100644 --- a/src/main/resources/PrivilegeModel.xsd +++ b/src/main/resources/PrivilegeModel.xsd @@ -1,5 +1,5 @@ + xmlns:pr="http://www.eitchnet.ch/dev/privilege/PrivilegeModel" elementFormDefault="qualified"> From 984ae06df6de72db9e77f705e694d7c441393a34 Mon Sep 17 00:00:00 2001 From: "U-PX\\rvb" Date: Tue, 20 Aug 2013 09:06:55 +0200 Subject: [PATCH 156/457] [Project] modified pom.xml to also generate javadocs and also deploy those to nexus --- pom.xml | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index ed75a6af3..456a807fb 100644 --- a/pom.xml +++ b/pom.xml @@ -135,14 +135,25 @@ attach-sources - verify + package - jar-no-fork + jar - + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + deploy + jar + + + org.apache.maven.plugins maven-jar-plugin @@ -156,7 +167,6 @@ - org.apache.maven.plugins maven-site-plugin @@ -164,6 +174,19 @@ UTF-8 + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.7 + + + deploy + deploy + deploy + + From 1c075554483107f8b287906786d8b82b432ae485 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 20 Aug 2013 18:45:18 +0200 Subject: [PATCH 157/457] [Minor] small code cleanup added default constructor, instead of instantiating instance variables at declaration --- .../eitchnet/utils/objectfilter/ObjectFilter.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index def9671a8..bd9cd4ff2 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -82,11 +82,19 @@ public class ObjectFilter { private final static Logger logger = LoggerFactory.getLogger(ObjectFilter.class); - private HashMap cache = new HashMap(); - private HashSet keySet = new HashSet(); - private static long id = ObjectCache.UNSET; + private final HashMap cache; + private final HashSet 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. *

    From 209a22033175b8764708ffefdb11842915fef45c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 20 Aug 2013 18:46:46 +0200 Subject: [PATCH 158/457] [Major] Moved property helper methods to PropertiesHelper Moved the property helper methods from SystemHelper to a new class PropertiesHelper and added methods where one can pass in the actual properties thus not being restricted to the system properties --- .../utils/helper/PropertiesHelper.java | 190 ++++++++++++++++++ .../eitchnet/utils/helper/SystemHelper.java | 64 ------ .../ch/eitchnet/utils/helper/XmlHelper.java | 4 +- 3 files changed, 192 insertions(+), 66 deletions(-) create mode 100644 src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java b/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java new file mode 100644 index 000000000..1c4fcf31a --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.utils.helper; + +import java.util.Properties; + +/** + * @author Robert von Burg + * + */ +public class PropertiesHelper { + + /** + * Returns the property with the given key from the given {@link Properties}. If def is null, and the property is + * not set, then a {@link RuntimeException} is thrown + * + * @param properties + * the {@link Properties} from which to retrieve the property + * @param context + * The context should be the name of the caller, so that stack trace gives enough detail as to which + * class expected the configuration property if no property was found + * @param key + * the key of the property to return + * @param def + * the default value, if null, then an exception will be thrown if the property is not set + * + * @return the property under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static String getProperty(Properties properties, String context, String key, String def) + throws RuntimeException { + String property = properties.getProperty(key, def); + if (property == null) + throw new RuntimeException("[" + context + "] Property " + key + " is not set, and no default was given!"); + + return property; + } + + /** + * Returns the {@link System#getProperty(String)} with the given key. If def is null, and the property is not set, + * then a {@link RuntimeException} is thrown + * + * @param context + * The context should be the name of the caller, so that stack trace gives enough detail as to which + * class expected the configuration property if no property was found + * @param key + * the key of the property to return + * @param def + * the default value, if null, then an exception will be thrown if the property is not set + * + * @return the property under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static String getProperty(String context, String key, String def) throws RuntimeException { + return getProperty(System.getProperties(), context, key, def); + } + + /** + * Delegates to {@link #getProperty(Properties, String, String, String)} but returns the value as a {@link Boolean} + * where {@link Boolean#valueOf(String)} defines the actual value + * + * @param properties + * the {@link Properties} from which to retrieve the property + * @return the property as a {@link Boolean} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Boolean getPropertyBool(Properties properties, String context, String key, Boolean def) + throws RuntimeException { + return Boolean.valueOf(getProperty(properties, context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(String, String, String)} but returns the value as a {@link Boolean} where + * {@link Boolean#valueOf(String)} defines the actual value + * + * @return the property as a {@link Boolean} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Boolean getPropertyBool(String context, String key, Boolean def) throws RuntimeException { + return Boolean.valueOf(getProperty(context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(Properties, String, String, String)} but returns the value as an {@link Integer} + * where {@link Integer#valueOf(String)} defines the actual value + * + * @return the property as a {@link Integer} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Integer getPropertyInt(Properties properties, String context, String key, Integer def) + throws RuntimeException { + return Integer.valueOf(getProperty(properties, context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(String, String, String)} but returns the value as an {@link Integer} where + * {@link Integer#valueOf(String)} defines the actual value + * + * @return the property as a {@link Integer} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Integer getPropertyInt(String context, String key, Integer def) throws RuntimeException { + return Integer.valueOf(getProperty(context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(Properties, String, String, String)} but returns the value as an {@link Double} + * where {@link Double#valueOf(String)} defines the actual value + * + * @return the property as a {@link Double} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Double getPropertyDouble(Properties properties, String context, String key, Double def) + throws RuntimeException { + return Double.valueOf(getProperty(properties, context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(String, String, String)} but returns the value as an {@link Double} where + * {@link Double#valueOf(String)} defines the actual value + * + * @return the property as a {@link Double} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Double getPropertyDouble(String context, String key, Double def) throws RuntimeException { + return Double.valueOf(getProperty(context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(Properties, String, String, String)} but returns the value as an {@link Long} + * where {@link Long#valueOf(String)} defines the actual value + * + * @return the property as a {@link Long} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Long getPropertyLong(Properties properties, String context, String key, Long def) + throws RuntimeException { + return Long.valueOf(getProperty(properties, context, key, def == null ? null : def.toString())); + } + + /** + * Delegates to {@link #getProperty(String, String, String)} but returns the value as an {@link Long} where + * {@link Long#valueOf(String)} defines the actual value + * + * @return the property as a {@link Long} under the given key + * + * @throws RuntimeException + * if the property is not set and def is null + */ + public static Long getPropertyLong(String context, String key, Long def) throws RuntimeException { + return Long.valueOf(getProperty(context, key, def == null ? null : def.toString())); + } +} diff --git a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java index fbf925bca..57dad260d 100644 --- a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java @@ -115,68 +115,4 @@ public class SystemHelper { return "Memory available " + SystemHelper.getMaxMemory() + " / Used: " + SystemHelper.getUsedMemory() + " / Free:" + SystemHelper.getFreeMemory(); } - - /** - * 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 { - String property = System.getProperty(key, def); - if (property == null) - throw new RuntimeException("[" + context + "] Property " + key + " is not set, and no default was given!"); - - return property; - } - - /** - * 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(SystemHelper.getProperty(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(SystemHelper.getProperty(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(SystemHelper.getProperty(context, key, def == null ? null : def.toString())); - } } diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java index 211004c3e..aa38e70f3 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -65,9 +65,9 @@ public class XmlHelper { public static final String UNIX_LINE_SEP = "\n"; /** - * DEFAULT_ENCODING = "UTF-8" : defines the default UTF-8 encoding expected of XML files + * DEFAULT_ENCODING = "utf-8" : defines the default UTF-8 encoding expected of XML files */ - public static final String DEFAULT_ENCODING = "UTF-8"; + public static final String DEFAULT_ENCODING = "utf-8"; private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); From f391055c6a9d3cb32b16fda8bb9197a1503a47e4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 30 Aug 2013 09:43:45 +0200 Subject: [PATCH 159/457] [Devel] reimplementing using visitor pattern Changing to the visitor pattern will make it easier to implement a SAX reader and writer This is a commit so that i don't loose my work. The code currently does not compile --- pom.xml | 6 +- src/main/java/ch/eitchnet/xmlpers/Main.java | 67 --- .../ch/eitchnet/xmlpers/XmlFilePersister.java | 423 ------------------ .../xmlpers/XmlPersistenceHandler.java | 116 ----- .../xmlpers/XmlPersistencePathBuilder.java | 157 ------- .../xmlpers/XmlPersistenceTransaction.java | 314 ------------- .../xmlpers/api/XmlPersistenceConstants.java | 33 ++ .../api/XmlPersistenceContextData.java | 48 ++ .../XmlPersistenceDao.java} | 60 +-- .../XmlPersistenceDaoFactory.java} | 19 +- .../api/XmlPersistenceDomContextData.java | 49 ++ .../XmlPersistenceException.java} | 8 +- .../api/XmlPersistenceFileHandler.java | 34 ++ .../xmlpers/api/XmlPersistenceHandler.java | 83 ++++ .../api/XmlPersistenceMetadataDao.java} | 35 +- .../api/XmlPersistenceSaxContextData.java | 65 +++ .../xmlpers/api/XmlPersistenceSaxHandler.java | 126 ++++++ .../xmlpers/api/XmlPersistenceSaxWriter.java | 35 ++ .../api/XmlPersistenceTransaction.java | 267 +++++++++++ .../eitchnet/xmlpers/impl/AbstractXmlDao.java | 174 +++++++ .../eitchnet/xmlpers/impl/MetadataXmlDao.java | 99 ++++ .../impl/XmlPersistenceDomHandler.java | 135 ++++++ .../xmlpers/impl/XmlPersistenceFileDao.java | 202 +++++++++ .../impl/XmlPersistencePathBuilder.java | 298 ++++++++++++ .../impl/XmlPersistenceStreamWriter.java | 94 ++++ .../java/ch/eitchnet/xmlpers/test/Main.java | 328 ++++++++++++++ .../xmlpers/test/XmlPersistenceTest.java | 142 +++--- .../ch/eitchnet/xmlpers/test/impl/Book.java | 111 +++++ .../eitchnet/xmlpers/test/impl/BookDao.java | 41 ++ .../eitchnet/xmlpers/test/impl/MyClass.java | 115 ----- .../xmlpers/test/impl/MyTypeResourceDao.java} | 11 +- .../xmlpers/test/impl/ResourceDao.java | 45 ++ .../xmlpers/test/impl/ResourceDomDao.java | 74 +++ .../{MyClassDao.java => ResourceSaxDao.java} | 68 +-- .../test/impl/TestModelDaoFactory.java | 77 ++++ .../xmlpers/test/model/Parameter.java | 126 ++++++ .../eitchnet/xmlpers/test/model/Resource.java | 129 ++++++ 37 files changed, 2818 insertions(+), 1396 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/xmlpers/Main.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceContextData.java rename src/main/java/ch/eitchnet/xmlpers/{XmlDao.java => api/XmlPersistenceDao.java} (51%) rename src/main/java/ch/eitchnet/xmlpers/{XmlDaoFactory.java => api/XmlPersistenceDaoFactory.java} (64%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDomContextData.java rename src/main/java/ch/eitchnet/xmlpers/{XmlPersistenceExecption.java => api/XmlPersistenceException.java} (82%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceFileHandler.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java rename src/{test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java => main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java} (58%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxContextData.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxHandler.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxWriter.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/AbstractXmlDao.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceStreamWriter.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/Main.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java rename src/{main/java/ch/eitchnet/xmlpers/XmlSaxWriter.java => test/java/ch/eitchnet/xmlpers/test/impl/MyTypeResourceDao.java} (83%) create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java rename src/test/java/ch/eitchnet/xmlpers/test/impl/{MyClassDao.java => ResourceSaxDao.java} (50%) create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java diff --git a/pom.xml b/pom.xml index 6f22a98db..85a3a0442 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ch.eitchnet ch.eitchnet.xmlpers jar - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT ch.eitchnet.xmlpers https://github.com/eitch/ch.eitchnet.xmlpers @@ -124,8 +124,8 @@ maven-compiler-plugin 2.3.2 - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/src/main/java/ch/eitchnet/xmlpers/Main.java b/src/main/java/ch/eitchnet/xmlpers/Main.java deleted file mode 100644 index 64ebde257..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/Main.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers; - -import java.io.File; -import java.io.FileWriter; - -import javanet.staxutils.IndentingXMLStreamWriter; - -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamWriter; - -/** - * @author Robert von Burg - * - */ -public class Main { - - public static void main(String[] args) throws Exception { - - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - - File file = new File("output.xml"); - XMLStreamWriter writer = factory.createXMLStreamWriter(new FileWriter(file)); - System.out.println("writer: " + writer.getClass().getName()); - - writer = new IndentingXMLStreamWriter(writer); - - writer.writeStartDocument(); - writer.writeStartElement("document"); - writer.writeEmptyElement("data"); - writer.writeAttribute("name", "value"); - //writer.writeEndElement(); - writer.writeEmptyElement("stuff"); - writer.writeAttribute("attr", "attrVal"); - //writer.writeEndElement(); - writer.writeEndDocument(); - - writer.flush(); - writer.close(); - System.out.println("Wrote to " + file.getAbsolutePath()); - - //Transformer transformer = TransformerFactory.newInstance().newTransformer(); - //Result outputTarget = new StaxR; - //Source xmlSource; - //transformer.transform(xmlSource, outputTarget); - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java b/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java deleted file mode 100644 index b077cb1b5..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/XmlFilePersister.java +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.xml.parsers.DocumentBuilder; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.SAXException; - -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.utils.helper.XmlHelper; - -/** - * @author Robert von Burg - * - */ -public class XmlFilePersister { - - private static final Logger logger = LoggerFactory.getLogger(XmlFilePersister.class); - - private boolean verbose; - private XmlPersistencePathBuilder xmlPathHelper; - - /** - * @param xmlPathHelper - * @param verbose - */ - public XmlFilePersister(XmlPersistencePathBuilder xmlPathHelper, boolean verbose) { - this.xmlPathHelper = xmlPathHelper; - this.verbose = verbose; - } - - /** - * @param type - * @param subType - * @param id - * @param document - */ - public void saveOrUpdate(String type, String subType, String id, Document document) { - - File pathF; - if (subType != null) - pathF = this.xmlPathHelper.getPathF(type, subType, id); - else - pathF = this.xmlPathHelper.getPathF(type, id); - - // if this is a new file, then check create parents, if the don't exist - if (!pathF.exists()) { - File parentFile = pathF.getParentFile(); - if (!parentFile.exists() && !parentFile.mkdirs()) { - throw new XmlPersistenceExecption("Could not create path for " + type + " / " + subType + " / " + id - + " at " + pathF.getAbsolutePath()); - } - } - - if (this.verbose) - XmlFilePersister.logger.info("Persisting " + type + " / " + subType + " / " + id + " to " - + pathF.getAbsolutePath() + "..."); - - try { - - XmlHelper.writeDocument(document, pathF); - - } catch (Exception e) { - throw new XmlPersistenceExecption("Could not persist object " + type + " / " + subType + " / " + id - + " to " + pathF.getAbsolutePath(), e); - } - - if (this.verbose) - XmlFilePersister.logger.info("Done."); - } - - /** - * @param type - * @param subType - * @param id - */ - public void remove(String type, String subType, String id) { - - File pathF; - if (subType != null) - pathF = this.xmlPathHelper.getPathF(type, subType, id); - else - pathF = this.xmlPathHelper.getPathF(type, id); - - if (this.verbose) - XmlFilePersister.logger.info("Remove persistence file for " + type + " / " + subType + " / " + id - + " from " + pathF.getAbsolutePath() + "..."); - - if (!pathF.exists()) { - XmlFilePersister.logger.error("Persistence file for " + type + " / " + subType + " / " + id - + " does not exist at " + pathF.getAbsolutePath()); - } else if (!pathF.delete()) { - throw new XmlPersistenceExecption("Could not delete persistence file for " + type + " / " + subType + " / " - + id + " at " + pathF.getAbsolutePath()); - } - - if (this.verbose) - XmlFilePersister.logger.info("Done."); - } - - /** - * @param type - * @param subType - */ - public void removeAll(String type, String subType) { - - File pathF; - if (subType == null) - pathF = this.xmlPathHelper.getPathF(type); - else - pathF = this.xmlPathHelper.getPathF(type, subType); - - if (!pathF.exists()) { - if (subType == null) - XmlFilePersister.logger.error("Path for " + type + " at " + pathF.getAbsolutePath() - + " does not exist, so removing not possible!"); - else - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() - + " does not exist, so removing not possible!"); - - } else { - - File[] filesToRemove = pathF.listFiles(); - boolean removed = FileHelper.deleteFiles(filesToRemove, this.verbose); - - if (!removed) { - if (subType == null) - throw new XmlPersistenceExecption("Could not delete persistence files for " + type + " at " - + pathF.getAbsolutePath()); - - throw new XmlPersistenceExecption("Could not delete persistence files for " + type + " / " + subType - + " at " + pathF.getAbsolutePath()); - } - } - } - - /** - * - * @param type - * @param subType - * - * @return - */ - public Set queryKeySet(String type, String subType) { - - // if a sub type is required, then it's simple: - if (subType != null) { - - File pathF = this.xmlPathHelper.getPathF(type, subType); - if (!pathF.exists()) { - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() - + " does not exist, so no objects exist!"); - return Collections.emptySet(); - } - - Set keySet = new HashSet(); - for (File f : pathF.listFiles()) { - String name = f.getName(); - keySet.add(name.substring(0, name.length() - XmlPersistencePathBuilder.FILE_EXT.length())); - } - - if (this.verbose) - XmlFilePersister.logger.info("Found " + keySet.size() + " elements for " + type + " / " + subType); - - return keySet; - } - - // otherwise we need to iterate any existing subTypes and create a combined key set - File pathF = this.xmlPathHelper.getPathF(type); - if (!pathF.exists()) { - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() - + " does not exist, so no objects exist!"); - return Collections.emptySet(); - } - - Set keySet = new HashSet(); - - File[] subTypeFiles = pathF.listFiles(); - for (File subTypeFile : subTypeFiles) { - - if (subTypeFile.isFile()) { - keySet.add(this.xmlPathHelper.getId(subTypeFile.getName())); - } else { - - for (File f : subTypeFile.listFiles()) { - keySet.add(this.xmlPathHelper.getId(f.getName())); - } - } - } - - if (this.verbose) - XmlFilePersister.logger.info("Found " + keySet.size() + " elements for " + type); - - return keySet; - } - - /** - * - * @param type - * @param subType - * - * @return - */ - public long querySize(String type, String subType) { - - // if a sub type is required, then it's simple: - if (subType != null) { - - File pathF = this.xmlPathHelper.getPathF(type, subType); - if (!pathF.exists()) { - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() - + " does not exist, so no objects exist!"); - return 0l; - } - - int length = pathF.listFiles().length; - - if (this.verbose) - XmlFilePersister.logger.info("Found " + length + " elements for " + type + " / " + subType); - - return length; - } - - // otherwise we need to iterate any existing sub types and - // return the size of the combined collection - - File pathF = this.xmlPathHelper.getPathF(type); - if (!pathF.exists()) { - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() - + " does not exist, so no objects exist!"); - return 0l; - } - - long numberOfFiles = 0l; - - File[] subTypeFiles = pathF.listFiles(); - for (File subTypeFile : subTypeFiles) { - - if (subTypeFile.isFile()) { - numberOfFiles++; - } else { - numberOfFiles += subTypeFile.listFiles().length; - } - } - - if (this.verbose) - XmlFilePersister.logger.info("Found " + numberOfFiles + " elements for " + type); - - return numberOfFiles; - } - -// XXX think about allowing paged loading... -// /** -// * @param type -// * @param subType -// * @param firstResult -// * @param maxResults -// * -// * @return -// */ -// public List queryFrom(String type, String subType, int firstResult, int maxResults) { -// -// File pathF = this.xmlPathHelper.getPathF(type, subType); -// if (!pathF.exists()) { -// logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() -// + " does not exist, so no objects exist!"); -// return Collections.emptyList(); -// } -// -// File[] listFiles = pathF.listFiles(); -// Arrays.sort(listFiles, new Comparator() { -// -// @Override -// public int compare(File file1, File file2) { -// return file1.getName().compareTo(file2.getName()); -// } -// }); -// -// // make sure positions are not illegal -// int size = listFiles.length; -// if (firstResult >= size) -// return Collections.emptyList(); -// -// if ((firstResult + maxResults) > size) -// maxResults = size - firstResult; -// -// File[] result = Arrays.copyOfRange(listFiles, firstResult, firstResult + maxResults); -// -// List list = new ArrayList(); -// for (File f : result) { -// list.add(loadObject(clazz, f)); -// } -// -// return list; -// } - - /** - * - * @param type - * @param subType - * - * @return - */ - public List queryAll(String type, String subType, DocumentBuilder docBuilder) { - - // if a sub type is required, then it's simple: - if (subType != null) { - - File pathF = this.xmlPathHelper.getPathF(type, subType); - if (!pathF.exists()) { - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() - + " does not exist, so no objects exist!"); - return Collections.emptyList(); - } - - List list = new ArrayList(); - for (File subTypeF : pathF.listFiles()) { - list.add(parseFile(subTypeF, docBuilder)); - } - - if (this.verbose) - XmlFilePersister.logger.info("Loaded " + list.size() + " elements for " + type + " / " + subType); - - return list; - } - - // otherwise we need to iterate any existing sub types and - // return those elements as well - - File pathF = this.xmlPathHelper.getPathF(type); - if (!pathF.exists()) { - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " at " + pathF.getAbsolutePath() - + " does not exist, so no objects exist!"); - return Collections.emptyList(); - } - - List list = new ArrayList(); - - File[] subTypeFiles = pathF.listFiles(); - for (File subTypeFile : subTypeFiles) { - - if (subTypeFile.isFile()) { - list.add(parseFile(subTypeFile, docBuilder)); - - } else { - for (File subTypeF : subTypeFile.listFiles()) { - list.add(parseFile(subTypeF, docBuilder)); - } - } - } - - if (this.verbose) - XmlFilePersister.logger.info("Loaded " + list.size() + " elements for " + type); - - return list; - } - - /** - * - * @param type - * @param subType - * @param id - * - * @return - */ - public Element queryById(String type, String subType, String id, DocumentBuilder docBuilder) { - - File pathF = this.xmlPathHelper.getPathF(type, subType, id); - if (!pathF.exists()) { - XmlFilePersister.logger.error("Path for " + type + " / " + subType + " / " + id + " at " - + pathF.getAbsolutePath() + " does not exist, so object does not exist!"); - return null; - } - - return parseFile(pathF, docBuilder); - } - - /** - * @param subTypeF - * @return - */ - private Element parseFile(File subTypeF, DocumentBuilder docBuilder) { - try { - - Document document = docBuilder.parse(subTypeF); - return document.getDocumentElement(); - - } catch (SAXException e) { - throw new XmlPersistenceExecption("Failed to parse file " + subTypeF.getAbsolutePath(), e); - } catch (IOException e) { - throw new XmlPersistenceExecption("Failed to read file " + subTypeF.getAbsolutePath(), e); - } - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java deleted file mode 100644 index a393e72b9..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceHandler.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.SystemHelper; -import ch.eitchnet.utils.objectfilter.ObjectFilter; - -/** - * @author Robert von Burg - * - */ -public class XmlPersistenceHandler { - - public static final String CONFIG_VERBOSE = "ch.eitchnet.xmlpers.config.verbose"; - public static final String CONFIG_BASEPATH = "ch.eitchnet.xmlpers.config.basepath"; - public static final String CONFIG_DAO_FACTORY_CLASS = "ch.eitchnet.xmlpers.config.daoFactoryClass"; - - protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); - - protected boolean verbose; - protected ThreadLocal xmlPersistenceTxThreadLocal; - protected XmlFilePersister persister; - protected XmlDaoFactory xmlDaoFactory; - - public void initialize() { - - String basePath = SystemHelper.getProperty(XmlPersistenceHandler.class.getSimpleName(), XmlPersistenceHandler.CONFIG_BASEPATH, null); - this.verbose = SystemHelper.getPropertyBool(XmlPersistenceHandler.class.getSimpleName(), XmlPersistenceHandler.CONFIG_VERBOSE, - Boolean.FALSE).booleanValue(); - - // get class to use as transaction - String daoFactoryClassName = SystemHelper.getProperty(XmlPersistenceHandler.class.getSimpleName(), - XmlPersistenceHandler.CONFIG_DAO_FACTORY_CLASS, null); - try { - @SuppressWarnings("unchecked") - Class xmlDaoFactoryClass = (Class) Class.forName(daoFactoryClassName); - - this.xmlDaoFactory = xmlDaoFactoryClass.newInstance(); - - } catch (ClassNotFoundException e) { - throw new XmlPersistenceExecption("XmlDaoFactory class does not exist " + daoFactoryClassName, e); - } catch (Exception e) { - throw new XmlPersistenceExecption("Failed to load class " + daoFactoryClassName, e); - } - - XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(basePath); - this.persister = new XmlFilePersister(pathBuilder, this.verbose); - - // initialize the Thread local object which is used per transaction - this.xmlPersistenceTxThreadLocal = new ThreadLocal(); - } - - public XmlPersistenceTransaction openTx() { - - if (this.verbose) - XmlPersistenceHandler.logger.info("Opening new transaction..."); - - // make sure no previous filter exists - XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); - if (xmlPersistenceTx != null) - throw new XmlPersistenceExecption("Previous transaction not properly closed"); - - // set a new persistence transaction object - ObjectFilter objectFilter = new ObjectFilter(); - xmlPersistenceTx = new XmlPersistenceTransaction(); - xmlPersistenceTx.initialize(this.persister, this.xmlDaoFactory, objectFilter, this.verbose); - - this.xmlPersistenceTxThreadLocal.set(xmlPersistenceTx); - - return xmlPersistenceTx; - } - - public XmlPersistenceTransaction getTx() { - XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); - if (xmlPersistenceTx == null) - throw new XmlPersistenceExecption("No transaction currently open!"); - - return xmlPersistenceTx; - } - - public void commitTx() { - - if (this.verbose) - XmlPersistenceHandler.logger.info("Committing transaction..."); - - try { - XmlPersistenceTransaction xmlPersistenceTx = this.xmlPersistenceTxThreadLocal.get(); - if (xmlPersistenceTx == null) - throw new XmlPersistenceExecption("No transaction currently open!"); - - xmlPersistenceTx.commitTx(); - } finally { - this.xmlPersistenceTxThreadLocal.set(null); - } - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java deleted file mode 100644 index baa894740..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistencePathBuilder.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers; - -import java.io.File; -import java.io.IOException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Robert von Burg - * - */ -public class XmlPersistencePathBuilder { - private static final Logger logger = LoggerFactory.getLogger(XmlPersistencePathBuilder.class); - - public static final String FILE_EXT = ".xml"; - public static final int EXT_LENGTH = XmlPersistencePathBuilder.FILE_EXT.length(); - - private String basePath; - - /** - * @param basePath - */ - public XmlPersistencePathBuilder(String basePath) { - File basePathF = new File(basePath); - if (!basePathF.exists()) - throw new XmlPersistenceExecption("The database store path does not exist at " - + basePathF.getAbsolutePath()); - if (!basePathF.canWrite()) - throw new XmlPersistenceExecption("The database store path is not writeable at " - + basePathF.getAbsolutePath()); - - try { - this.basePath = basePathF.getCanonicalPath(); - } catch (IOException e) { - throw new XmlPersistenceExecption("Failed to build canonical path from " + basePath, e); - } - - XmlPersistencePathBuilder.logger.info("Using base path " + basePath); - } - - /** - * @param id - * @return - */ - public String getFilename(String id) { - return id.concat(XmlPersistencePathBuilder.FILE_EXT); - } - - /** - * @param filename - * @return - */ - public String getId(String filename) { - if (filename.charAt(filename.length() - XmlPersistencePathBuilder.EXT_LENGTH) != '.') - throw new XmlPersistenceExecption("The filename does not have a . at index " - + (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); - - return filename.substring(0, filename.length() - XmlPersistencePathBuilder.EXT_LENGTH); - } - - /** - * @param type - * - * @return - */ - public String getPath(String type) { - - StringBuilder sb = new StringBuilder(this.basePath); - sb.append("/"); - sb.append(type); - - return sb.toString(); - } - - /** - * @param type - * - * @return - */ - public File getPathF(String type) { - return new File(getPath(type)); - } - - /** - * @param type - * @param subType - * @return - */ - public String getPath(String type, String subType) { - - StringBuilder sb = new StringBuilder(this.basePath); - sb.append("/"); - sb.append(type); - sb.append("/"); - sb.append(subType); - - return sb.toString(); - } - - /** - * @param type - * @param subType - * @return - */ - public File getPathF(String type, String subType) { - return new File(getPath(type, subType)); - } - - /** - * @param type - * @param subType - * @param id - * @return - */ - public String getPath(String type, String subType, String id) { - - StringBuilder sb = new StringBuilder(this.basePath); - sb.append("/"); - sb.append(type); - sb.append("/"); - sb.append(subType); - sb.append("/"); - sb.append(getFilename(id)); - - return sb.toString(); - } - - /** - * @param type - * @param subType - * @param id - * @return - */ - public File getPathF(String type, String subType, String id) { - return new File(getPath(type, subType, id)); - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java deleted file mode 100644 index 9d2f3f2b0..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceTransaction.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.DOMImplementation; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import ch.eitchnet.utils.helper.XmlHelper; -import ch.eitchnet.utils.objectfilter.ITransactionObject; -import ch.eitchnet.utils.objectfilter.ObjectFilter; - -/** - * @author Robert von Burg - */ -public class XmlPersistenceTransaction { - - private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTransaction.class); - - private boolean verbose; - private XmlFilePersister persister; - private XmlDaoFactory xmlDaoFactory; - private ObjectFilter objectFilter; - private DocumentBuilder docBuilder; - private DOMImplementation domImplementation; - - /** - * @param persister - * @param xmlDaoFactory - * @param objectFilter - */ - public void initialize(XmlFilePersister persister, XmlDaoFactory xmlDaoFactory, - ObjectFilter objectFilter, boolean verbose) { - this.persister = persister; - this.xmlDaoFactory = xmlDaoFactory; - this.objectFilter = objectFilter; - this.verbose = verbose; - } - - private DocumentBuilder getDocBuilder() { - if (this.docBuilder == null) { - try { - this.docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - } catch (ParserConfigurationException e) { - throw new XmlPersistenceExecption("Failed to load document builder: " + e.getLocalizedMessage(), e); - } - } - return this.docBuilder; - } - - /** - * @return - */ - protected DOMImplementation getDomImpl() { - if (this.domImplementation == null) - this.domImplementation = getDocBuilder().getDOMImplementation(); - return this.domImplementation; - } - - /* - * modifying methods - */ - - /** - * @param object - */ - public void add(ITransactionObject object) { - this.objectFilter.add(object); - } - - /** - * @param objects - */ - public void addAll(List objects) { - this.objectFilter.addAll(objects); - } - - /** - * @param object - */ - public void update(ITransactionObject object) { - this.objectFilter.update(object); - } - - /** - * @param objects - */ - public void updateAll(List objects) { - this.objectFilter.updateAll(objects); - } - - /** - * @param object - */ - public void remove(ITransactionObject object) { - this.objectFilter.remove(object); - } - - /** - * @param objects - */ - public void removeAll(List objects) { - this.objectFilter.removeAll(objects); - } - - /* - * querying methods - */ - - /** - * @param type - * @return - */ - public Set queryKeySet(String type) { - return queryKeySet(type, null); - } - - /** - * @param type - * @param subType - * @return - */ - public Set queryKeySet(String type, String subType) { - return this.persister.queryKeySet(type, subType); - } - - /** - * @param type - * @return - */ - public long querySize(String type) { - return querySize(type, null); - } - - /** - * @param type - * @param subType - * @return - */ - public long querySize(String type, String subType) { - return this.persister.querySize(type, subType); - } - - /** - * @param type - * @return - */ - public List queryAll(String type) { - return queryAll(type, null); - } - - /** - * @param type - * @param subType - * @return - */ - public List queryAll(String type, String subType) { - - // XXX ok, this is very ugly, but for starters it will have to do - XmlDao dao = this.xmlDaoFactory.getDao(type); - - List elements = this.persister.queryAll(type, subType, getDocBuilder()); - List objects = new ArrayList(elements.size()); - - for (Element element : elements) { - @SuppressWarnings("unchecked") - T object = (T) dao.parseFromDom(element); - objects.add(object); - } - - return objects; - } - - /** - * @param type - * @param id - * @return - */ - public T queryById(String type, String id) { - return queryById(type, null, id); - } - - /** - * @param type - * @param subType - * @param id - * @return - */ - public T queryById(String type, String subType, String id) { - - XmlDao dao = this.xmlDaoFactory.getDao(type); - - Element element = this.persister.queryById(type, subType, id, getDocBuilder()); - if (element == null) - throw new XmlPersistenceExecption("No object exists for " + type + " / " + subType + " / " + id); - - @SuppressWarnings("unchecked") - T object = (T) dao.parseFromDom(element); - - return object; - } - - /* - * committing - */ - - /** - * - */ - void commitTx() { - - if (this.verbose) - XmlPersistenceTransaction.logger.info("Committing..."); - - Set keySet = this.objectFilter.keySet(); - if (keySet.isEmpty()) - return; - - for (String key : keySet) { - - XmlDao dao = this.xmlDaoFactory.getDao(key); - - List removed = this.objectFilter.getRemoved(key); - if (removed.isEmpty()) { - if (this.verbose) - XmlPersistenceTransaction.logger.info("No objects removed in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransaction.logger.info(removed.size() + " objects removed in this tx."); - - for (ITransactionObject object : removed) { - - String type = dao.getType(object); - String subType = dao.getSubType(object); - String id = dao.getId(object); - - this.persister.remove(type, subType, id); - } - } - - List updated = this.objectFilter.getUpdated(key); - if (updated.isEmpty()) { - if (this.verbose) - XmlPersistenceTransaction.logger.info("No objects updated in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransaction.logger.info(updated.size() + " objects updated in this tx."); - - for (ITransactionObject object : updated) { - - String type = dao.getType(object); - String subType = dao.getSubType(object); - String id = dao.getId(object); - - Document doc = XmlHelper.createDocument(); - Element asDom = dao.serializeToDom(object, doc); - doc.appendChild(asDom); - this.persister.saveOrUpdate(type, subType, id, doc); - } - } - - List added = this.objectFilter.getAdded(key); - if (added.isEmpty()) { - if (this.verbose) - XmlPersistenceTransaction.logger.info("No objects added in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransaction.logger.info(added.size() + " objects added in this tx."); - - for (ITransactionObject object : added) { - - String type = dao.getType(object); - String subType = dao.getSubType(object); - String id = dao.getId(object); - - Document doc = XmlHelper.createDocument(); - Element asDom = dao.serializeToDom(object, doc); - doc.appendChild(asDom); - this.persister.saveOrUpdate(type, subType, id, doc); - } - } - } - - this.objectFilter.clearCache(); - XmlPersistenceTransaction.logger.info("Completed TX"); - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java new file mode 100644 index 000000000..050c9789a --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceConstants { + + public static final String PROP_VERBOSE = "ch.eitchnet.xmlpers.verbose"; + public static final String PROP_BASEPATH = "ch.eitchnet.xmlpers.basepath"; + public static final String PROP_DAO_FACTORY_CLASS = "ch.eitchnet.xmlpers.daoFactoryClass"; +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceContextData.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceContextData.java new file mode 100644 index 000000000..821f26ea9 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceContextData.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import java.io.File; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceContextData { + + private File file; + + /** + * @return the file + */ + public File getFile() { + return this.file; + } + + /** + * @param file + * the file to set + */ + public void setFile(File file) { + this.file = file; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlDao.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java similarity index 51% rename from src/main/java/ch/eitchnet/xmlpers/XmlDao.java rename to src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java index 134f290b1..e034d9c76 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java @@ -17,57 +17,37 @@ * along with ch.eitchnet.java.xmlpers. If not, see . * */ -package ch.eitchnet.xmlpers; +package ch.eitchnet.xmlpers.api; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.ContentHandler; +import java.util.List; +import java.util.Set; + +import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; /** * @author Robert von Burg - * */ -public interface XmlDao { +public interface XmlPersistenceDao { - /** - * @param object - * @return - */ - public String getType(T object); + public void setXmlPersistenceFileDao(XmlPersistenceFileDao fileDao); - /** - * @param object - * @return - */ - public String getSubType(T object); + public void setXmlPersistenceFileHandler(XmlPersistenceFileHandler fileHandler); - /** - * @param object - * @return - */ - public String getId(T object); + public void add(T object); - /** - * - * @param object - * @param document - * @return - */ - public Element serializeToDom(T object, Document document); + public void update(T object); - /** - * @param element - * @return - */ - public T parseFromDom(Element element); + public void remove(T object); - /** - * @param object - * @param contentHandler - */ - // XXX Use the XMLSerializer object for serializing to SAX... - public void serializeToSax(T object, ContentHandler contentHandler); + public void removeById(String id); - // XXX parse from SAX is missing... + public void removeAll(); + public T queryById(String id); + + public List queryAll(); + + public Set queryKeySet(); + + public long querySize(); } diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java similarity index 64% rename from src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java rename to src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java index 90b469703..76ac22cad 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlDaoFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java @@ -17,13 +17,24 @@ * along with ch.eitchnet.java.xmlpers. If not, see . * */ -package ch.eitchnet.xmlpers; +package ch.eitchnet.xmlpers.api; + +import java.util.Properties; + +import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; /** * @author Robert von Burg - * */ -public interface XmlDaoFactory { +public interface XmlPersistenceDaoFactory { - public XmlDao getDao(String type); + public void initialize(XmlPersistenceFileDao fileDao, Properties properties); + + public XmlPersistenceMetadataDao getMetadataDao(); + + public XmlPersistenceDao getDao(T object); + + public XmlPersistenceDao getDao(String type); + + public XmlPersistenceDao getDao(String type, String subType); } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDomContextData.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDomContextData.java new file mode 100644 index 000000000..081660b04 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDomContextData.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import org.w3c.dom.Document; + + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceDomContextData extends XmlPersistenceContextData { + + private Document document; + + /** + * @return the document + */ + public Document getDocument() { + return this.document; + } + + /** + * @param document + * the document to set + */ + public void setDocument(Document document) { + this.document = document; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java similarity index 82% rename from src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java rename to src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java index ff01fca32..d56835be8 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlPersistenceExecption.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java @@ -17,26 +17,26 @@ * along with ch.eitchnet.java.xmlpers. If not, see . * */ -package ch.eitchnet.xmlpers; +package ch.eitchnet.xmlpers.api; /** * @author Robert von Burg */ -public class XmlPersistenceExecption extends RuntimeException { +public class XmlPersistenceException extends RuntimeException { private static final long serialVersionUID = 1L; /** * @param message * @param cause */ - public XmlPersistenceExecption(String message, Throwable cause) { + public XmlPersistenceException(String message, Throwable cause) { super(message, cause); } /** * @param message */ - public XmlPersistenceExecption(String message) { + public XmlPersistenceException(String message) { super(message); } } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceFileHandler.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceFileHandler.java new file mode 100644 index 000000000..a3d71c00b --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceFileHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + + +/** + * @author Robert von Burg + * + */ +public interface XmlPersistenceFileHandler { + + public void read(XmlPersistenceContextData contextData); + + public void write(XmlPersistenceContextData contextData); +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java new file mode 100644 index 000000000..8a1106aa5 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.api; + +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; +import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceHandler { + + protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); + + protected boolean initialized; + protected boolean verbose; + protected XmlPersistenceDaoFactory daoFactory; + + public void initialize(Properties properties) { + if (this.initialized) + throw new IllegalStateException("Already initialized!"); + + // get properties + String context = XmlPersistenceHandler.class.getSimpleName(); + boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, + Boolean.FALSE).booleanValue(); + String daoFactoryClassName = PropertiesHelper.getProperty(properties, context, + XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, null); + + // load dao factory + XmlPersistenceDaoFactory daoFactory; + try { + @SuppressWarnings("unchecked") + Class xmlDaoFactoryClass = (Class) Class.forName(daoFactoryClassName); + + daoFactory = xmlDaoFactoryClass.newInstance(); + + } catch (ClassNotFoundException e) { + throw new XmlPersistenceException("XmlDaoFactory class does not exist " + daoFactoryClassName, e); + } catch (Exception e) { + throw new XmlPersistenceException("Failed to load class " + daoFactoryClassName, e); + } + + // initialize the dao factory + XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(properties); + XmlPersistenceFileDao fileDao = new XmlPersistenceFileDao(pathBuilder, properties); + daoFactory.initialize(fileDao, properties); + + this.daoFactory = daoFactory; + this.verbose = verbose; + } + + public XmlPersistenceTransaction openTx() { + + XmlPersistenceTransaction tx = new XmlPersistenceTransaction(this.daoFactory, this.verbose); + XmlPersistenceTransaction.setTx(tx); + return tx; + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java similarity index 58% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java rename to src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java index efa26d450..36039f7dd 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyDaoFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java @@ -17,27 +17,32 @@ * along with ch.eitchnet.java.xmlpers. If not, see . * */ -package ch.eitchnet.xmlpers.test.impl; +package ch.eitchnet.xmlpers.api; -import ch.eitchnet.xmlpers.XmlDao; -import ch.eitchnet.xmlpers.XmlDaoFactory; +import java.util.Set; + +import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; /** * @author Robert von Burg - * */ -public class MyDaoFactory implements XmlDaoFactory { +public interface XmlPersistenceMetadataDao { - /** - * @see ch.eitchnet.xmlpers.XmlDaoFactory#getDao(java.lang.String) - */ - @SuppressWarnings("unchecked") - @Override - public XmlDao getDao(String type) { - if (type.equals(MyClass.class.getName())) - return (XmlDao) new MyClassDao(); + public void setXmlPersistenceFileDao(XmlPersistenceFileDao fileDao); - throw new RuntimeException("Class with type " + type + " is unknown!"); - } + public void setXmlPersistenceFileHandler(XmlPersistenceFileHandler fileHandler); + public void removeAll(); + + public Set queryKeySet(); + + public Set queryKeySet(String type); + + public Set queryKeySet(String type, String subType); + + public long querySize(); + + public long querySize(String type); + + public long querySize(String type, String subType); } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxContextData.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxContextData.java new file mode 100644 index 000000000..8ab786c9a --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxContextData.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import org.xml.sax.helpers.DefaultHandler; + + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceSaxContextData extends XmlPersistenceContextData { + + private DefaultHandler defaultHandler; + private XmlPersistenceSaxWriter xmlWriter; + + /** + * @return the defaultHandler + */ + public DefaultHandler getDefaultHandler() { + return this.defaultHandler; + } + + /** + * @param defaultHandler + * the defaultHandler to set + */ + public void setDefaultHandler(DefaultHandler defaultHandler) { + this.defaultHandler = defaultHandler; + } + + /** + * @return the xmlWriter + */ + public XmlPersistenceSaxWriter getXmlWriter() { + return this.xmlWriter; + } + + /** + * @param xmlWriter + * the xmlWriter to set + */ + public void setXmlWriter(XmlPersistenceSaxWriter xmlWriter) { + this.xmlWriter = xmlWriter; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxHandler.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxHandler.java new file mode 100644 index 000000000..f9029ac60 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxHandler.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import javanet.staxutils.IndentingXMLStreamWriter; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.utils.exceptions.XmlException; +import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceSaxHandler implements XmlPersistenceFileHandler { + + private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceSaxHandler.class); + + @Override + public void read(XmlPersistenceContextData contextData) { + + XmlPersistenceSaxContextData cd = (XmlPersistenceSaxContextData) contextData; + + // check assertions + if (cd.getFile() == null) + throw new IllegalStateException("No file has been set on the context data!"); + if (cd.getDefaultHandler() == null) + throw new IllegalStateException("No DefaultHandler has been set on the context data!"); + + File file; + try { + + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser sp = spf.newSAXParser(); + + file = cd.getFile(); + DefaultHandler defaultHandler = cd.getDefaultHandler(); + sp.parse(file, defaultHandler); + + } catch (ParserConfigurationException | SAXException | IOException e) { + + throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); + } + + logger.info("SAX parsed file " + file.getAbsolutePath()); + } + + @Override + public void write(XmlPersistenceContextData contextData) { + + XmlPersistenceSaxContextData cd = (XmlPersistenceSaxContextData) contextData; + + // check assertions + if (cd.getFile() == null) + throw new IllegalStateException("No file has been set on the context data!"); + if (cd.getXmlWriter() == null) + throw new IllegalStateException("No Xml writer has been set on the context data!"); + + XMLStreamWriter writer = null; + try { + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + File file = cd.getFile(); + writer = factory.createXMLStreamWriter(new FileWriter(file)); + writer = new IndentingXMLStreamWriter(writer); + + // start document + writer.writeStartDocument("utf-8", "1.0"); + + // then delegate object writing to caller + XmlPersistenceStreamWriter xmlWriter = new XmlPersistenceStreamWriter(writer); + cd.getXmlWriter().write(xmlWriter); + + // and now end + writer.writeEndDocument(); + writer.flush(); + + } catch (FactoryConfigurationError | XMLStreamException | IOException e) { + throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (Exception e) { + logger.error("Failed to close stream: " + e.getMessage()); + } + } + } + + logger.info("Wrote SAX to " + cd.getFile().getAbsolutePath()); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxWriter.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxWriter.java new file mode 100644 index 000000000..df950f904 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxWriter.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import javax.xml.stream.XMLStreamException; + +import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; + +/** + * @author Robert von Burg + * + */ +public interface XmlPersistenceSaxWriter { + + public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException; +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java new file mode 100644 index 000000000..1e5e9151f --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.api; + +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.objectfilter.ObjectFilter; + +/** + * @author Robert von Burg + */ +public class XmlPersistenceTransaction { + + private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTransaction.class); + + private static final ThreadLocal TX_THREADLOCAL_THREAD_LOCAL; + static { + TX_THREADLOCAL_THREAD_LOCAL = new ThreadLocal<>(); + } + + private final XmlPersistenceDaoFactory daoFactory; + private final boolean verbose; + private final ObjectFilter objectFilter; + + /** + * @param verbose + */ + public XmlPersistenceTransaction(XmlPersistenceDaoFactory daoFactory, boolean verbose) { + this.daoFactory = daoFactory; + this.verbose = verbose; + this.objectFilter = new ObjectFilter(); + } + + /* + * modifying methods + */ + + /** + * @param object + */ + public void add(T object) { + this.objectFilter.add(object); + } + + /** + * @param objects + */ + @SuppressWarnings("unchecked") + public void addAll(List objects) { + this.objectFilter.addAll((List) objects); + } + + /** + * @param object + */ + public void update(T object) { + this.objectFilter.update(object); + } + + /** + * @param objects + */ + @SuppressWarnings("unchecked") + public void updateAll(List objects) { + this.objectFilter.updateAll((List) objects); + } + + /** + * @param object + */ + public void remove(T object) { + this.objectFilter.remove(object); + } + + /** + * @param objects + */ + @SuppressWarnings("unchecked") + public void removeAll(List objects) { + this.objectFilter.removeAll((List) objects); + } + + /* + * querying methods + */ + + /** + * @param type + * @return + */ + public Set queryKeySet() { + return this.daoFactory.getMetadataDao().queryKeySet(); + } + + /** + * @param type + * @return + */ + public Set queryKeySet(String type) { + return this.daoFactory.getMetadataDao().queryKeySet(type); + } + + /** + * @param type + * @param subType + * @return + */ + public Set queryKeySet(String type, String subType) { + return this.daoFactory.getMetadataDao().queryKeySet(type, subType); + } + + /** + * @param type + * @return + */ + public long querySize(String type) { + return querySize(type, null); + } + + /** + * @param type + * @param subType + * @return + */ + public long querySize(String type, String subType) { + return this.daoFactory.getDao(type, subType).querySize(); + } + + /** + * @param type + * @return + */ + public List queryAll(String type) { + XmlPersistenceDao dao = this.daoFactory.getDao(type); + List objects = dao.queryAll(type); + return objects; + } + + /** + * @param type + * @param subType + * @return + */ + public List queryAll(String type, String subType) { + XmlPersistenceDao dao = this.daoFactory.getDao(type, subType); + List objects = dao.queryAll(type, subType); + return objects; + } + + /** + * @param type + * @param id + * @return + */ + public T queryById(String type, String id) { + return queryById(type, id); + } + + /** + * @param type + * @param subType + * @param id + * @return + */ + public T queryById(String type, String subType, String id) { + XmlPersistenceDao dao = this.daoFactory.getDao(type, subType); + T object = dao.queryById(id); + return object; + } + + /** + * + */ + public void commit() { + + if (this.verbose) + XmlPersistenceTransaction.logger.info("Committing TX..."); + + Set keySet = this.objectFilter.keySet(); + if (keySet.isEmpty()) + return; + + for (String key : keySet) { + + List removed = this.objectFilter.getRemoved(key); + if (removed.isEmpty()) { + if (this.verbose) + XmlPersistenceTransaction.logger.info("No objects removed in this tx."); + } else { + if (this.verbose) + XmlPersistenceTransaction.logger.info(removed.size() + " objects removed in this tx."); + + for (Object object : removed) { + XmlPersistenceDao dao = this.daoFactory.getDao(object); + dao.remove(object); + } + } + + List updated = this.objectFilter.getUpdated(key); + if (updated.isEmpty()) { + if (this.verbose) + XmlPersistenceTransaction.logger.info("No objects updated in this tx."); + } else { + if (this.verbose) + XmlPersistenceTransaction.logger.info(updated.size() + " objects updated in this tx."); + + for (Object object : updated) { + + XmlPersistenceDao dao = this.daoFactory.getDao(object); + dao.update(object); + } + } + + List added = this.objectFilter.getAdded(key); + if (added.isEmpty()) { + if (this.verbose) + XmlPersistenceTransaction.logger.info("No objects added in this tx."); + } else { + if (this.verbose) + XmlPersistenceTransaction.logger.info(added.size() + " objects added in this tx."); + + for (Object object : added) { + + XmlPersistenceDao dao = this.daoFactory.getDao(object); + dao.add(object); + } + } + } + + this.objectFilter.clearCache(); + XmlPersistenceTransaction.logger.info("Completed TX"); + } + + public static XmlPersistenceTransaction getTx() { + XmlPersistenceTransaction tx = TX_THREADLOCAL_THREAD_LOCAL.get(); + if (tx == null) + throw new IllegalStateException("No transaction is currently open!"); + return tx; + } + + public static void setTx(XmlPersistenceTransaction tx) { + if (TX_THREADLOCAL_THREAD_LOCAL.get() != null) + throw new IllegalStateException("A transaction is already open!"); + TX_THREADLOCAL_THREAD_LOCAL.set(tx); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/AbstractXmlDao.java b/src/main/java/ch/eitchnet/xmlpers/impl/AbstractXmlDao.java new file mode 100644 index 000000000..7d4491855 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/AbstractXmlDao.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.impl; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import ch.eitchnet.utils.exceptions.XmlException; +import ch.eitchnet.xmlpers.api.XmlPersistenceDao; +import ch.eitchnet.xmlpers.api.XmlPersistenceFileHandler; + +/** + * @author Robert von Burg + * + */ +public abstract class AbstractXmlDao implements XmlPersistenceDao { + + private XmlPersistenceFileDao fileDao; + private XmlPersistenceFileHandler fileHandler; + + // TODO think about setting some methods to final + // TODO maybe decouple the write and load method into their own class + // TODO if no sub type is given, then don't search recursively - it means subType does not exist + + @Override + public void setXmlPersistenceFileDao(XmlPersistenceFileDao fileDao) { + this.fileDao = fileDao; + } + + @Override + public void setXmlPersistenceFileHandler(XmlPersistenceFileHandler fileHandler) { + this.fileHandler = fileHandler; + } + + /** + * @return the fileHandler + */ + protected XmlPersistenceFileHandler getFileHandler() { + return this.fileHandler; + } + + /** + * @return the fileDao + */ + protected XmlPersistenceFileDao getFileDao() { + return this.fileDao; + } + + @Override + public void remove(T object) { + this.fileDao.remove(getType(), getSubType(), getId(object)); + } + + @Override + public void removeById(String id) { + this.fileDao.remove(getType(), getSubType(), id); + } + + @Override + public void removeAll() { + this.fileDao.removeAll(getType(), getSubType()); + } + + @Override + public Set queryKeySet() { + return this.fileDao.queryKeySet(getType(), getSubType()); + } + + @Override + public long querySize() { + return this.fileDao.querySize(getType(), getSubType()); + } + + @Override + public List queryAll() { + File queryPath = this.fileDao.getPathBuilder().getPath(getType(), getSubType()); + String msg = "Can not read persistence unit for {0} / {1} at {2}"; + return queryAll(queryPath, msg, getType(), getSubType(), queryPath); + } + + private List queryAll(File queryPath, String errorMsg, Object... msgArgs) { + File[] persistenceUnits = queryPath.listFiles(); + List result = new ArrayList<>(); + for (File persistenceUnit : persistenceUnits) { + if (!persistenceUnit.isFile()) + continue; + + assertReadable(persistenceUnit, errorMsg, msgArgs); + T object = read(persistenceUnit); + result.add(object); + } + + return result; + } + + private void assertReadable(File persistenceUnit, String errorMsg, Object... msgArgs) { + if (!persistenceUnit.canRead()) { + String msg = String.format(errorMsg, msgArgs); + throw new XmlException(msg); + } + } + + @Override + public T queryById(String id) { + File persistenceUnit = this.fileDao.getPathBuilder().getPath(getType(), getSubType(), id); + String msg = "Can not read persistence unit for {0} / {1} / {2} at {3}"; + return queryById(persistenceUnit, msg, getType(), getSubType(), id, persistenceUnit); + } + + private T queryById(File persistenceUnit, String msg, Object... msgArgs) { + assertReadable(persistenceUnit, msg, msgArgs); + T object = read(persistenceUnit); + return object; + } + + @Override + public void add(T object) { + XmlPersistencePathBuilder pathBuilder = this.fileDao.getPathBuilder(); + File addPath = pathBuilder.getAddPath(getType(), getSubType(), getId(object)); + write(object, addPath); + } + + @Override + public void update(T object) { + XmlPersistencePathBuilder pathBuilder = this.fileDao.getPathBuilder(); + File updatePath = pathBuilder.getUpdatePath(getType(), getSubType(), getId(object)); + write(object, updatePath); + } + + /** + * Returns the type of domain object being handled by this {@link XmlPersistenceDao}. This would in most cases be + * the simple name of the class being persisted + * + * @return the type of object being persisted + */ + protected abstract String getType(); + + /** + * Returns the sub type, enabling categorizing types by a sub type. Default implementation returns null, thus no + * categorization is performed for this {@link XmlPersistenceDao} implementation + * + * @return the sub type to further categorize the type of object being persisted + */ + protected String getSubType() { + return null; + } + + protected abstract String getId(T object); + + protected abstract T read(File filePath); + + protected abstract void write(T object, File filePath); +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java b/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java new file mode 100644 index 000000000..019a2ad71 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.impl; + +import java.util.Set; + +import ch.eitchnet.xmlpers.api.XmlPersistenceFileHandler; +import ch.eitchnet.xmlpers.api.XmlPersistenceMetadataDao; + +/** + * @author Robert von Burg + * + */ +public abstract class MetadataXmlDao implements XmlPersistenceMetadataDao { + + private XmlPersistenceFileDao fileDao; + private XmlPersistenceFileHandler fileHandler; + + // TODO think about setting some methods to final + // TODO maybe decouple the write and load method into their own class + + @Override + public void setXmlPersistenceFileDao(XmlPersistenceFileDao fileDao) { + this.fileDao = fileDao; + } + + @Override + public void setXmlPersistenceFileHandler(XmlPersistenceFileHandler fileHandler) { + this.fileHandler = fileHandler; + } + + /** + * @return the fileHandler + */ + protected XmlPersistenceFileHandler getFileHandler() { + return this.fileHandler; + } + + /** + * @return the fileDao + */ + protected XmlPersistenceFileDao getFileDao() { + return this.fileDao; + } + + @Override + public void removeAll() { + this.fileDao.removeAll(); + } + + @Override + public Set queryKeySet(String type, String subType) { + return this.fileDao.queryKeySet(type, subType); + } + + @Override + public Set queryKeySet(String type) { + return this.fileDao.queryKeySet(type); + } + + @Override + public Set queryKeySet() { + return this.fileDao.queryKeySet(); + } + + @Override + public long querySize(String type, String subType) { + return this.fileDao.querySize(type, subType); + } + + @Override + public long querySize(String type) { + return this.fileDao.querySize(type); + } + + @Override + public long querySize() { + return this.fileDao.querySize(); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java new file mode 100644 index 000000000..102c7738a --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.impl; + +import java.io.File; +import java.io.IOException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +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.xml.sax.SAXException; + +import ch.eitchnet.utils.exceptions.XmlException; +import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.xmlpers.api.XmlPersistenceContextData; +import ch.eitchnet.xmlpers.api.XmlPersistenceDomContextData; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.api.XmlPersistenceFileHandler; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceDomHandler implements XmlPersistenceFileHandler { + + private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceDomHandler.class); + + public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException { + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + return docBuilder; + } + + @Override + public void read(XmlPersistenceContextData contextData) { + + XmlPersistenceDomContextData cd = (XmlPersistenceDomContextData) contextData; + + // check assertions + if (cd.getFile() == null) + throw new IllegalStateException("No file has been set on the context data!"); + + try { + DocumentBuilder docBuilder = createDocumentBuilder(); + File file = cd.getFile(); + Document document = docBuilder.parse(file); + cd.setDocument(document); + } catch (ParserConfigurationException | SAXException | IOException e) { + throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); + } + + logger.info("DOM parsed file " + cd.getFile().getAbsolutePath()); + } + + @Override + public void write(XmlPersistenceContextData contextData) { + + XmlPersistenceDomContextData cd = (XmlPersistenceDomContextData) contextData; + + // check assertions + if (cd.getFile() == null) + throw new IllegalStateException("No file has been set on the context data!"); + if (cd.getDocument() == null) + throw new IllegalStateException("No document has been set on the context data!"); + + String lineSep = System.getProperty(XmlHelper.PROP_LINE_SEPARATOR); + try { + Document document = cd.getDocument(); + String encoding = document.getInputEncoding(); + if (encoding == null || encoding.isEmpty()) { + logger.info("No encoding passed. Using default encoding " + XmlHelper.DEFAULT_ENCODING); + encoding = XmlHelper.DEFAULT_ENCODING; + } + + if (!lineSep.equals("\n")) { + logger.info("Overriding line separator to \\n"); + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, "\n"); + } + + // Set up a transformer + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer transformer = transfac.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + + // Transform to file + File file = new File("target/res_dom.xml"); + StreamResult result = new StreamResult(file); + Source xmlSource = new DOMSource(document); + transformer.transform(xmlSource, result); + + logger.info("Wrote DOM to " + file.getAbsolutePath()); + + } catch (TransformerFactoryConfigurationError | TransformerException e) { + throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); + } finally { + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, lineSep); + } + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java new file mode 100644 index 000000000..4697d8e58 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.impl; + +import java.io.File; +import java.text.MessageFormat; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; + +/** + * @author Robert von Burg + */ +public class XmlPersistenceFileDao { + + private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceFileDao.class); + + private boolean verbose; + private XmlPersistencePathBuilder pathBuilder; + + public XmlPersistenceFileDao(XmlPersistencePathBuilder pathBuilder, Properties properties) { + + // get properties + String context = XmlPersistencePathBuilder.class.getSimpleName(); + boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, + Boolean.FALSE).booleanValue(); + + // initialize + this.verbose = verbose; + this.pathBuilder = pathBuilder; + } + + /** + * @return the pathBuilder + */ + public XmlPersistencePathBuilder getPathBuilder() { + return this.pathBuilder; + } + + public void removeAll() { + + File removePath = this.pathBuilder.getRemovePath(); + String failMsg = "Deletion of persistence units at {1} failed! Check file permissions!"; + remove(removePath, failMsg, removePath); + } + + public void removeAll(String type) { + + File removePath = this.pathBuilder.getRemovePath(type); + String failMsg = "Deletion of persistence units for {0} at {1} failed! Check file permissions!"; + remove(removePath, failMsg, type, removePath); + } + + public void removeAll(String type, String subType) { + + File removePath = this.pathBuilder.getRemovePath(type, subType); + String failMsg = "Deletion of persistence units for {0} / {1} at {2} failed! Check file permissions!"; + remove(removePath, failMsg, type, subType, removePath); + } + + public void remove(String type, String id) { + File removePath = this.pathBuilder.getRemovePath(type, id); + String failMsg = "Deletion of persistence units for {0} / {1} at {2} failed! Check file permissions!"; + remove(removePath, failMsg, type, id, removePath); + } + + public void remove(String type, String subType, String id) { + File removePath = this.pathBuilder.getRemovePath(type, subType, id); + String failMsg = "Deletion of persistence units for {0} / {1} / {2} at {3} failed! Check file permissions!"; + remove(removePath, failMsg, type, subType, id, removePath); + } + + private void remove(File removePath, String failMsg, Object... msgParts) { + File[] removePaths = new File[] { removePath }; + boolean removed = FileHelper.deleteFiles(removePaths, this.verbose); + if (!removed) { + String msg = MessageFormat.format(failMsg, msgParts); + throw new XmlPersistenceException(msg); + } + } + + /** + * Returns the set of types + * + * @return + */ + public Set queryKeySet() { + File queryPath = this.pathBuilder.getQueryPath(); + Set keySet = queryKeySet(queryPath); + if (this.verbose) + XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " types"); + return keySet; + } + + /** + * Returns the set of sub types for the given type + * + * @param type + * + * @return + */ + public Set queryKeySet(String type) { + File queryPath = this.pathBuilder.getQueryPath(type); + Set keySet = queryKeySet(queryPath); + if (this.verbose) + XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " elements for " + type); + return keySet; + } + + /** + * Returns the set of ids for the objects with the give type and sub type + * + * @param type + * @param subType + * + * @return + */ + public Set queryKeySet(String type, String subType) { + File queryPath = this.pathBuilder.getQueryPath(type, subType); + Set keySet = queryKeySet(queryPath); + if (this.verbose) + XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " elements for " + type); + return keySet; + } + + /** + * @param queryPath + * @return + */ + private Set queryKeySet(File queryPath) { + Set keySet = new HashSet(); + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + if (subTypeFile.isFile()) + keySet.add(this.pathBuilder.getId(subTypeFile.getName())); + } + + return keySet; + } + + public long querySize() { + File queryPath = this.pathBuilder.getQueryPath(); + long numberOfFiles = querySize(queryPath); + if (this.verbose) + XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " types"); + return numberOfFiles; + } + + public long querySize(String type) { + File queryPath = this.pathBuilder.getQueryPath(type); + long numberOfFiles = querySize(queryPath); + if (this.verbose) + XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " elements for " + type); + return numberOfFiles; + } + + public long querySize(String type, String subType) { + File queryPath = this.pathBuilder.getQueryPath(type, subType); + long numberOfFiles = querySize(queryPath); + if (this.verbose) + XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " elements for " + type); + return numberOfFiles; + } + + private long querySize(File queryPath) { + long numberOfFiles = 0l; + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + + if (subTypeFile.isFile()) + numberOfFiles++; + } + return numberOfFiles; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java new file mode 100644 index 000000000..952669c7a --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.impl; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; + +/** + * @author Robert von Burg + */ +public class XmlPersistencePathBuilder { + private static final Logger logger = LoggerFactory.getLogger(XmlPersistencePathBuilder.class); + + public static final String FILE_EXT = ".xml"; + public static final int EXT_LENGTH = XmlPersistencePathBuilder.FILE_EXT.length(); + + private final boolean verbose; + private final String basePath; + + public XmlPersistencePathBuilder(Properties properties) { + + // get properties + String context = XmlPersistencePathBuilder.class.getSimpleName(); + boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, + Boolean.FALSE).booleanValue(); + String basePath = PropertiesHelper + .getProperty(properties, context, XmlPersistenceConstants.PROP_BASEPATH, null); + + // validate base path exists and is writable + File basePathF = new File(basePath); + if (!basePathF.exists()) + throw new XmlPersistenceException("The database store path does not exist at " + + basePathF.getAbsolutePath()); + if (!basePathF.canWrite()) + throw new XmlPersistenceException("The database store path is not writeable at " + + basePathF.getAbsolutePath()); + + // we want a clean base path + String canonicalBasePath; + try { + canonicalBasePath = basePathF.getCanonicalPath(); + } catch (IOException e) { + throw new XmlPersistenceException("Failed to build canonical path from " + basePath, e); + } + + // this.basePathF = basePathF; + this.basePath = canonicalBasePath; + this.verbose = verbose; + + logger.info("Using base path " + basePath); + } + + String getFilename(String id) { + return id.concat(XmlPersistencePathBuilder.FILE_EXT); + } + + String getId(String filename) { + assertFilename(filename); + + return filename.substring(0, filename.length() - XmlPersistencePathBuilder.EXT_LENGTH); + } + + String getPathAsString(String type, String subType, String id) { + StringBuilder sb = new StringBuilder(this.basePath); + if (!StringHelper.isEmpty(type)) { + sb.append("/"); + sb.append(type); + } + if (!StringHelper.isEmpty(subType)) { + sb.append("/"); + sb.append(subType); + } + if (!StringHelper.isEmpty(id)) { + sb.append("/"); + sb.append(getFilename(id)); + } + + return sb.toString(); + } + + File getPath() { + return new File(getPathAsString(null, null, null)); + } + + File getPath(String type) { + assertType(type); + return new File(getPathAsString(type, null, null)); + } + + File getPath(String type, String subType) { + assertType(type); + assertSubType(subType); + return new File(getPathAsString(type, subType, null)); + } + + File getPath(String type, String subType, String id) { + assertType(type); + assertSubType(subType); + assertId(id); + return new File(getPathAsString(type, subType, id)); + } + + File getAddPath(String type, String subType, String id) { + assertType(type); + assertSubType(subType); + assertId(id); + + File path = getPath(type, subType, id); + + if (path.exists()) { + String msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; + throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); + } + + // check if parent path exists + if (!path.exists()) { + File parentFile = path.getParentFile(); + if (!parentFile.exists() && !parentFile.mkdirs()) { + String msg = "Could not create parent path for {0} / {1} / {2} at {3}"; + throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); + } + } + + return path; + } + + File getUpdatePath(String type, String subType, String id) { + assertType(type); + assertSubType(subType); + assertId(id); + + File path = getPath(type, subType, id); + + if (!path.exists()) { + String msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; + throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); + } + + return path; + } + + File getRemovePath() { + + File path = getPath(); + if (!path.exists()) { + String msg = "No Persistence units exist at {0}"; + throw new XmlPersistenceException(MessageFormat.format(msg, path.getAbsolutePath())); + } + + if (this.verbose) { + String msg = "Remove path for all is {0}..."; + logger.info(MessageFormat.format(msg, path.getAbsolutePath())); + } + + return path; + } + + File getRemovePath(String type) { + assertType(type); + + File path = getPath(type); + if (!path.exists()) { + String msg = "No Persistence units exist for {0} at {1}"; + throw new XmlPersistenceException(MessageFormat.format(msg, type, path.getAbsolutePath())); + } + + if (this.verbose) { + String msg = "Remove path for {0} is {1}..."; + logger.info(MessageFormat.format(msg, type, path.getAbsolutePath())); + } + + return path; + } + + File getRemovePath(String type, String subType) { + assertType(type); + assertSubType(subType); + + File path = getPath(type, subType); + if (!path.exists()) { + String msg = "No Persistence units exist for {0} / {1} at {2}"; + throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, path.getAbsolutePath())); + } + + if (this.verbose) { + String msg = "Remove path for {0} / {1} is {2}..."; + logger.info(MessageFormat.format(msg, type, subType, path.getAbsolutePath())); + } + + return path; + } + + File getRemovePath(String type, String subType, String id) { + assertType(type); + assertSubType(subType); + assertId(id); + + File path = getPath(type, subType, id); + if (!path.exists()) { + String msg = "Persistence unit for {0} / {1} / {2} does not exist at {3}"; + throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); + } + + if (this.verbose) { + String msg = "Remove path for {0} / {1} / {2} is {3}..."; + logger.info(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); + } + + return path; + } + + File getQueryPath() { + return getPath(); + } + + File getQueryPath(String type) { + assertType(type); + File path = getPath(type); + if (this.verbose) { + String msg = "Query path for {0} is {1}..."; + logger.info(MessageFormat.format(msg, type, path.getAbsolutePath())); + } + return path; + } + + File getQueryPath(String type, String subType) { + assertType(type); + assertSubType(subType); + File path = getPath(type, subType); + if (this.verbose) { + String msg = "Query path for {0} / {1} is {2}..."; + logger.info(MessageFormat.format(msg, type, subType, path.getAbsolutePath())); + } + return path; + } + + File getQueryPath(String type, String subType, String id) { + assertType(type); + assertSubType(subType); + assertId(id); + File path = getPath(type, subType, id); + if (this.verbose) { + String msg = "Query path for {0} / {1} / {2} is {3}..."; + logger.info(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); + } + return path; + } + + private void assertId(String id) { + if (StringHelper.isEmpty(id)) + throw new XmlPersistenceException( + "The id can not be empty! An object must always be handled with at least the type and id!"); + } + + private void assertType(String type) { + if (StringHelper.isEmpty(type)) + throw new XmlPersistenceException( + "The type can not be empty! An object must always be handled with at least the type and id!"); + } + + private void assertSubType(String subType) { + if (StringHelper.isEmpty(subType)) + throw new XmlPersistenceException("The subType can not be empty!"); + } + + private void assertFilename(String filename) { + if (filename.charAt(filename.length() - XmlPersistencePathBuilder.EXT_LENGTH) != '.') + throw new XmlPersistenceException("The filename does not have a . at index " + + (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceStreamWriter.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceStreamWriter.java new file mode 100644 index 000000000..7294e331d --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceStreamWriter.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.impl; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceStreamWriter { + + private XMLStreamWriter writer; + + public XmlPersistenceStreamWriter(XMLStreamWriter writer) { + this.writer = writer; + } + + /** + * @param localName + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeEmptyElement(java.lang.String) + */ + public void writeEmptyElement(String localName) throws XMLStreamException { + this.writer.writeEmptyElement(localName); + } + + /** + * @param localName + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeStartElement(java.lang.String) + */ + public void writeElement(String localName) throws XMLStreamException { + this.writer.writeStartElement(localName); + } + + /** + * Note: Don't call this method to close an element written by {@link #writeEmptyElement(String)} + * + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeEndElement() + */ + public void endElement() throws XMLStreamException { + this.writer.writeEndElement(); + } + + /** + * @param localName + * @param value + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeAttribute(java.lang.String, java.lang.String) + */ + public void writeAttribute(String localName, String value) throws XMLStreamException { + this.writer.writeAttribute(localName, value); + } + + /** + * @param data + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeCData(java.lang.String) + */ + public void writeCData(String data) throws XMLStreamException { + this.writer.writeCData(data); + } + + /** + * @param text + * @throws XMLStreamException + * @see javax.xml.stream.XMLStreamWriter#writeCharacters(java.lang.String) + */ + public void writeCharacters(String text) throws XMLStreamException { + this.writer.writeCharacters(text); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/Main.java b/src/test/java/ch/eitchnet/xmlpers/test/Main.java new file mode 100644 index 000000000..bc61911d5 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/Main.java @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test; + +import java.io.File; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.List; + +import javanet.staxutils.IndentingXMLStreamWriter; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamWriter; +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.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.utils.exceptions.XmlException; +import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.xmlpers.test.model.Parameter; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class Main { + + private static final Logger logger = LoggerFactory.getLogger(Main.class); + + private static Resource res; + + public static void main(String[] args) throws Exception { + + res = new Resource(); + res.setId("id1"); + res.setName("name1"); + res.setType("type1"); + + Parameter param1 = new Parameter(); + param1.setId("paramId1"); + param1.setName("paramName1"); + param1.setType("paramType1"); + param1.setValue("paramValue1"); + res.addParameter(param1); + + Parameter param2 = new Parameter(); + param2.setId("paramId2"); + param2.setName("paramName2"); + param2.setType("paramType2"); + param2.setValue("paramValue2"); + res.addParameter(param2); + + Parameter param3 = new Parameter(); + param3.setId("paramId3"); + param3.setName("paramName3"); + param3.setType("paramType3"); + param3.setValue("paramValue3"); + res.addParameter(param3); + + logger.info("Writing Res:\n" + res); + + writeSax(res); + writeDom(res); + + List resoures; + resoures = readDom(); + logger.info("Parsed Resources:\n" + resoures); + resoures = readSax(); + logger.info("Parsed Resources:\n" + resoures); + } + + /** + * @return + * + */ + private static List readDom() throws Exception { + + File file = new File("target/res_dom.xml"); + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document document = docBuilder.parse(file); + Element rootElement = document.getDocumentElement(); + + List resources = new ArrayList(); + + NodeList resElements = rootElement.getElementsByTagName("Resource"); + for (int i = 0; i < resElements.getLength(); i++) { + Element resElement = (Element) resElements.item(i); + String id = resElement.getAttribute("id"); + String name = resElement.getAttribute("name"); + String type = resElement.getAttribute("type"); + + Resource res = new Resource(); + res.setId(id); + res.setName(name); + res.setType(type); + + NodeList paramElements = resElement.getElementsByTagName("Parameter"); + parseParameters(res, paramElements); + + resources.add(res); + } + + logger.info("DOM parsed file " + file.getAbsolutePath()); + + return resources; + } + + /** + * @param res2 + * @param paramElements + */ + private static void parseParameters(Resource res, NodeList paramElements) { + for (int i = 0; i < paramElements.getLength(); i++) { + Element paramElement = (Element) paramElements.item(i); + String id = paramElement.getAttribute("id"); + String name = paramElement.getAttribute("name"); + String type = paramElement.getAttribute("type"); + String value = paramElement.getAttribute("value"); + + Parameter param = new Parameter(); + param.setId(id); + param.setName(name); + param.setType(type); + param.setValue(value); + + res.addParameter(param); + } + } + + /** + * @return + * + */ + private static List readSax() throws Exception { + + final List resources = new ArrayList<>(); + final Resource[] currentRes = new Resource[1]; + + DefaultHandler xmlHandler = new DefaultHandler() { + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + switch (qName) { + case "Resource": + Resource res = new Resource(); + res.setId(attributes.getValue("id")); + res.setName(attributes.getValue("name")); + res.setType(attributes.getValue("type")); + currentRes[0] = res; + resources.add(res); + break; + case "Parameter": + Parameter param = new Parameter(); + param.setId(attributes.getValue("id")); + param.setName(attributes.getValue("name")); + param.setType(attributes.getValue("type")); + param.setValue(attributes.getValue("value")); + currentRes[0].addParameter(param); + break; + case "model": + break; + default: + throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); + } + } + }; + + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser sp = spf.newSAXParser(); + File file = new File("target/res_sax.xml"); + sp.parse(file, xmlHandler); + + logger.info("SAX parsed file " + file.getAbsolutePath()); + + return resources; + } + + private static void writeDom(Resource res) throws Exception { + + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Element resElement = doc.createElement("Resource"); + resElement.setAttribute("id", res.getId()); + resElement.setAttribute("name", res.getName()); + resElement.setAttribute("type", res.getType()); + + for (String paramId : res.getParameterKeySet()) { + Parameter param = res.getParameterBy(paramId); + Element paramElement = doc.createElement("Parameter"); + paramElement.setAttribute("id", param.getId()); + paramElement.setAttribute("name", param.getName()); + paramElement.setAttribute("type", param.getType()); + paramElement.setAttribute("value", param.getValue()); + resElement.appendChild(paramElement); + } + + Element rootElement = doc.createElement("model"); + rootElement.appendChild(resElement); + + doc.appendChild(rootElement); + + String lineSep = System.getProperty(XmlHelper.PROP_LINE_SEPARATOR); + try { + + String encoding = doc.getInputEncoding(); + if (encoding == null || encoding.isEmpty()) { + logger.info("No encoding passed. Using default encoding " + XmlHelper.DEFAULT_ENCODING); + encoding = XmlHelper.DEFAULT_ENCODING; + } + + if (!lineSep.equals("\n")) { + logger.info("Overriding line separator to \\n"); + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, "\n"); + } + + // Set up a transformer + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer transformer = transfac.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + + // Transform to file + File file = new File("target/res_dom.xml"); + StreamResult result = new StreamResult(file); + Source xmlSource = new DOMSource(doc); + transformer.transform(xmlSource, result); + + logger.info("Wrote DOM to " + file.getAbsolutePath()); + + } catch (Exception e) { + + throw new XmlException("Exception while exporting to file: " + e, e); + + } finally { + + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, lineSep); + } + } + + private static void writeSax(Resource res) throws Exception { + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + + File file = new File("target/res_sax.xml"); + XMLStreamWriter writer = factory.createXMLStreamWriter(new FileWriter(file)); + + writer = new IndentingXMLStreamWriter(writer); + + writer.writeStartDocument("utf-8", "1.0"); + writer.writeStartElement("model"); + + writer.writeStartElement("Resource"); + writer.writeAttribute("id", res.getId()); + writer.writeAttribute("name", res.getName()); + writer.writeAttribute("type", res.getType()); + + for (String paramId : res.getParameterKeySet()) { + Parameter param = res.getParameterBy(paramId); + writer.writeEmptyElement("Parameter"); + writer.writeAttribute("id", param.getId()); + writer.writeAttribute("name", param.getName()); + writer.writeAttribute("type", param.getType()); + writer.writeAttribute("value", param.getValue()); + } + + //writer.writeEmptyElement("data"); + //writer.writeAttribute("name", "value"); + ////writer.writeEndElement(); + //writer.writeEmptyElement("stuff"); + //writer.writeAttribute("attr", "attrVal"); + + writer.writeEndElement(); + writer.writeEndDocument(); + + writer.flush(); + writer.close(); + logger.info("Wrote SAX to " + file.getAbsolutePath()); + + //Transformer transformer = TransformerFactory.newInstance().newTransformer(); + //Result outputTarget = new StaxR; + //Source xmlSource; + //transformer.transform(xmlSource, outputTarget); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java index 12aeb4314..7e6eb3048 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java @@ -21,6 +21,7 @@ package ch.eitchnet.xmlpers.test; import java.io.File; import java.util.List; +import java.util.Properties; import java.util.Set; import org.junit.Assert; @@ -30,12 +31,12 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.objectfilter.ITransactionObject; -import ch.eitchnet.xmlpers.XmlPersistenceExecption; -import ch.eitchnet.xmlpers.XmlPersistenceHandler; -import ch.eitchnet.xmlpers.XmlPersistenceTransaction; -import ch.eitchnet.xmlpers.test.impl.MyClass; -import ch.eitchnet.xmlpers.test.impl.MyDaoFactory; +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.api.XmlPersistenceHandler; +import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; +import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; +import ch.eitchnet.xmlpers.test.model.Resource; /** * @author Robert von Burg @@ -45,6 +46,12 @@ public class XmlPersistenceTest { private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTest.class.getName()); + private static final String RES_TYPE = "@subType"; + private static final String RES_TYPE_INEXISTANT = "@inexistant"; + private static final String RES_NAME = "@name"; + private static final String RES_NAME_MODIFIED = "@name_modified"; + private static final String RES_ID = "@id"; + private static XmlPersistenceHandler persistenceHandler; /** @@ -60,13 +67,13 @@ public class XmlPersistenceTest { if (!basePathF.exists() && !basePathF.mkdirs()) Assert.fail("Could not create temporaray database store in " + basePathF.getAbsolutePath()); - System.setProperty(XmlPersistenceHandler.CONFIG_BASEPATH, "target/testdb"); - System.setProperty(XmlPersistenceHandler.CONFIG_VERBOSE, "true"); - System.setProperty(XmlPersistenceHandler.CONFIG_DAO_FACTORY_CLASS, MyDaoFactory.class.getName()); + Properties props = new Properties(); + props.setProperty(XmlPersistenceConstants.PROP_BASEPATH, "target/testdb"); + props.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); + props.setProperty(XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, TestModelDaoFactory.class.getName()); XmlPersistenceTest.persistenceHandler = new XmlPersistenceHandler(); - XmlPersistenceTest.persistenceHandler.initialize(); - + XmlPersistenceTest.persistenceHandler.initialize(props); XmlPersistenceTest.logger.info("Initialized persistence handler."); } catch (Exception e) { @@ -101,12 +108,12 @@ public class XmlPersistenceTest { XmlPersistenceTest.logger.info("Trying to create..."); // new instance - MyClass myClass = new MyClass("@id", "@name", "@subtype"); + Resource resource = new Resource(RES_ID, RES_NAME, RES_TYPE); // persist instance XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - tx.add(myClass); - XmlPersistenceTest.persistenceHandler.commitTx(); + tx.add(resource); + tx.commit(); XmlPersistenceTest.logger.info("Done creating."); @@ -121,11 +128,11 @@ public class XmlPersistenceTest { try { XmlPersistenceTest.logger.info("Trying to read..."); - // query MyClass with id @id + // query Resource with id @id XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); - XmlPersistenceTest.logger.info("Found MyClass: " + myClass); - XmlPersistenceTest.persistenceHandler.commitTx(); + Resource resource = tx.queryById(Resource.class.getName(), RES_TYPE, RES_ID); + XmlPersistenceTest.logger.info("Found Resource: " + resource); + tx.commit(); XmlPersistenceTest.logger.info("Done reading."); @@ -142,15 +149,15 @@ public class XmlPersistenceTest { // query the instance XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); - XmlPersistenceTest.logger.info("Found MyClass: " + myClass); + Resource resource = tx.queryById(Resource.class.getName(), RES_TYPE, RES_ID); + XmlPersistenceTest.logger.info("Found Resource: " + resource); // modify the instance - myClass.setName("@name_modified"); + resource.setName(RES_NAME_MODIFIED); // update the instance - tx.update(myClass); - XmlPersistenceTest.persistenceHandler.commitTx(); + tx.update(resource); + tx.commit(); XmlPersistenceTest.logger.info("Done updating."); @@ -166,39 +173,35 @@ public class XmlPersistenceTest { // query the instance XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); - XmlPersistenceTest.logger.info("Found MyClass: " + myClass); + Resource resource = tx.queryById(Resource.class.getName(), RES_TYPE, RES_ID); + XmlPersistenceTest.logger.info("Found Resource: " + resource); - tx.remove(myClass); - XmlPersistenceTest.persistenceHandler.commitTx(); + tx.remove(resource); + tx.commit(); XmlPersistenceTest.logger.info("Done removing."); } - /** - * - */ @Test public void testQueryFail() { + XmlPersistenceTransaction tx = null; try { XmlPersistenceTest.logger.info("Trying to query removed object..."); - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - MyClass myClass = tx.queryById(MyClass.class.getName(), "@subtype", "@id"); - XmlPersistenceTest.logger.info("Found MyClass: " + myClass); + tx = XmlPersistenceTest.persistenceHandler.openTx(); + Resource resource = tx.queryById(Resource.class.getName(), RES_TYPE, RES_ID); + XmlPersistenceTest.logger.info("Found Resource: " + resource); XmlPersistenceTest.logger.info("Done querying removed object"); - } catch (XmlPersistenceExecption e) { + } catch (XmlPersistenceException e) { Assert.assertEquals("Wrong error message. Expected error that object does not exist", - "No object exists for ch.eitchnet.xmlpers.test.impl.MyClass / @subtype / @id", + "No object exists for ch.eitchnet.xmlpers.test.impl.Resource / @subtype / @id", e.getLocalizedMessage()); } finally { - XmlPersistenceTest.persistenceHandler.commitTx(); + if (tx != null) + tx.commit(); } } - /** - * - */ @Test public void testReCreate() { @@ -206,12 +209,12 @@ public class XmlPersistenceTest { XmlPersistenceTest.logger.info("Trying to recreate..."); // new instance - MyClass myClass = new MyClass("@id", "@name", "@subtype"); + Resource resource = new Resource(RES_ID, RES_NAME, RES_TYPE); // persist instance XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - tx.add(myClass); - XmlPersistenceTest.persistenceHandler.commitTx(); + tx.add(resource); + tx.commit(); XmlPersistenceTest.logger.info("Done creating."); @@ -221,101 +224,94 @@ public class XmlPersistenceTest { } } - /** - * - */ @Test @Ignore public void testQueryFromTo() { Assert.fail("Not yet implemented"); } - /** - * - */ @Test public void testQueryAll() { + XmlPersistenceTransaction tx = null; try { XmlPersistenceTest.logger.info("Trying to query all..."); // query all - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - List list = tx.queryAll(MyClass.class.getName()); + tx = XmlPersistenceTest.persistenceHandler.openTx(); + List list = tx.queryAll(Resource.class.getName()); Assert.assertTrue("Expected only one object, found " + list.size(), list.size() == 1); // also with subtype - list = tx.queryAll(MyClass.class.getName(), "@subtype"); + list = tx.queryAll(Resource.class.getName(), RES_TYPE); Assert.assertTrue("Expected only one object, found " + list.size(), list.size() == 1); // and now something useless - list = tx.queryAll(MyClass.class.getName(), "@inexistant"); + list = tx.queryAll(Resource.class.getName(), RES_TYPE_INEXISTANT); Assert.assertTrue("Expected no objects, found " + list.size(), list.size() == 0); XmlPersistenceTest.logger.info("Done querying."); } finally { - XmlPersistenceTest.persistenceHandler.commitTx(); + if (tx != null) + tx.commit(); } } - /** - * - */ @Test public void testKeySet() { + XmlPersistenceTransaction tx = null; try { - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); + tx = XmlPersistenceTest.persistenceHandler.openTx(); - Set keySet = tx.queryKeySet(MyClass.class.getName()); + Set keySet = tx.queryKeySet(Resource.class.getName()); Assert.assertTrue("Expected one key, found " + keySet.size(), keySet.size() == 1); // also with subtype - keySet = tx.queryKeySet(MyClass.class.getName(), "@subtype"); + keySet = tx.queryKeySet(Resource.class.getName(), RES_TYPE); Assert.assertTrue("Expected one key, found " + keySet.size(), keySet.size() == 1); // and now something useless - keySet = tx.queryKeySet(MyClass.class.getName(), "@inexistant"); + keySet = tx.queryKeySet(Resource.class.getName(), RES_TYPE_INEXISTANT); Assert.assertTrue("Expected no keys, found " + keySet, keySet.size() == 0); } finally { - XmlPersistenceTest.persistenceHandler.commitTx(); + if (tx != null) + tx.commit(); } } - /** - * - */ @Test public void testRemoveAll() { + XmlPersistenceTransaction tx = null; try { - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); + tx = XmlPersistenceTest.persistenceHandler.openTx(); - List objects = tx.queryAll(MyClass.class.getName(), "@subType"); + List objects = tx.queryAll(Resource.class.getName(), RES_TYPE); tx.removeAll(objects); } finally { - XmlPersistenceTest.persistenceHandler.commitTx(); + if (tx != null) + tx.commit(); } } - /** - * - */ @Test public void testSize() { + XmlPersistenceTransaction tx = null; try { - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); + tx = XmlPersistenceTest.persistenceHandler.openTx(); - long size = tx.querySize(MyClass.class.getName(), "@subType"); + long size = tx.querySize(Resource.class.getName(), RES_TYPE); Assert.assertTrue("Expected size = 0, found: " + size, size == 0); } finally { - XmlPersistenceTest.persistenceHandler.commitTx(); + if (tx != null) + tx.commit(); } } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java new file mode 100644 index 000000000..54ec38957 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl; + +/** + * @author Robert von Burg + * + */ +public class Book { + + private final Long id; + private String title; + private String author; + private String press; + private double price; + + /** + * + */ + public Book(Long id) { + if (id == null) + throw new IllegalArgumentException("Id may not be null!"); + this.id = id; + } + + /** + * @return the id + */ + public Long getId() { + return this.id; + } + + /** + * @return the title + */ + public String getTitle() { + return this.title; + } + + /** + * @param title + * the title to set + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * @return the author + */ + public String getAuthor() { + return this.author; + } + + /** + * @param author + * the author to set + */ + public void setAuthor(String author) { + this.author = author; + } + + /** + * @return the press + */ + public String getPress() { + return this.press; + } + + /** + * @param press + * the press to set + */ + public void setPress(String press) { + this.press = press; + } + + /** + * @return the price + */ + public double getPrice() { + return this.price; + } + + /** + * @param price + * the price to set + */ + public void setPrice(double price) { + this.price = price; + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java new file mode 100644 index 000000000..26f1800d0 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl; + +import ch.eitchnet.xmlpers.impl.AbstractXmlDao; + +/** + * @author Robert von Burg + * + */ +public abstract class BookDao extends AbstractXmlDao { + + @Override + protected String getType() { + return Book.class.getSimpleName(); + } + + @Override + protected String getId(Book object) { + return object.getId().toString(); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java deleted file mode 100644 index a0c1d3e2c..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClass.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.test.impl; - -import ch.eitchnet.utils.objectfilter.ITransactionObject; - -/** - * @author Robert von Burg - * - */ -public class MyClass implements ITransactionObject { - - private long txId; - private String id; - private String name; - private String type; - - /** - * @param id - * @param name - * @param type - */ - public MyClass(String id, String name, String type) { - super(); - this.id = id; - this.name = name; - this.type = type; - } - - /** - * @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 name - */ - public String getName() { - return this.name; - } - - /** - * @param name - * the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * @return the type - */ - public String getType() { - return this.type; - } - - /** - * @param type - * the type to set - */ - public void setType(String type) { - this.type = type; - } - - /** - * @see ch.eitchnet.utils.objectfilter.ITransactionObject#setTransactionID(long) - */ - @Override - public void setTransactionID(long id) { - this.txId = id; - } - - /** - * @see ch.eitchnet.utils.objectfilter.ITransactionObject#getTransactionID() - */ - @Override - public long getTransactionID() { - return this.txId; - } - - /** - * @see ch.eitchnet.utils.objectfilter.ITransactionObject#resetTransactionID() - */ - @Override - public void resetTransactionID() { - this.txId = ITransactionObject.UNSET; - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/XmlSaxWriter.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyTypeResourceDao.java similarity index 83% rename from src/main/java/ch/eitchnet/xmlpers/XmlSaxWriter.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/MyTypeResourceDao.java index 8a8a80609..fef67ed53 100644 --- a/src/main/java/ch/eitchnet/xmlpers/XmlSaxWriter.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyTypeResourceDao.java @@ -19,13 +19,16 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers; - +package ch.eitchnet.xmlpers.test.impl; /** * @author Robert von Burg - * + * */ -public interface XmlSaxWriter { +public class MyTypeResourceDao extends ResourceDao { + @Override + public String getSubType() { + return "MyType"; + } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java new file mode 100644 index 000000000..3f7517370 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl; + +import ch.eitchnet.xmlpers.impl.AbstractXmlDao; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public abstract class ResourceDao extends AbstractXmlDao { + + @Override + public String getType() { + return Resource.class.getSimpleName(); + } + + @Override + public abstract String getSubType(); + + @Override + public String getId(Resource object) { + return object.getId(); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java new file mode 100644 index 000000000..2a267cbba --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.test.impl; + +import java.io.File; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public abstract class ResourceDomDao extends ResourceDao { + + @Override + protected Resource read(File filePath) { + // TODO Auto-generated method stub + return null; + } + + @Override + protected void write(Resource object, File filePath) { + // TODO Auto-generated method stub + + } + + public Element serializeToDom(Resource object, Document document) { + + Element element = document.createElement("Resource"); + + element.setAttribute("id", object.getId()); + element.setAttribute("type", object.getType()); + + Element nameElement = document.createElement("Name"); + element.appendChild(nameElement); + Text textNode = document.createTextNode(object.getName()); + nameElement.appendChild(textNode); + + return element; + } + + public Resource parseFromDom(Element element) { + + String id = element.getAttribute("id"); + String type = element.getAttribute("type"); + Element nameElement = (Element) element.getElementsByTagName("Name").item(0); + String name = nameElement.getTextContent(); + + Resource Resource = new Resource(id, name, type); + + return Resource; + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java similarity index 50% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java index 9e426d13a..c31e9018c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyClassDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java @@ -19,83 +19,29 @@ */ package ch.eitchnet.xmlpers.test.impl; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Text; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; -import ch.eitchnet.xmlpers.XmlDao; +import ch.eitchnet.xmlpers.test.model.Resource; /** * @author Robert von Burg * */ -public class MyClassDao implements XmlDao { +public class ResourceSaxDao extends ResourceDao { - @Override - public String getType(MyClass object) { - return MyClass.class.getName(); - } - - @Override - public String getSubType(MyClass object) { - return object.getType(); - } - - @Override - public String getId(MyClass object) { - return object.getId(); - } - - @Override - public Element serializeToDom(MyClass object, Document document) { - - Element element = document.createElement("MyClass"); - - element.setAttribute("id", object.getId()); - element.setAttribute("type", object.getType()); - - Element nameElement = document.createElement("Name"); - element.appendChild(nameElement); - Text textNode = document.createTextNode(object.getName()); - nameElement.appendChild(textNode); - - return element; - } - - /** - * @see ch.eitchnet.xmlpers.XmlDao#parseFromDom(org.w3c.dom.Element) - */ - @Override - public MyClass parseFromDom(Element element) { - - String id = element.getAttribute("id"); - String type = element.getAttribute("type"); - Element nameElement = (Element) element.getElementsByTagName("Name").item(0); - String name = nameElement.getTextContent(); - - MyClass myClass = new MyClass(id, name, type); - - return myClass; - } - - /** - * @see ch.eitchnet.xmlpers.XmlDao#serializeToSax(java.lang.Object, org.xml.sax.ContentHandler) - */ - @Override - public void serializeToSax(MyClass object, ContentHandler contentHandler) { + public void serializeToSax(Resource object, ContentHandler contentHandler) { try { contentHandler.startDocument(); - // MyClass element / root + // Resource element / root { AttributesImpl atts = new AttributesImpl(); atts.addAttribute("", "", "id", "", object.getId()); atts.addAttribute("", "", "type", "", object.getType()); - contentHandler.startElement("", "", "MyClass", atts); + contentHandler.startElement("", "", "Resource", atts); // name element { @@ -105,8 +51,8 @@ public class MyClassDao implements XmlDao { contentHandler.endElement("", "", "name"); } - // MyClass end - contentHandler.endElement("", "", "MyClass"); + // Resource end + contentHandler.endElement("", "", "Resource"); } // end document diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java new file mode 100644 index 000000000..abd7c9e45 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.test.impl; + +import java.util.Properties; + +import ch.eitchnet.xmlpers.api.XmlPersistenceDao; +import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; +import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class TestModelDaoFactory implements XmlPersistenceDaoFactory { + + @Override + public void initialize(XmlPersistenceFileDao fileDao, Properties properties) { + // TODO Auto-generated method stub + + } + + @Override + public XmlPersistenceDao createDaoInstance(T object) { + + if (object.getClass() != Resource.class) + throw new IllegalArgumentException("The object with class " + object.getClass() + " is not handled!"); + + Resource resource = (Resource) object; + String type = resource.getType(); + XmlPersistenceDao dao; + switch (type) { + case "MyType": + dao = new MyTypeResourceDao(); + break; + default: + throw new IllegalArgumentException("The resource with type " + type + " is not handled!"); + } + + // inject the DAO or SAX handler... + + @SuppressWarnings("unchecked") + XmlPersistenceDao xmlDao = (XmlPersistenceDao) dao; + return xmlDao; + } + + @Override + public XmlPersistenceDao createDaoInstance(String type) { + // TODO Auto-generated method stub + return null; + } + + @Override + public XmlPersistenceDao createDaoInstance(String type, String subType) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java new file mode 100644 index 000000000..99fa52403 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.model; + +public class Parameter { + + private String id; + private String name; + private String type; + private String value; + + /** + * + */ + public Parameter() { + // empty constructor + } + + /** + * @param id + * @param name + * @param type + * @param value + */ + public Parameter(String id, String name, String type, String value) { + super(); + this.id = id; + this.name = name; + this.type = type; + this.value = value; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Parameter [id="); + builder.append(this.id); + builder.append(", name="); + builder.append(this.name); + builder.append(", type="); + builder.append(this.type); + builder.append(", value="); + builder.append(this.value); + builder.append("]"); + return builder.toString(); + } + + /** + * @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 name + */ + public String getName() { + return this.name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the type + */ + public String getType() { + return this.type; + } + + /** + * @param type + * the type to set + */ + public void setType(String type) { + this.type = type; + } + + /** + * @return the value + */ + public String getValue() { + return this.value; + } + + /** + * @param value + * the value to set + */ + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java new file mode 100644 index 000000000..283b758d5 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.model; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class Resource { + + private String id; + private String name; + private String type; + private Map parameters = new HashMap(); + + /** + * + */ + public Resource() { + // empty constructor + } + + /** + * @param id + * @param name + * @param type + */ + public Resource(String id, String name, String type) { + super(); + this.id = id; + this.name = name; + this.type = type; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Resource [id="); + builder.append(this.id); + builder.append(", name="); + builder.append(this.name); + builder.append(", type="); + builder.append(this.type); + builder.append(", parameters="); + for (Entry param : this.parameters.entrySet()) { + builder.append("\n"); + builder.append(" " + param.getKey() + " = " + param.getValue()); + } + builder.append("]"); + return builder.toString(); + } + + /** + * @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 name + */ + public String getName() { + return this.name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the type + */ + public String getType() { + return this.type; + } + + /** + * @param type + * the type to set + */ + public void setType(String type) { + this.type = type; + } + + public void addParameter(Parameter parameter) { + this.parameters.put(parameter.getId(), parameter); + } + + public Set getParameterKeySet() { + return this.parameters.keySet(); + } + + public Parameter getParameterBy(String id) { + return this.parameters.get(id); + } +} \ No newline at end of file From 289cee3f51af698a6b3f83fac105473fff70d3d7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 14 Sep 2013 08:45:12 +0200 Subject: [PATCH 160/457] [Major] major rewrite, now implemented SAX and DOM methods. SAX works, DOM does not yet work. Simpler implementation now by not having to modify domain objects so they can be persisted. --- .../xmlpers/api/AbstractDaoFactory.java | 91 ++++ .../xmlpers/{impl => api}/AbstractXmlDao.java | 117 +++-- .../java/ch/eitchnet/xmlpers/api/DomUtil.java | 43 ++ .../ch/eitchnet/xmlpers/api/XmlIoMode.java} | 9 +- .../xmlpers/api/XmlPersistenceConstants.java | 8 +- .../xmlpers/api/XmlPersistenceDao.java | 6 - .../xmlpers/api/XmlPersistenceDaoFactory.java | 8 +- .../xmlpers/api/XmlPersistenceHandler.java | 90 +--- .../api/XmlPersistenceMetadataDao.java | 14 +- .../api/XmlPersistenceTransaction.java | 255 ++-------- .../eitchnet/xmlpers/impl/MetadataXmlDao.java | 53 +-- .../impl/TransactionDaoFactoryFacade.java | 107 +++++ .../impl/XmlPersistenceDomHandler.java | 18 +- .../xmlpers/impl/XmlPersistenceFileDao.java | 257 +++++++++-- .../impl/XmlPersistenceHandlerImpl.java | 90 ++++ .../impl/XmlPersistencePathBuilder.java | 173 ++++--- .../XmlPersistenceSaxHandler.java | 7 +- .../impl/XmlPersistenceTransactionImpl.java | 224 +++++++++ .../test/AbstractXmlPersistenceTest.java | 434 ++++++++++++++++++ .../xmlpers/test/XmlPersistenceDomTest.java | 51 ++ .../xmlpers/test/XmlPersistenceSaxTest.java | 49 ++ .../xmlpers/test/XmlPersistenceTest.java | 317 ------------- .../ch/eitchnet/xmlpers/test/impl/Book.java | 16 + .../eitchnet/xmlpers/test/impl/BookDao.java | 2 +- .../xmlpers/test/impl/BookDomDao.java | 89 ++++ .../xmlpers/test/impl/BookSaxDao.java | 113 +++++ .../xmlpers/test/impl/ResourceDao.java | 12 +- .../xmlpers/test/impl/ResourceDomDao.java | 73 ++- .../xmlpers/test/impl/ResourceSaxDao.java | 114 ++++- .../xmlpers/test/impl/TestConstants.java | 34 ++ .../test/impl/TestModelDaoFactory.java | 90 ++-- 31 files changed, 2030 insertions(+), 934 deletions(-) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java rename src/main/java/ch/eitchnet/xmlpers/{impl => api}/AbstractXmlDao.java (51%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/DomUtil.java rename src/{test/java/ch/eitchnet/xmlpers/test/impl/MyTypeResourceDao.java => main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java} (83%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/TransactionDaoFactoryFacade.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceHandlerImpl.java rename src/main/java/ch/eitchnet/xmlpers/{api => impl}/XmlPersistenceSaxHandler.java (93%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceSaxTest.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java new file mode 100644 index 000000000..09a21cc29 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.xmlpers.impl.MetadataXmlDao; +import ch.eitchnet.xmlpers.impl.XmlPersistenceDomHandler; +import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; +import ch.eitchnet.xmlpers.impl.XmlPersistenceSaxHandler; +import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; + +/** + * @author Robert von Burg + */ +public abstract class AbstractDaoFactory implements XmlPersistenceDaoFactory { + + private static final Logger logger = LoggerFactory.getLogger(AbstractDaoFactory.class); + + private XmlIoMode xmlIoMode; + private XmlPersistenceFileDao fileDao; + + @Override + public void initialize(XmlPersistenceFileDao fileDao, Properties properties) { + this.fileDao = fileDao; + // TODO catch and throw proper exception + String xmlIoModeS = PropertiesHelper.getProperty(properties, TestModelDaoFactory.class.getName(), + XmlPersistenceConstants.PROP_XML_IO_MOD, XmlIoMode.SAX.name()); + this.xmlIoMode = XmlIoMode.valueOf(xmlIoModeS.toUpperCase()); + logger.info("Defaut Xml IO Mode is " + this.xmlIoMode.name()); + } + + /** + * @return + * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getMetadataDao() + */ + @Override + public XmlPersistenceMetadataDao getMetadataDao() { + MetadataXmlDao metadataDao = new MetadataXmlDao(); + metadataDao.initialize(this.fileDao); + return metadataDao; + } + + protected XmlIoMode getXmlIoMode() { + return this.xmlIoMode; + } + + protected XmlPersistenceDao initializeDao(XmlPersistenceDao dao) { + if (!(dao instanceof AbstractXmlDao)) { + throw new IllegalArgumentException("Your dao implementation does not extend from " + + AbstractXmlDao.class.getName() + "!"); + } + AbstractXmlDao abstractXmlDao = (AbstractXmlDao) dao; + abstractXmlDao.initialize(this.fileDao, getXmlFileHandler(this.xmlIoMode)); + return dao; + } + + protected XmlPersistenceFileHandler getXmlFileHandler(XmlIoMode ioMode) { + switch (ioMode) { + case DOM: + return new XmlPersistenceDomHandler(); + case SAX: + return new XmlPersistenceSaxHandler(); + default: + throw new IllegalArgumentException("The XmlIoMode " + ioMode + " is not yet supported!"); + } + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/AbstractXmlDao.java b/src/main/java/ch/eitchnet/xmlpers/api/AbstractXmlDao.java similarity index 51% rename from src/main/java/ch/eitchnet/xmlpers/impl/AbstractXmlDao.java rename to src/main/java/ch/eitchnet/xmlpers/api/AbstractXmlDao.java index 7d4491855..321958b0c 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/AbstractXmlDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/AbstractXmlDao.java @@ -19,16 +19,16 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.impl; +package ch.eitchnet.xmlpers.api; import java.io.File; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Set; import ch.eitchnet.utils.exceptions.XmlException; -import ch.eitchnet.xmlpers.api.XmlPersistenceDao; -import ch.eitchnet.xmlpers.api.XmlPersistenceFileHandler; +import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; /** * @author Robert von Burg @@ -40,17 +40,23 @@ public abstract class AbstractXmlDao implements XmlPersistenceDao { private XmlPersistenceFileHandler fileHandler; // TODO think about setting some methods to final - // TODO maybe decouple the write and load method into their own class // TODO if no sub type is given, then don't search recursively - it means subType does not exist - @Override - public void setXmlPersistenceFileDao(XmlPersistenceFileDao fileDao) { + void initialize(XmlPersistenceFileDao fileDao, XmlPersistenceFileHandler fileHandler) { + if (fileDao == null || fileHandler == null) + throw new IllegalArgumentException("Neither fileDao nor fileHandler may be null!"); + if (this.fileDao != null) + throw new IllegalStateException("DAO is already initialized!"); this.fileDao = fileDao; + this.fileHandler = fileHandler; } - @Override - public void setXmlPersistenceFileHandler(XmlPersistenceFileHandler fileHandler) { - this.fileHandler = fileHandler; + protected XmlPersistenceFileDao getXmlPersistenceFileDao() { + return this.fileDao; + } + + protected XmlPersistenceFileHandler getXmlPersistenceFileHandler() { + return this.fileHandler; } /** @@ -69,45 +75,69 @@ public abstract class AbstractXmlDao implements XmlPersistenceDao { @Override public void remove(T object) { - this.fileDao.remove(getType(), getSubType(), getId(object)); + if (getSubType() == null) + this.fileDao.remove(getType(), getId(object)); + else + this.fileDao.remove(getType(), getSubType(), getId(object)); } @Override public void removeById(String id) { - this.fileDao.remove(getType(), getSubType(), id); + if (getSubType() == null) + this.fileDao.remove(getType(), id); + else + this.fileDao.remove(getType(), getSubType(), id); } @Override public void removeAll() { - this.fileDao.removeAll(getType(), getSubType()); + if (getSubType() == null) + this.fileDao.removeAll(getType()); + else + this.fileDao.removeAll(getType(), getSubType()); } @Override public Set queryKeySet() { + if (getSubType() == null) + return this.fileDao.queryKeySet(getType()); return this.fileDao.queryKeySet(getType(), getSubType()); } @Override public long querySize() { + if (getSubType() == null) + return this.fileDao.querySize(getType()); return this.fileDao.querySize(getType(), getSubType()); } @Override public List queryAll() { - File queryPath = this.fileDao.getPathBuilder().getPath(getType(), getSubType()); - String msg = "Can not read persistence unit for {0} / {1} at {2}"; - return queryAll(queryPath, msg, getType(), getSubType(), queryPath); - } - private List queryAll(File queryPath, String errorMsg, Object... msgArgs) { - File[] persistenceUnits = queryPath.listFiles(); - List result = new ArrayList<>(); - for (File persistenceUnit : persistenceUnits) { - if (!persistenceUnit.isFile()) - continue; + if (getSubType() == null) { - assertReadable(persistenceUnit, errorMsg, msgArgs); - T object = read(persistenceUnit); + Set idsByType = this.fileDao.queryKeySet(getType()); + List result = new ArrayList<>(idsByType.size()); + + for (String id : idsByType) { + File objectPath = this.fileDao.getReadPath(getType(), id); + String msg = "Can not read persistence units for {0} / {1} at {2}"; + assertReadable(objectPath, msg, getType(), id, objectPath); + T object = read(objectPath); + result.add(object); + } + + return result; + } + + Set idsByType = this.fileDao.queryKeySet(getType(), getSubType()); + List result = new ArrayList<>(idsByType.size()); + + for (String id : idsByType) { + File objectPath = this.fileDao.getReadPath(getType(), getSubType(), id); + String msg = "Can not read persistence units for {0} / {1} / {2} at {3}"; + assertReadable(objectPath, msg, getType(), getSubType(), id, objectPath); + T object = read(objectPath); result.add(object); } @@ -116,36 +146,51 @@ public abstract class AbstractXmlDao implements XmlPersistenceDao { private void assertReadable(File persistenceUnit, String errorMsg, Object... msgArgs) { if (!persistenceUnit.canRead()) { - String msg = String.format(errorMsg, msgArgs); + String msg = MessageFormat.format(errorMsg, msgArgs); throw new XmlException(msg); } } @Override public T queryById(String id) { - File persistenceUnit = this.fileDao.getPathBuilder().getPath(getType(), getSubType(), id); - String msg = "Can not read persistence unit for {0} / {1} / {2} at {3}"; - return queryById(persistenceUnit, msg, getType(), getSubType(), id, persistenceUnit); - } + if (getSubType() == null) { + File persistenceUnit = this.fileDao.getReadPath(getType(), id); + if (!persistenceUnit.exists()) + return null; + String msg = "Can not read persistence unit for {0} / {1} at {2}"; + assertReadable(persistenceUnit, msg, getType(), id, persistenceUnit); + T object = read(persistenceUnit); + return object; + } - private T queryById(File persistenceUnit, String msg, Object... msgArgs) { - assertReadable(persistenceUnit, msg, msgArgs); + File persistenceUnit = this.fileDao.getReadPath(getType(), getSubType(), id); + if (!persistenceUnit.exists()) + return null; + String msg = "Can not read persistence unit for {0} / {1} / {2} at {3}"; + assertReadable(persistenceUnit, msg, getType(), getSubType(), id, persistenceUnit); T object = read(persistenceUnit); return object; } @Override public void add(T object) { - XmlPersistencePathBuilder pathBuilder = this.fileDao.getPathBuilder(); - File addPath = pathBuilder.getAddPath(getType(), getSubType(), getId(object)); + + File addPath; + if (getSubType() == null) + addPath = this.fileDao.getAddPath(getType(), getId(object)); + else + addPath = this.fileDao.getAddPath(getType(), getSubType(), getId(object)); write(object, addPath); } @Override public void update(T object) { - XmlPersistencePathBuilder pathBuilder = this.fileDao.getPathBuilder(); - File updatePath = pathBuilder.getUpdatePath(getType(), getSubType(), getId(object)); - write(object, updatePath); + File addPath; + if (getSubType() == null) + addPath = this.fileDao.getUpdatePath(getType(), getId(object)); + else + addPath = this.fileDao.getUpdatePath(getType(), getSubType(), getId(object)); + write(object, addPath); } /** diff --git a/src/main/java/ch/eitchnet/xmlpers/api/DomUtil.java b/src/main/java/ch/eitchnet/xmlpers/api/DomUtil.java new file mode 100644 index 000000000..54959a342 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/DomUtil.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** + * @author Robert von Burg + * + */ +public class DomUtil { + + public static DocumentBuilder createDocumentBuilder() { + try { + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + return docBuilder; + } catch (ParserConfigurationException e) { + throw new XmlPersistenceException("No Xml Parser could be loaded: " + e.getMessage(), e); + } + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyTypeResourceDao.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java similarity index 83% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/MyTypeResourceDao.java rename to src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java index fef67ed53..e0a89f617 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyTypeResourceDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java @@ -19,16 +19,13 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl; +package ch.eitchnet.xmlpers.api; /** * @author Robert von Burg * */ -public class MyTypeResourceDao extends ResourceDao { +public enum XmlIoMode { - @Override - public String getSubType() { - return "MyType"; - } + DOM, SAX; } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java index 050c9789a..3fd3266e4 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java @@ -27,7 +27,9 @@ package ch.eitchnet.xmlpers.api; */ public class XmlPersistenceConstants { - public static final String PROP_VERBOSE = "ch.eitchnet.xmlpers.verbose"; - public static final String PROP_BASEPATH = "ch.eitchnet.xmlpers.basepath"; - public static final String PROP_DAO_FACTORY_CLASS = "ch.eitchnet.xmlpers.daoFactoryClass"; + private static final String PROP_PREFIX = "ch.eitchnet.xmlpers."; + public static final String PROP_VERBOSE = "ch.eitchnet.xmlpers." + "verbose"; + public static final String PROP_BASEPATH = "ch.eitchnet.xmlpers." + "basePath"; + public static final String PROP_DAO_FACTORY_CLASS = "ch.eitchnet.xmlpers." + "daoFactoryClass"; + public static final String PROP_XML_IO_MOD = PROP_PREFIX + "ioMode"; } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java index e034d9c76..8923211fc 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java @@ -22,17 +22,11 @@ package ch.eitchnet.xmlpers.api; import java.util.List; import java.util.Set; -import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; - /** * @author Robert von Burg */ public interface XmlPersistenceDao { - public void setXmlPersistenceFileDao(XmlPersistenceFileDao fileDao); - - public void setXmlPersistenceFileHandler(XmlPersistenceFileHandler fileHandler); - public void add(T object); public void update(T object); diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java index 76ac22cad..4b5ac1782 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java @@ -30,11 +30,11 @@ public interface XmlPersistenceDaoFactory { public void initialize(XmlPersistenceFileDao fileDao, Properties properties); - public XmlPersistenceMetadataDao getMetadataDao(); - + public XmlPersistenceMetadataDao getMetadataDao(); + public XmlPersistenceDao getDao(T object); - public XmlPersistenceDao getDao(String type); + public XmlPersistenceDao getDaoBy(String type); - public XmlPersistenceDao getDao(String type, String subType); + public XmlPersistenceDao getDaoBy(String type, String subType); } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java index 8a1106aa5..9f814a05a 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java @@ -1,83 +1,31 @@ /* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers + * Copyright (c) 2012, Robert von Burg * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * All rights reserved. * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * This file is part of the XXX. * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . */ package ch.eitchnet.xmlpers.api; -import java.util.Properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.PropertiesHelper; -import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; -import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; - /** * @author Robert von Burg * */ -public class XmlPersistenceHandler { +public interface XmlPersistenceHandler { - protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); - - protected boolean initialized; - protected boolean verbose; - protected XmlPersistenceDaoFactory daoFactory; - - public void initialize(Properties properties) { - if (this.initialized) - throw new IllegalStateException("Already initialized!"); - - // get properties - String context = XmlPersistenceHandler.class.getSimpleName(); - boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, - Boolean.FALSE).booleanValue(); - String daoFactoryClassName = PropertiesHelper.getProperty(properties, context, - XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, null); - - // load dao factory - XmlPersistenceDaoFactory daoFactory; - try { - @SuppressWarnings("unchecked") - Class xmlDaoFactoryClass = (Class) Class.forName(daoFactoryClassName); - - daoFactory = xmlDaoFactoryClass.newInstance(); - - } catch (ClassNotFoundException e) { - throw new XmlPersistenceException("XmlDaoFactory class does not exist " + daoFactoryClassName, e); - } catch (Exception e) { - throw new XmlPersistenceException("Failed to load class " + daoFactoryClassName, e); - } - - // initialize the dao factory - XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(properties); - XmlPersistenceFileDao fileDao = new XmlPersistenceFileDao(pathBuilder, properties); - daoFactory.initialize(fileDao, properties); - - this.daoFactory = daoFactory; - this.verbose = verbose; - } - - public XmlPersistenceTransaction openTx() { - - XmlPersistenceTransaction tx = new XmlPersistenceTransaction(this.daoFactory, this.verbose); - XmlPersistenceTransaction.setTx(tx); - return tx; - } -} + public abstract XmlPersistenceTransaction openTx(); +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java index 36039f7dd..4f8e74b40 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java @@ -21,26 +21,22 @@ package ch.eitchnet.xmlpers.api; import java.util.Set; -import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; - /** * @author Robert von Burg */ public interface XmlPersistenceMetadataDao { - public void setXmlPersistenceFileDao(XmlPersistenceFileDao fileDao); + public Set queryTypeSet(); - public void setXmlPersistenceFileHandler(XmlPersistenceFileHandler fileHandler); - - public void removeAll(); - - public Set queryKeySet(); + public Set queryTypeSet(String type); public Set queryKeySet(String type); public Set queryKeySet(String type, String subType); - public long querySize(); + public long queryTypeSize(); + + public long querySubTypeSize(String type); public long querySize(String type); diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java index 1e5e9151f..ae6545d15 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java @@ -1,267 +1,76 @@ /* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers + * Copyright (c) 2012, Robert von Burg * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * All rights reserved. * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * This file is part of the XXX. * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . */ package ch.eitchnet.xmlpers.api; import java.util.List; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.objectfilter.ObjectFilter; /** * @author Robert von Burg + * */ -public class XmlPersistenceTransaction { - - private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTransaction.class); - - private static final ThreadLocal TX_THREADLOCAL_THREAD_LOCAL; - static { - TX_THREADLOCAL_THREAD_LOCAL = new ThreadLocal<>(); - } - - private final XmlPersistenceDaoFactory daoFactory; - private final boolean verbose; - private final ObjectFilter objectFilter; - - /** - * @param verbose - */ - public XmlPersistenceTransaction(XmlPersistenceDaoFactory daoFactory, boolean verbose) { - this.daoFactory = daoFactory; - this.verbose = verbose; - this.objectFilter = new ObjectFilter(); - } - - /* - * modifying methods - */ +public interface XmlPersistenceTransaction { /** * @param object */ - public void add(T object) { - this.objectFilter.add(object); - } + public abstract void add(T object); /** * @param objects */ - @SuppressWarnings("unchecked") - public void addAll(List objects) { - this.objectFilter.addAll((List) objects); - } + public abstract void addAll(List objects); /** * @param object */ - public void update(T object) { - this.objectFilter.update(object); - } + public abstract void update(T object); /** * @param objects */ - @SuppressWarnings("unchecked") - public void updateAll(List objects) { - this.objectFilter.updateAll((List) objects); - } + public abstract void updateAll(List objects); /** * @param object */ - public void remove(T object) { - this.objectFilter.remove(object); - } + public abstract void remove(T object); /** * @param objects */ - @SuppressWarnings("unchecked") - public void removeAll(List objects) { - this.objectFilter.removeAll((List) objects); - } - - /* - * querying methods - */ + public abstract void removeAll(List objects); /** - * @param type - * @return + * @return the daoFactory */ - public Set queryKeySet() { - return this.daoFactory.getMetadataDao().queryKeySet(); - } - - /** - * @param type - * @return - */ - public Set queryKeySet(String type) { - return this.daoFactory.getMetadataDao().queryKeySet(type); - } - - /** - * @param type - * @param subType - * @return - */ - public Set queryKeySet(String type, String subType) { - return this.daoFactory.getMetadataDao().queryKeySet(type, subType); - } - - /** - * @param type - * @return - */ - public long querySize(String type) { - return querySize(type, null); - } - - /** - * @param type - * @param subType - * @return - */ - public long querySize(String type, String subType) { - return this.daoFactory.getDao(type, subType).querySize(); - } - - /** - * @param type - * @return - */ - public List queryAll(String type) { - XmlPersistenceDao dao = this.daoFactory.getDao(type); - List objects = dao.queryAll(type); - return objects; - } - - /** - * @param type - * @param subType - * @return - */ - public List queryAll(String type, String subType) { - XmlPersistenceDao dao = this.daoFactory.getDao(type, subType); - List objects = dao.queryAll(type, subType); - return objects; - } - - /** - * @param type - * @param id - * @return - */ - public T queryById(String type, String id) { - return queryById(type, id); - } - - /** - * @param type - * @param subType - * @param id - * @return - */ - public T queryById(String type, String subType, String id) { - XmlPersistenceDao dao = this.daoFactory.getDao(type, subType); - T object = dao.queryById(id); - return object; - } + public abstract XmlPersistenceDaoFactory getDaoFactory(); /** * */ - public void commit() { + public abstract void commit(); - if (this.verbose) - XmlPersistenceTransaction.logger.info("Committing TX..."); - - Set keySet = this.objectFilter.keySet(); - if (keySet.isEmpty()) - return; - - for (String key : keySet) { - - List removed = this.objectFilter.getRemoved(key); - if (removed.isEmpty()) { - if (this.verbose) - XmlPersistenceTransaction.logger.info("No objects removed in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransaction.logger.info(removed.size() + " objects removed in this tx."); - - for (Object object : removed) { - XmlPersistenceDao dao = this.daoFactory.getDao(object); - dao.remove(object); - } - } - - List updated = this.objectFilter.getUpdated(key); - if (updated.isEmpty()) { - if (this.verbose) - XmlPersistenceTransaction.logger.info("No objects updated in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransaction.logger.info(updated.size() + " objects updated in this tx."); - - for (Object object : updated) { - - XmlPersistenceDao dao = this.daoFactory.getDao(object); - dao.update(object); - } - } - - List added = this.objectFilter.getAdded(key); - if (added.isEmpty()) { - if (this.verbose) - XmlPersistenceTransaction.logger.info("No objects added in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransaction.logger.info(added.size() + " objects added in this tx."); - - for (Object object : added) { - - XmlPersistenceDao dao = this.daoFactory.getDao(object); - dao.add(object); - } - } - } - - this.objectFilter.clearCache(); - XmlPersistenceTransaction.logger.info("Completed TX"); - } - - public static XmlPersistenceTransaction getTx() { - XmlPersistenceTransaction tx = TX_THREADLOCAL_THREAD_LOCAL.get(); - if (tx == null) - throw new IllegalStateException("No transaction is currently open!"); - return tx; - } - - public static void setTx(XmlPersistenceTransaction tx) { - if (TX_THREADLOCAL_THREAD_LOCAL.get() != null) - throw new IllegalStateException("A transaction is already open!"); - TX_THREADLOCAL_THREAD_LOCAL.set(tx); - } -} + /** + * + */ + public abstract void clear(); +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java b/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java index 019a2ad71..80dbd6317 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java @@ -23,53 +23,39 @@ package ch.eitchnet.xmlpers.impl; import java.util.Set; -import ch.eitchnet.xmlpers.api.XmlPersistenceFileHandler; import ch.eitchnet.xmlpers.api.XmlPersistenceMetadataDao; /** * @author Robert von Burg * */ -public abstract class MetadataXmlDao implements XmlPersistenceMetadataDao { +public class MetadataXmlDao implements XmlPersistenceMetadataDao { private XmlPersistenceFileDao fileDao; - private XmlPersistenceFileHandler fileHandler; - // TODO think about setting some methods to final - // TODO maybe decouple the write and load method into their own class - - @Override - public void setXmlPersistenceFileDao(XmlPersistenceFileDao fileDao) { + public void initialize(XmlPersistenceFileDao fileDao) { + if (fileDao == null) + throw new IllegalArgumentException("fileDao may not be null!"); + if (this.fileDao != null) + throw new IllegalStateException("DAO is already initialized!"); this.fileDao = fileDao; } - @Override - public void setXmlPersistenceFileHandler(XmlPersistenceFileHandler fileHandler) { - this.fileHandler = fileHandler; - } - - /** - * @return the fileHandler - */ - protected XmlPersistenceFileHandler getFileHandler() { - return this.fileHandler; - } - /** * @return the fileDao */ protected XmlPersistenceFileDao getFileDao() { return this.fileDao; } - + @Override - public void removeAll() { - this.fileDao.removeAll(); + public Set queryTypeSet() { + return this.fileDao.queryTypeSet(); } @Override - public Set queryKeySet(String type, String subType) { - return this.fileDao.queryKeySet(type, subType); + public Set queryTypeSet(String type) { + return this.fileDao.queryTypeSet(type); } @Override @@ -78,13 +64,18 @@ public abstract class MetadataXmlDao implements XmlPersistenceMetadataDao { } @Override - public Set queryKeySet() { - return this.fileDao.queryKeySet(); + public Set queryKeySet(String type, String subType) { + return this.fileDao.queryKeySet(type, subType); } @Override - public long querySize(String type, String subType) { - return this.fileDao.querySize(type, subType); + public long queryTypeSize() { + return this.fileDao.queryTypeSize(); + } + + @Override + public long querySubTypeSize(String type) { + return this.fileDao.queryTypeSize(type); } @Override @@ -93,7 +84,7 @@ public abstract class MetadataXmlDao implements XmlPersistenceMetadataDao { } @Override - public long querySize() { - return this.fileDao.querySize(); + public long querySize(String type, String subType) { + return this.fileDao.querySize(type, subType); } } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/TransactionDaoFactoryFacade.java b/src/main/java/ch/eitchnet/xmlpers/impl/TransactionDaoFactoryFacade.java new file mode 100644 index 000000000..3aa95a8ec --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/TransactionDaoFactoryFacade.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.impl; + +import java.util.Properties; + +import ch.eitchnet.xmlpers.api.XmlPersistenceDao; +import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.api.XmlPersistenceMetadataDao; + +/** + * @author Robert von Burg + * + */ +public class TransactionDaoFactoryFacade implements XmlPersistenceDaoFactory { + + private XmlPersistenceDaoFactory daoFactory; + private XmlPersistenceTransactionImpl tx; + + TransactionDaoFactoryFacade(XmlPersistenceDaoFactory daoFactory) { + this.daoFactory = daoFactory; + } + + void setTx(XmlPersistenceTransactionImpl tx) { + this.tx = tx; + } + + /** + * @throws UnsupportedOperationException + * as this method may not be called on the facade + */ + @Override + public void initialize(XmlPersistenceFileDao fileDao, Properties properties) throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + /** + * @return + * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getMetadataDao() + */ + @Override + public XmlPersistenceMetadataDao getMetadataDao() { + assertTxOpen(); + return this.daoFactory.getMetadataDao(); + } + + /** + * @param object + * @return + * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getDao(java.lang.Object) + */ + @Override + public XmlPersistenceDao getDao(T object) { + assertTxOpen(); + return this.daoFactory.getDao(object); + } + + /** + * @param type + * @return + * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getDaoBy(java.lang.String) + */ + @Override + public XmlPersistenceDao getDaoBy(String type) { + assertTxOpen(); + return this.daoFactory.getDaoBy(type); + } + + /** + * @param type + * @param subType + * @return + * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getDaoBy(java.lang.String, java.lang.String) + */ + @Override + public XmlPersistenceDao getDaoBy(String type, String subType) { + assertTxOpen(); + return this.daoFactory.getDaoBy(type, subType); + } + + private void assertTxOpen() { + if (this.tx.isCleared()) { + throw new XmlPersistenceException( + "The transaction has already been closed, thus no operation may be performed anymore with this dao factory instance"); + } + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java index 102c7738a..30484e387 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java @@ -25,8 +25,6 @@ import java.io.File; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; @@ -43,6 +41,7 @@ import org.xml.sax.SAXException; import ch.eitchnet.utils.exceptions.XmlException; import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.xmlpers.api.DomUtil; import ch.eitchnet.xmlpers.api.XmlPersistenceContextData; import ch.eitchnet.xmlpers.api.XmlPersistenceDomContextData; import ch.eitchnet.xmlpers.api.XmlPersistenceException; @@ -56,12 +55,6 @@ public class XmlPersistenceDomHandler implements XmlPersistenceFileHandler { private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceDomHandler.class); - public DocumentBuilder createDocumentBuilder() throws ParserConfigurationException { - DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); - return docBuilder; - } - @Override public void read(XmlPersistenceContextData contextData) { @@ -72,11 +65,11 @@ public class XmlPersistenceDomHandler implements XmlPersistenceFileHandler { throw new IllegalStateException("No file has been set on the context data!"); try { - DocumentBuilder docBuilder = createDocumentBuilder(); + DocumentBuilder docBuilder = DomUtil.createDocumentBuilder(); File file = cd.getFile(); Document document = docBuilder.parse(file); cd.setDocument(document); - } catch (ParserConfigurationException | SAXException | IOException e) { + } catch (SAXException | IOException e) { throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); } @@ -119,12 +112,11 @@ public class XmlPersistenceDomHandler implements XmlPersistenceFileHandler { // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); // Transform to file - File file = new File("target/res_dom.xml"); - StreamResult result = new StreamResult(file); + StreamResult result = new StreamResult(cd.getFile()); Source xmlSource = new DOMSource(document); transformer.transform(xmlSource, result); - logger.info("Wrote DOM to " + file.getAbsolutePath()); + logger.info("Wrote DOM to " + cd.getFile().getAbsolutePath()); } catch (TransformerFactoryConfigurationError | TransformerException e) { throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java index 4697d8e58..58ec57411 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java @@ -21,6 +21,7 @@ package ch.eitchnet.xmlpers.impl; import java.io.File; import java.text.MessageFormat; +import java.util.Collections; import java.util.HashSet; import java.util.Properties; import java.util.Set; @@ -28,7 +29,6 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.utils.helper.PropertiesHelper; import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; import ch.eitchnet.xmlpers.api.XmlPersistenceException; @@ -56,52 +56,98 @@ public class XmlPersistenceFileDao { } /** - * @return the pathBuilder + * Does a recursive and complete deletion of all objects, types and sub-types */ - public XmlPersistencePathBuilder getPathBuilder() { - return this.pathBuilder; - } - public void removeAll() { - File removePath = this.pathBuilder.getRemovePath(); - String failMsg = "Deletion of persistence units at {1} failed! Check file permissions!"; - remove(removePath, failMsg, removePath); + Set types = queryTypeSet(); + for (String type : types) { + + Set idsByType = queryKeySet(type); + for (String id : idsByType) { + remove(type, id); + } + + Set subTypes = queryTypeSet(type); + for (String subType : subTypes) { + + Set idsBySubType = queryKeySet(type, subType); + for (String id : idsBySubType) { + remove(type, subType, id); + } + } + } } - + public void removeAll(String type) { - File removePath = this.pathBuilder.getRemovePath(type); - String failMsg = "Deletion of persistence units for {0} at {1} failed! Check file permissions!"; - remove(removePath, failMsg, type, removePath); + Set idsByType = queryKeySet(type); + for (String id : idsByType) { + remove(type, id); + } + + Set subTypes = queryTypeSet(type); + for (String subType : subTypes) { + + Set idsBySubType = queryKeySet(type, subType); + for (String id : idsBySubType) { + remove(type, subType, id); + } + } } public void removeAll(String type, String subType) { - File removePath = this.pathBuilder.getRemovePath(type, subType); - String failMsg = "Deletion of persistence units for {0} / {1} at {2} failed! Check file permissions!"; - remove(removePath, failMsg, type, subType, removePath); + Set idsBySubType = queryKeySet(type, subType); + for (String id : idsBySubType) { + remove(type, subType, id); + } } public void remove(String type, String id) { File removePath = this.pathBuilder.getRemovePath(type, id); String failMsg = "Deletion of persistence units for {0} / {1} at {2} failed! Check file permissions!"; remove(removePath, failMsg, type, id, removePath); + + // if no more objects with this type exist, then delete the path + failMsg = "Deletion of empty directory for {0} at {2} failed! Check file permissions!"; + File parentFile = removePath.getParentFile(); + deleteEmptyDirectory(parentFile, failMsg, type, parentFile); } public void remove(String type, String subType, String id) { File removePath = this.pathBuilder.getRemovePath(type, subType, id); String failMsg = "Deletion of persistence units for {0} / {1} / {2} at {3} failed! Check file permissions!"; remove(removePath, failMsg, type, subType, id, removePath); + + // if no more objects with this subType exist, then delete the path + failMsg = "Deletion of empty directory for {0} / {1} at {2} failed! Check file permissions!"; + File parentFile = removePath.getParentFile(); + deleteEmptyDirectory(parentFile, failMsg, type, subType, parentFile); } - private void remove(File removePath, String failMsg, Object... msgParts) { - File[] removePaths = new File[] { removePath }; - boolean removed = FileHelper.deleteFiles(removePaths, this.verbose); - if (!removed) { - String msg = MessageFormat.format(failMsg, msgParts); - throw new XmlPersistenceException(msg); - } + public File getAddPath(String type, String id) { + return this.pathBuilder.getAddPath(type, id); + } + + public File getAddPath(String type, String subType, String id) { + return this.pathBuilder.getAddPath(type, subType, id); + } + + public File getReadPath(String type, String id) { + return this.pathBuilder.getReadPath(type, id); + } + + public File getReadPath(String type, String subType, String id) { + return this.pathBuilder.getReadPath(type, subType, id); + } + + public File getUpdatePath(String type, String id) { + return this.pathBuilder.getUpdatePath(type, id); + } + + public File getUpdatePath(String type, String subType, String id) { + return this.pathBuilder.getUpdatePath(type, subType, id); } /** @@ -109,9 +155,9 @@ public class XmlPersistenceFileDao { * * @return */ - public Set queryKeySet() { + public Set queryTypeSet() { File queryPath = this.pathBuilder.getQueryPath(); - Set keySet = queryKeySet(queryPath); + Set keySet = queryTypeSet(queryPath); if (this.verbose) XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " types"); return keySet; @@ -120,6 +166,19 @@ public class XmlPersistenceFileDao { /** * Returns the set of sub types for the given type * + * @return + */ + public Set queryTypeSet(String type) { + File queryPath = this.pathBuilder.getQueryPath(type); + Set keySet = queryTypeSet(queryPath); + if (this.verbose) + XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " subTypes of type " + type); + return keySet; + } + + /** + * Returns the object ids for the given type + * * @param type * * @return @@ -133,7 +192,7 @@ public class XmlPersistenceFileDao { } /** - * Returns the set of ids for the objects with the give type and sub type + * Returns the object ids for the give type and sub type * * @param type * @param subType @@ -148,30 +207,22 @@ public class XmlPersistenceFileDao { return keySet; } - /** - * @param queryPath - * @return - */ - private Set queryKeySet(File queryPath) { - Set keySet = new HashSet(); - - File[] subTypeFiles = queryPath.listFiles(); - for (File subTypeFile : subTypeFiles) { - if (subTypeFile.isFile()) - keySet.add(this.pathBuilder.getId(subTypeFile.getName())); - } - - return keySet; - } - - public long querySize() { + public long queryTypeSize() { File queryPath = this.pathBuilder.getQueryPath(); - long numberOfFiles = querySize(queryPath); + long numberOfFiles = queryTypeSize(queryPath); if (this.verbose) XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " types"); return numberOfFiles; } + public long queryTypeSize(String type) { + File queryPath = this.pathBuilder.getQueryPath(type); + long numberOfFiles = queryTypeSize(queryPath); + if (this.verbose) + XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " elements for " + type); + return numberOfFiles; + } + public long querySize(String type) { File queryPath = this.pathBuilder.getQueryPath(type); long numberOfFiles = querySize(queryPath); @@ -188,7 +239,99 @@ public class XmlPersistenceFileDao { return numberOfFiles; } + /** + * Returns the types, i.e. directories in the given query path + * + * @param queryPath + * the path for which the types should be gathered + * + * @return a set of types in the given query path + */ + private Set queryTypeSet(File queryPath) { + Set keySet = new HashSet(); + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + if (subTypeFile.isFile()) { + String filename = subTypeFile.getName(); + String id = this.pathBuilder.getId(filename); + keySet.add(id); + } + } + + return keySet; + } + + /** + * Returns the ids of all objects in the given query path, i.e. the id part of all the files in the given query path + * + * @param queryPath + * the path for which the ids should be gathered + * + * @return a set of ids for the objects in the given query path + */ + private Set queryKeySet(File queryPath) { + if (!queryPath.exists()) + return Collections.emptySet(); + if (!queryPath.isDirectory()) + throw new IllegalArgumentException("The path is not a directory, thus can not query key set for it: " + + queryPath.getAbsolutePath()); + + Set keySet = new HashSet(); + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + if (subTypeFile.isFile()) { + String filename = subTypeFile.getName(); + String id = this.pathBuilder.getId(filename); + keySet.add(id); + } + } + + return keySet; + } + + /** + * Returns the number of all types, i.e. directories in the given query path + * + * @param queryPath + * the path in which to count the types + * + * @return the number of types in the given query path + */ + private long queryTypeSize(File queryPath) { + if (!queryPath.exists()) + return 0L; + if (!queryPath.isDirectory()) + throw new IllegalArgumentException("The path is not a directory, thus can not query type size for it: " + + queryPath.getAbsolutePath()); + + long numberOfFiles = 0l; + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + + if (subTypeFile.isDirectory()) + numberOfFiles++; + } + return numberOfFiles; + } + + /** + * Returns the number of all objects in the given query path + * + * @param queryPath + * the path in which to count the objects + * + * @return the number of objects in the given query path + */ private long querySize(File queryPath) { + if (!queryPath.exists()) + return 0L; + if (!queryPath.isDirectory()) + throw new IllegalArgumentException("The path is not a directory, thus can not query key size for it: " + + queryPath.getAbsolutePath()); + long numberOfFiles = 0l; File[] subTypeFiles = queryPath.listFiles(); @@ -199,4 +342,30 @@ public class XmlPersistenceFileDao { } return numberOfFiles; } + + private void remove(File removePath, String failMsg, Object... msgParts) { + if (!removePath.isFile()) + throw new IllegalArgumentException("The given path for deletion is not a file:" + + removePath.getAbsolutePath()); + if (!removePath.delete()) { + String msg = MessageFormat.format(failMsg, msgParts); + throw new XmlPersistenceException(msg); + } + } + + private void deleteEmptyDirectory(File directoryPath, String failMsg, Object... msgArgs) { + if (!directoryPath.isDirectory()) + throw new IllegalArgumentException("The given path for deletion when empty is not a directory:" + + directoryPath.getAbsolutePath()); + if (directoryPath.list().length == 0) { + if (this.verbose) { + String msg = "Deleting empty directory for type {0} at {1}"; + logger.info(MessageFormat.format(msg, directoryPath.getName(), directoryPath)); + } + if (!directoryPath.delete()) { + String msg = MessageFormat.format(failMsg, msgArgs); + throw new XmlPersistenceException(msg); + } + } + } } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceHandlerImpl.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceHandlerImpl.java new file mode 100644 index 000000000..98790ba01 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceHandlerImpl.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.impl; + +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.api.XmlPersistenceHandler; +import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceHandlerImpl implements XmlPersistenceHandler { + + protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandlerImpl.class); + + protected boolean initialized; + protected boolean verbose; + protected XmlPersistenceDaoFactory daoFactory; + + public void initialize(Properties properties) { + if (this.initialized) + throw new IllegalStateException("Already initialized!"); + + // get properties + String context = XmlPersistenceHandlerImpl.class.getSimpleName(); + boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, + Boolean.FALSE).booleanValue(); + String daoFactoryClassName = PropertiesHelper.getProperty(properties, context, + XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, null); + + // load dao factory + XmlPersistenceDaoFactory daoFactory; + try { + @SuppressWarnings("unchecked") + Class xmlDaoFactoryClass = (Class) Class + .forName(daoFactoryClassName); + + daoFactory = xmlDaoFactoryClass.newInstance(); + + } catch (ClassNotFoundException e) { + throw new XmlPersistenceException("XmlDaoFactory class does not exist " + daoFactoryClassName, e); + } catch (Exception e) { + throw new XmlPersistenceException("Failed to load class " + daoFactoryClassName, e); + } + + // initialize the dao factory + XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(properties); + XmlPersistenceFileDao fileDao = new XmlPersistenceFileDao(pathBuilder, properties); + daoFactory.initialize(fileDao, properties); + + this.daoFactory = daoFactory; + this.verbose = verbose; + } + + @Override + public XmlPersistenceTransaction openTx() { + + TransactionDaoFactoryFacade daoFactoryFacade = new TransactionDaoFactoryFacade(this.daoFactory); + XmlPersistenceTransactionImpl tx = new XmlPersistenceTransactionImpl(daoFactoryFacade, this.verbose); + daoFactoryFacade.setTx(tx); + XmlPersistenceTransactionImpl.setTx(tx); + return tx; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java index 952669c7a..4c0b3c9af 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java @@ -56,25 +56,26 @@ public class XmlPersistencePathBuilder { // validate base path exists and is writable File basePathF = new File(basePath); if (!basePathF.exists()) - throw new XmlPersistenceException("The database store path does not exist at " - + basePathF.getAbsolutePath()); + throw new XmlPersistenceException(MessageFormat.format("The database store path does not exist at {0}", + basePathF.getAbsolutePath())); if (!basePathF.canWrite()) - throw new XmlPersistenceException("The database store path is not writeable at " - + basePathF.getAbsolutePath()); + throw new XmlPersistenceException(MessageFormat.format("The database store path is not writeable at {0}", + basePathF.getAbsolutePath())); // we want a clean base path String canonicalBasePath; try { canonicalBasePath = basePathF.getCanonicalPath(); } catch (IOException e) { - throw new XmlPersistenceException("Failed to build canonical path from " + basePath, e); + throw new XmlPersistenceException( + MessageFormat.format("Failed to build canonical path from {0}", basePath), e); } // this.basePathF = basePathF; this.basePath = canonicalBasePath; this.verbose = verbose; - logger.info("Using base path " + basePath); + logger.info(MessageFormat.format("Using base path {0}", basePath)); } String getFilename(String id) { @@ -105,26 +106,21 @@ public class XmlPersistencePathBuilder { return sb.toString(); } - File getPath() { - return new File(getPathAsString(null, null, null)); - } - - File getPath(String type) { + File getAddPath(String type, String id) { assertType(type); - return new File(getPathAsString(type, null, null)); - } - - File getPath(String type, String subType) { - assertType(type); - assertSubType(subType); - return new File(getPathAsString(type, subType, null)); - } - - File getPath(String type, String subType, String id) { - assertType(type); - assertSubType(subType); assertId(id); - return new File(getPathAsString(type, subType, id)); + + File path = new File(getPathAsString(type, null, id)); + + // assert path exists + String msg = "Persistence unit already exists for {0} / {1} at {2}"; + assertPathNotExists(path, msg, type, id, path.getAbsolutePath()); + + // check if parent path exists + msg = "Could not create parent path for {0} / {1} at {2}"; + createMissingParents(path, msg, type, id, path.getAbsolutePath()); + + return path; } File getAddPath(String type, String subType, String id) { @@ -132,20 +128,51 @@ public class XmlPersistencePathBuilder { assertSubType(subType); assertId(id); - File path = getPath(type, subType, id); + File path = new File(getPathAsString(type, subType, id)); - if (path.exists()) { - String msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; - throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); - } + // assert path exists + String msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; + assertPathNotExists(path, msg, type, subType, id, path.getAbsolutePath()); // check if parent path exists + msg = "Could not create parent path for {0} / {1} / {2} at {3}"; + createMissingParents(path, msg, type, subType, id, path.getAbsolutePath()); + + return path; + } + + File getReadPath(String type, String id) { + assertType(type); + assertId(id); + File path = new File(getPathAsString(type, null, id)); + if (this.verbose) { + String msg = "Query path for {0} / {1} is {2}..."; + logger.info(MessageFormat.format(msg, type, id, path.getAbsolutePath())); + } + return path; + } + + File getReadPath(String type, String subType, String id) { + assertType(type); + assertSubType(subType); + assertId(id); + File path = new File(getPathAsString(type, subType, id)); + if (this.verbose) { + String msg = "Query path for {0} / {1} / {2} is {3}..."; + logger.info(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); + } + return path; + } + + File getUpdatePath(String type, String id) { + assertType(type); + assertId(id); + + File path = new File(getPathAsString(type, null, id)); + if (!path.exists()) { - File parentFile = path.getParentFile(); - if (!parentFile.exists() && !parentFile.mkdirs()) { - String msg = "Could not create parent path for {0} / {1} / {2} at {3}"; - throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); - } + String msg = "Persistence unit does not exist for {0} / {1} at {2}"; + throw new XmlPersistenceException(MessageFormat.format(msg, type, id, path.getAbsolutePath())); } return path; @@ -156,7 +183,7 @@ public class XmlPersistencePathBuilder { assertSubType(subType); assertId(id); - File path = getPath(type, subType, id); + File path = new File(getPathAsString(type, subType, id)); if (!path.exists()) { String msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; @@ -166,52 +193,19 @@ public class XmlPersistencePathBuilder { return path; } - File getRemovePath() { - - File path = getPath(); - if (!path.exists()) { - String msg = "No Persistence units exist at {0}"; - throw new XmlPersistenceException(MessageFormat.format(msg, path.getAbsolutePath())); - } - - if (this.verbose) { - String msg = "Remove path for all is {0}..."; - logger.info(MessageFormat.format(msg, path.getAbsolutePath())); - } - - return path; - } - - File getRemovePath(String type) { + File getRemovePath(String type, String id) { assertType(type); + assertId(id); - File path = getPath(type); - if (!path.exists()) { - String msg = "No Persistence units exist for {0} at {1}"; - throw new XmlPersistenceException(MessageFormat.format(msg, type, path.getAbsolutePath())); - } - - if (this.verbose) { - String msg = "Remove path for {0} is {1}..."; - logger.info(MessageFormat.format(msg, type, path.getAbsolutePath())); - } - - return path; - } - - File getRemovePath(String type, String subType) { - assertType(type); - assertSubType(subType); - - File path = getPath(type, subType); + File path = new File(getPathAsString(type, null, id)); if (!path.exists()) { String msg = "No Persistence units exist for {0} / {1} at {2}"; - throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, path.getAbsolutePath())); + throw new XmlPersistenceException(MessageFormat.format(msg, type, id, path.getAbsolutePath())); } if (this.verbose) { String msg = "Remove path for {0} / {1} is {2}..."; - logger.info(MessageFormat.format(msg, type, subType, path.getAbsolutePath())); + logger.info(MessageFormat.format(msg, type, id, path.getAbsolutePath())); } return path; @@ -222,7 +216,7 @@ public class XmlPersistencePathBuilder { assertSubType(subType); assertId(id); - File path = getPath(type, subType, id); + File path = new File(getPathAsString(type, subType, id)); if (!path.exists()) { String msg = "Persistence unit for {0} / {1} / {2} does not exist at {3}"; throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); @@ -237,12 +231,12 @@ public class XmlPersistencePathBuilder { } File getQueryPath() { - return getPath(); + return new File(getPathAsString(null, null, null)); } File getQueryPath(String type) { assertType(type); - File path = getPath(type); + File path = new File(getPathAsString(type, null, null)); if (this.verbose) { String msg = "Query path for {0} is {1}..."; logger.info(MessageFormat.format(msg, type, path.getAbsolutePath())); @@ -253,7 +247,7 @@ public class XmlPersistencePathBuilder { File getQueryPath(String type, String subType) { assertType(type); assertSubType(subType); - File path = getPath(type, subType); + File path = new File(getPathAsString(type, subType, null)); if (this.verbose) { String msg = "Query path for {0} / {1} is {2}..."; logger.info(MessageFormat.format(msg, type, subType, path.getAbsolutePath())); @@ -261,18 +255,6 @@ public class XmlPersistencePathBuilder { return path; } - File getQueryPath(String type, String subType, String id) { - assertType(type); - assertSubType(subType); - assertId(id); - File path = getPath(type, subType, id); - if (this.verbose) { - String msg = "Query path for {0} / {1} / {2} is {3}..."; - logger.info(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); - } - return path; - } - private void assertId(String id) { if (StringHelper.isEmpty(id)) throw new XmlPersistenceException( @@ -295,4 +277,17 @@ public class XmlPersistencePathBuilder { throw new XmlPersistenceException("The filename does not have a . at index " + (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); } + + private void assertPathNotExists(File path, String msg, Object... args) { + if (path.exists()) { + throw new XmlPersistenceException(MessageFormat.format(msg, args)); + } + } + + private void createMissingParents(File path, String msg, Object... args) { + File parentFile = path.getParentFile(); + if (!parentFile.exists() && !parentFile.mkdirs()) { + throw new XmlPersistenceException(MessageFormat.format(msg, args)); + } + } } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxHandler.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceSaxHandler.java similarity index 93% rename from src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxHandler.java rename to src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceSaxHandler.java index f9029ac60..52c5ef0cf 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxHandler.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceSaxHandler.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.api; +package ch.eitchnet.xmlpers.impl; import java.io.File; import java.io.FileWriter; @@ -41,7 +41,10 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; -import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.api.XmlPersistenceContextData; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.api.XmlPersistenceFileHandler; +import ch.eitchnet.xmlpers.api.XmlPersistenceSaxContextData; /** * @author Robert von Burg diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java new file mode 100644 index 000000000..a8cdbb7ac --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.impl; + +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.objectfilter.ObjectFilter; +import ch.eitchnet.xmlpers.api.XmlPersistenceDao; +import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; +import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; + +/** + * @author Robert von Burg + */ +public class XmlPersistenceTransactionImpl implements XmlPersistenceTransaction { + + private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTransactionImpl.class); + + private static final ThreadLocal TX_THREADLOCAL_THREAD_LOCAL; + static { + TX_THREADLOCAL_THREAD_LOCAL = new ThreadLocal<>(); + } + + private XmlPersistenceDaoFactory daoFactory; + private ObjectFilter objectFilter; + private boolean verbose; + private boolean cleared; + + /** + * @param verbose + */ + public XmlPersistenceTransactionImpl(XmlPersistenceDaoFactory daoFactory, boolean verbose) { + this.daoFactory = daoFactory; + this.verbose = verbose; + this.objectFilter = new ObjectFilter(); + } + + /* + * modifying methods + */ + + /** + * @param object + */ + @Override + public void add(T object) { + this.objectFilter.add(object); + } + + /** + * @param objects + */ + @Override + @SuppressWarnings("unchecked") + public void addAll(List objects) { + this.objectFilter.addAll((List) objects); + } + + /** + * @param object + */ + @Override + public void update(T object) { + this.objectFilter.update(object); + } + + /** + * @param objects + */ + @Override + @SuppressWarnings("unchecked") + public void updateAll(List objects) { + this.objectFilter.updateAll((List) objects); + } + + /** + * @param object + */ + @Override + public void remove(T object) { + this.objectFilter.remove(object); + } + + /** + * @param objects + */ + @Override + @SuppressWarnings("unchecked") + public void removeAll(List objects) { + this.objectFilter.removeAll((List) objects); + } + + /** + * @return the daoFactory + */ + @Override + public XmlPersistenceDaoFactory getDaoFactory() { + return this.daoFactory; + } + + /** + * + */ + @Override + public void commit() { + + try { + if (this.verbose) + XmlPersistenceTransactionImpl.logger.info("Committing TX..."); + Set keySet = this.objectFilter.keySet(); + if (keySet.isEmpty()) + return; + for (String key : keySet) { + + List removed = this.objectFilter.getRemoved(key); + if (removed.isEmpty()) { + if (this.verbose) + XmlPersistenceTransactionImpl.logger.info("No objects removed in this tx."); + } else { + if (this.verbose) + XmlPersistenceTransactionImpl.logger.info(removed.size() + " objects removed in this tx."); + + for (Object object : removed) { + XmlPersistenceDao dao = this.daoFactory.getDao(object); + dao.remove(object); + } + } + + List updated = this.objectFilter.getUpdated(key); + if (updated.isEmpty()) { + if (this.verbose) + XmlPersistenceTransactionImpl.logger.info("No objects updated in this tx."); + } else { + if (this.verbose) + XmlPersistenceTransactionImpl.logger.info(updated.size() + " objects updated in this tx."); + + for (Object object : updated) { + + XmlPersistenceDao dao = this.daoFactory.getDao(object); + dao.update(object); + } + } + + List added = this.objectFilter.getAdded(key); + if (added.isEmpty()) { + if (this.verbose) + XmlPersistenceTransactionImpl.logger.info("No objects added in this tx."); + } else { + if (this.verbose) + XmlPersistenceTransactionImpl.logger.info(added.size() + " objects added in this tx."); + + for (Object object : added) { + + XmlPersistenceDao dao = this.daoFactory.getDao(object); + dao.add(object); + } + } + } + + XmlPersistenceTransactionImpl.logger.info("Completed TX"); + + } finally { + // clean up + clear(); + } + } + + /** + * Clears the object filter and releases the transaction. After calling this method, this transaction instance can + * not be used anymore + */ + @Override + public void clear() { + if (!this.cleared) { + this.objectFilter.clearCache(); + this.objectFilter = null; + + this.daoFactory = null; + TX_THREADLOCAL_THREAD_LOCAL.set(null); + this.cleared = true; + } + } + + /** + * @return + */ + public boolean isCleared() { + return this.cleared; + } + + public static XmlPersistenceTransaction getTx() { + XmlPersistenceTransaction tx = TX_THREADLOCAL_THREAD_LOCAL.get(); + if (tx == null) + throw new IllegalStateException("No transaction is currently open!"); + return tx; + } + + public static void setTx(XmlPersistenceTransactionImpl tx) { + if (TX_THREADLOCAL_THREAD_LOCAL.get() != null) + throw new IllegalStateException("A transaction is already open!"); + TX_THREADLOCAL_THREAD_LOCAL.set(tx); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java new file mode 100644 index 000000000..706c40441 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.test; + +import java.io.File; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.xmlpers.api.XmlPersistenceDao; +import ch.eitchnet.xmlpers.api.XmlPersistenceHandler; +import ch.eitchnet.xmlpers.api.XmlPersistenceMetadataDao; +import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; +import ch.eitchnet.xmlpers.impl.XmlPersistenceHandlerImpl; +import ch.eitchnet.xmlpers.test.impl.Book; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Parameter; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + */ +public abstract class AbstractXmlPersistenceTest { + + protected static final Logger logger = LoggerFactory.getLogger(AbstractXmlPersistenceTest.class.getName()); + + protected static final String RES_TYPE = "@subType"; + protected static final String RES_TYPE_INEXISTANT = "@inexistant"; + protected static final String RES_NAME = "@name"; + protected static final String RES_NAME_MODIFIED = "@name_modified"; + protected static final String RES_ID = "@id"; + + protected static final String PARAM_TYPE = "@paramType"; + protected static final String PARAM_NAME = "@paramName"; + protected static final String PARAM_ID = "@paramId"; + protected static final String PARAM_VALUE_1 = "@paramValue1"; + protected static final String PARAM_VALUE_2 = "@paramValue2"; + + protected static final long BOOK_ID = 10L; + protected static final String BOOK_TITLE = "Nick Hornby"; + protected static final String BOOK_AUTHOR = "A long way down"; + protected static final String BOOK_PRESS_1 = "Some press"; + protected static final String BOOK_PRESS_2 = "Another press"; + protected static final double BOOK_PRICE = 45.55D; + + protected static XmlPersistenceHandler persistenceHandler; + + protected XmlPersistenceTransaction tx; + + /** + * @throws Exception + * if something goes wrong + */ + public static void init(Properties props) throws Exception { + try { + String userDir = System.getProperty("user.dir"); + String basePath = userDir + "/target/testdb"; + File basePathF = new File(basePath); + if (!basePathF.exists() && !basePathF.mkdirs()) + Assert.fail("Could not create temporaray database store in " + basePathF.getAbsolutePath()); + + AbstractXmlPersistenceTest.persistenceHandler = new XmlPersistenceHandlerImpl(); + ((XmlPersistenceHandlerImpl) AbstractXmlPersistenceTest.persistenceHandler).initialize(props); + AbstractXmlPersistenceTest.logger.info("Initialized persistence handler."); + + } catch (Exception e) { + AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); + + throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); + } + } + + @AfterClass + public static void afterClass() { + String userDir = System.getProperty("user.dir"); + String basePath = userDir + "/target/testdb"; + File basePathF = new File(basePath); + if (basePathF.exists() && !FileHelper.deleteFile(basePathF, true)) + Assert.fail("Could not delete temporaray database store at " + basePathF.getAbsolutePath()); + } + + @After + public void tearDown() { + if (this.tx != null) + this.tx.clear(); + } + + /** + * Tests the following story: + *
      + *
    • create book
    • + *
    • read book
    • + *
    • update book
    • + *
    • remove book
    • + *
    + */ + @Test + public void testBookPersistence() { + + // create + Book book = createBook(); + assertBook(book); + this.tx = persistenceHandler.openTx(); + this.tx.add(book); + this.tx.commit(); + + // read + this.tx = persistenceHandler.openTx(); + XmlPersistenceDao dao = this.tx.getDaoFactory().getDao(book); + Book persistedBook = dao.queryById(String.valueOf(BOOK_ID)); + assertBook(persistedBook); + + // update + persistedBook.setPress(BOOK_PRESS_2); + this.tx.update(persistedBook); + this.tx.commit(); + + // read + this.tx = persistenceHandler.openTx(); + dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_BOOK); + persistedBook = dao.queryById(String.valueOf(BOOK_ID)); + assertBookUpdated(persistedBook); + + // delete + this.tx.remove(book); + this.tx.commit(); + + // fail to read + this.tx = persistenceHandler.openTx(); + dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_BOOK); + persistedBook = dao.queryById(String.valueOf(BOOK_ID)); + Assert.assertNull(persistedBook); + } + + /** + * Tests the following story: + *
      + *
    • create resource
    • + *
    • read resource
    • + *
    • update resource
    • + *
    • remove resource
    • + *
    + */ + @Test + public void testResourcePersistence() { + + persistResource(); + readResource(); + updateResource(); + removeResource(); + } + + private void persistResource() { + + try { + AbstractXmlPersistenceTest.logger.info("Trying to create..."); + + // new instance + Resource resource = createResource(); + assertResource(resource); + + // persist instance + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + this.tx.add(resource); + this.tx.commit(); + + AbstractXmlPersistenceTest.logger.info("Done creating."); + + } catch (Exception e) { + AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); + Assert.fail("Failed by exception: " + e.getLocalizedMessage()); + } + } + + private void readResource() { + + try { + AbstractXmlPersistenceTest.logger.info("Trying to read..."); + + // query Resource + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); + Resource resource = dao.queryById(RES_ID); + AbstractXmlPersistenceTest.logger.info("Found Resource: " + resource); + assertResource(resource); + this.tx.commit(); + + AbstractXmlPersistenceTest.logger.info("Done reading."); + + } catch (Exception e) { + AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); + Assert.fail("Failed by exception: " + e.getLocalizedMessage()); + } + } + + private void updateResource() { + + try { + AbstractXmlPersistenceTest.logger.info("Trying to update an object..."); + + // query the instance + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); + Resource resource = dao.queryById(RES_ID); + AbstractXmlPersistenceTest.logger.info("Found Resource: " + resource); + assertResource(resource); + + // modify the instance + resource.setName(RES_NAME_MODIFIED); + resource.getParameterBy(PARAM_ID).setValue(PARAM_VALUE_2); + + // update the instance + this.tx.update(resource); + this.tx.commit(); + + // re-read and validate + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); + resource = dao.queryById(RES_ID); + this.tx.commit(); + AbstractXmlPersistenceTest.logger.info("Found Resource: " + resource); + assertResourceUpdated(resource); + + AbstractXmlPersistenceTest.logger.info("Done updating."); + + } catch (Exception e) { + AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); + Assert.fail("Failed by exception: " + e.getLocalizedMessage()); + } + } + + private void removeResource() { + + AbstractXmlPersistenceTest.logger.info("Trying to remove..."); + + // query the instance + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); + Resource resource = dao.queryById(RES_ID); + AbstractXmlPersistenceTest.logger.info("Found Resource: " + resource); + assertResourceUpdated(resource); + + this.tx.remove(resource); + this.tx.commit(); + + AbstractXmlPersistenceTest.logger.info("Done removing."); + } + + @Test + public void testQueryFail() { + + AbstractXmlPersistenceTest.logger.info("Trying to query removed object..."); + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); + Resource resource = dao.queryById(RES_ID); + this.tx.commit(); + Assert.assertNull("Expected resource not to be found!", resource); + } + + @Test + public void testReCreate() { + + try { + AbstractXmlPersistenceTest.logger.info("Trying to recreate..."); + + Resource resource = createResource(); + assertResource(resource); + + // persist instance + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + this.tx.add(resource); + this.tx.commit(); + + AbstractXmlPersistenceTest.logger.info("Done creating."); + + } catch (Exception e) { + AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); + Assert.fail("Failed by exception: " + e.getLocalizedMessage()); + } + } + + @Test + @Ignore + public void testQueryFromTo() { + Assert.fail("Not yet implemented"); + } + + @Test + public void testQueryAll() { + + AbstractXmlPersistenceTest.logger.info("Trying to query all..."); + + // query all + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); + List list = dao.queryAll(); + Assert.assertEquals("Expected only one object, found " + list, 1, list.size()); + + // and now something useless + dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE_INEXISTANT); + list = dao.queryAll(); + this.tx.commit(); + Assert.assertEquals("Expected no objects, found " + list, 0, list.size()); + + AbstractXmlPersistenceTest.logger.info("Done querying."); + } + + @Test + public void testKeySet() { + + // first prepare by creating a resource + createResource(); + + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + XmlPersistenceMetadataDao metadataDao = this.tx.getDaoFactory().getMetadataDao(); + + // query by type only, which should return nothing on this level + Set keySet = metadataDao.queryKeySet(TestConstants.TYPE_RES); + Assert.assertEquals("A resource can only be queried by type/subtype, but dao returned values!", 0, + keySet.size()); + + // now we shoud find our resource with the given type + keySet = metadataDao.queryKeySet(TestConstants.TYPE_RES, RES_TYPE); + Assert.assertEquals("Expected one key, found " + keySet, 1, keySet.size()); + + // and now something useless + keySet = metadataDao.queryKeySet(TestConstants.TYPE_RES, RES_TYPE_INEXISTANT); + Assert.assertEquals("Expected no keys, found " + keySet, 0, keySet.size()); + + this.tx.commit(); + } + + @Test + public void testRemoveAll() { + + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); + List objects = dao.queryAll(); + this.tx.removeAll(objects); + this.tx.commit(); + } + + @Test + public void testSize() { + + this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); + XmlPersistenceMetadataDao metadataDao = this.tx.getDaoFactory().getMetadataDao(); + long size = metadataDao.querySize(TestConstants.TYPE_RES, RES_TYPE); + this.tx.commit(); + Assert.assertEquals("Expected size = 0, found: " + size, 0, size); + } + + private Book createBook() { + Book book = new Book(BOOK_ID, BOOK_TITLE, BOOK_AUTHOR, BOOK_PRESS_1, BOOK_PRICE); + return book; + } + + private void assertBook(Book book) { + Assert.assertNotNull(book); + Assert.assertEquals(BOOK_ID, book.getId().longValue()); + Assert.assertEquals(BOOK_TITLE, book.getTitle()); + Assert.assertEquals(BOOK_AUTHOR, book.getAuthor()); + Assert.assertEquals(BOOK_PRESS_1, book.getPress()); + Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); + } + + private void assertBookUpdated(Book book) { + Assert.assertNotNull(book); + Assert.assertEquals(BOOK_ID, book.getId().longValue()); + Assert.assertEquals(BOOK_TITLE, book.getTitle()); + Assert.assertEquals(BOOK_AUTHOR, book.getAuthor()); + Assert.assertEquals(BOOK_PRESS_2, book.getPress()); + Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); + } + + private Resource createResource() { + Resource resource = new Resource(RES_ID, RES_NAME, RES_TYPE); + Parameter param = new Parameter(PARAM_ID, PARAM_NAME, PARAM_TYPE, PARAM_VALUE_1); + resource.addParameter(param); + return resource; + } + + private void assertResource(Resource resource) { + Assert.assertNotNull(resource); + Assert.assertEquals(RES_ID, resource.getId()); + Assert.assertEquals(RES_NAME, resource.getName()); + Assert.assertEquals(RES_TYPE, resource.getType()); + Parameter param = resource.getParameterBy(PARAM_ID); + Assert.assertNotNull(param); + Assert.assertEquals(PARAM_ID, param.getId()); + Assert.assertEquals(PARAM_NAME, param.getName()); + Assert.assertEquals(PARAM_TYPE, param.getType()); + Assert.assertEquals(PARAM_VALUE_1, param.getValue()); + } + + private void assertResourceUpdated(Resource resource) { + Assert.assertNotNull(resource); + Assert.assertEquals(RES_ID, resource.getId()); + Assert.assertEquals(RES_NAME_MODIFIED, resource.getName()); + Assert.assertEquals(RES_TYPE, resource.getType()); + Parameter param = resource.getParameterBy(PARAM_ID); + Assert.assertNotNull(param); + Assert.assertEquals(PARAM_ID, param.getId()); + Assert.assertEquals(PARAM_NAME, param.getName()); + Assert.assertEquals(PARAM_TYPE, param.getType()); + Assert.assertEquals(PARAM_VALUE_2, param.getValue()); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java new file mode 100644 index 000000000..35448c2c0 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.test; + +import java.util.Properties; + +import org.junit.BeforeClass; +import org.junit.Ignore; + +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; + +/** + * @author Robert von Burg + */ +@Ignore +public class XmlPersistenceDomTest extends AbstractXmlPersistenceTest { + + /** + * @throws Exception + * if something goes wrong + */ + @BeforeClass + public static void init() throws Exception { + + Properties props = new Properties(); + props.setProperty(XmlPersistenceConstants.PROP_BASEPATH, "target/testdb"); + props.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); + props.setProperty(XmlPersistenceConstants.PROP_XML_IO_MOD, "dom"); + props.setProperty(XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, TestModelDaoFactory.class.getName()); + + init(props); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceSaxTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceSaxTest.java new file mode 100644 index 000000000..fba8c2eb2 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceSaxTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.test; + +import java.util.Properties; + +import org.junit.BeforeClass; + +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; + +/** + * @author Robert von Burg + */ +public class XmlPersistenceSaxTest extends AbstractXmlPersistenceTest { + + /** + * @throws Exception + * if something goes wrong + */ + @BeforeClass + public static void init() throws Exception { + + Properties props = new Properties(); + props.setProperty(XmlPersistenceConstants.PROP_BASEPATH, "target/testdb"); + props.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); + props.setProperty(XmlPersistenceConstants.PROP_XML_IO_MOD, "sax"); + props.setProperty(XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, TestModelDaoFactory.class.getName()); + + init(props); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java deleted file mode 100644 index 7e6eb3048..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceTest.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.test; - -import java.io.File; -import java.util.List; -import java.util.Properties; -import java.util.Set; - -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.api.XmlPersistenceHandler; -import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; -import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - * - */ -public class XmlPersistenceTest { - - private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTest.class.getName()); - - private static final String RES_TYPE = "@subType"; - private static final String RES_TYPE_INEXISTANT = "@inexistant"; - private static final String RES_NAME = "@name"; - private static final String RES_NAME_MODIFIED = "@name_modified"; - private static final String RES_ID = "@id"; - - private static XmlPersistenceHandler persistenceHandler; - - /** - * @throws Exception - * if something goes wrong - */ - @BeforeClass - public static void init() throws Exception { - try { - String userDir = System.getProperty("user.dir"); - String basePath = userDir + "/target/testdb"; - File basePathF = new File(basePath); - if (!basePathF.exists() && !basePathF.mkdirs()) - Assert.fail("Could not create temporaray database store in " + basePathF.getAbsolutePath()); - - Properties props = new Properties(); - props.setProperty(XmlPersistenceConstants.PROP_BASEPATH, "target/testdb"); - props.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); - props.setProperty(XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, TestModelDaoFactory.class.getName()); - - XmlPersistenceTest.persistenceHandler = new XmlPersistenceHandler(); - XmlPersistenceTest.persistenceHandler.initialize(props); - XmlPersistenceTest.logger.info("Initialized persistence handler."); - - } catch (Exception e) { - XmlPersistenceTest.logger.error(e.getMessage(), e); - - throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); - } - } - - /** - * Tests the following story: - *
      - *
    • create object
    • - *
    • read object
    • - *
    • update object
    • - *
    • remove object
    • - *
    - * - */ - @Test - public void testPersistenceStory() { - - createObject(); - readObject(); - updateObject(); - removeObject(); - } - - private void createObject() { - - try { - XmlPersistenceTest.logger.info("Trying to create..."); - - // new instance - Resource resource = new Resource(RES_ID, RES_NAME, RES_TYPE); - - // persist instance - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - tx.add(resource); - tx.commit(); - - XmlPersistenceTest.logger.info("Done creating."); - - } catch (Exception e) { - XmlPersistenceTest.logger.error(e.getMessage(), e); - Assert.fail("Failed: " + e.getLocalizedMessage()); - } - } - - private void readObject() { - - try { - XmlPersistenceTest.logger.info("Trying to read..."); - - // query Resource with id @id - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - Resource resource = tx.queryById(Resource.class.getName(), RES_TYPE, RES_ID); - XmlPersistenceTest.logger.info("Found Resource: " + resource); - tx.commit(); - - XmlPersistenceTest.logger.info("Done reading."); - - } catch (Exception e) { - XmlPersistenceTest.logger.error(e.getMessage(), e); - Assert.fail("Failed: " + e.getLocalizedMessage()); - } - } - - private void updateObject() { - - try { - XmlPersistenceTest.logger.info("Trying to update an object..."); - - // query the instance - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - Resource resource = tx.queryById(Resource.class.getName(), RES_TYPE, RES_ID); - XmlPersistenceTest.logger.info("Found Resource: " + resource); - - // modify the instance - resource.setName(RES_NAME_MODIFIED); - - // update the instance - tx.update(resource); - tx.commit(); - - XmlPersistenceTest.logger.info("Done updating."); - - } catch (Exception e) { - XmlPersistenceTest.logger.error(e.getMessage(), e); - Assert.fail("Failed: " + e.getLocalizedMessage()); - } - } - - private void removeObject() { - - XmlPersistenceTest.logger.info("Trying to remove..."); - - // query the instance - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - Resource resource = tx.queryById(Resource.class.getName(), RES_TYPE, RES_ID); - XmlPersistenceTest.logger.info("Found Resource: " + resource); - - tx.remove(resource); - tx.commit(); - - XmlPersistenceTest.logger.info("Done removing."); - } - - @Test - public void testQueryFail() { - - XmlPersistenceTransaction tx = null; - try { - XmlPersistenceTest.logger.info("Trying to query removed object..."); - tx = XmlPersistenceTest.persistenceHandler.openTx(); - Resource resource = tx.queryById(Resource.class.getName(), RES_TYPE, RES_ID); - XmlPersistenceTest.logger.info("Found Resource: " + resource); - XmlPersistenceTest.logger.info("Done querying removed object"); - } catch (XmlPersistenceException e) { - Assert.assertEquals("Wrong error message. Expected error that object does not exist", - "No object exists for ch.eitchnet.xmlpers.test.impl.Resource / @subtype / @id", - e.getLocalizedMessage()); - } finally { - if (tx != null) - tx.commit(); - } - } - - @Test - public void testReCreate() { - - try { - XmlPersistenceTest.logger.info("Trying to recreate..."); - - // new instance - Resource resource = new Resource(RES_ID, RES_NAME, RES_TYPE); - - // persist instance - XmlPersistenceTransaction tx = XmlPersistenceTest.persistenceHandler.openTx(); - tx.add(resource); - tx.commit(); - - XmlPersistenceTest.logger.info("Done creating."); - - } catch (Exception e) { - XmlPersistenceTest.logger.error(e.getMessage(), e); - Assert.fail("Failed: " + e.getLocalizedMessage()); - } - } - - @Test - @Ignore - public void testQueryFromTo() { - Assert.fail("Not yet implemented"); - } - - @Test - public void testQueryAll() { - - XmlPersistenceTransaction tx = null; - try { - - XmlPersistenceTest.logger.info("Trying to query all..."); - - // query all - tx = XmlPersistenceTest.persistenceHandler.openTx(); - List list = tx.queryAll(Resource.class.getName()); - Assert.assertTrue("Expected only one object, found " + list.size(), list.size() == 1); - - // also with subtype - list = tx.queryAll(Resource.class.getName(), RES_TYPE); - Assert.assertTrue("Expected only one object, found " + list.size(), list.size() == 1); - - // and now something useless - list = tx.queryAll(Resource.class.getName(), RES_TYPE_INEXISTANT); - Assert.assertTrue("Expected no objects, found " + list.size(), list.size() == 0); - - XmlPersistenceTest.logger.info("Done querying."); - - } finally { - if (tx != null) - tx.commit(); - } - } - - @Test - public void testKeySet() { - - XmlPersistenceTransaction tx = null; - try { - tx = XmlPersistenceTest.persistenceHandler.openTx(); - - Set keySet = tx.queryKeySet(Resource.class.getName()); - Assert.assertTrue("Expected one key, found " + keySet.size(), keySet.size() == 1); - - // also with subtype - keySet = tx.queryKeySet(Resource.class.getName(), RES_TYPE); - Assert.assertTrue("Expected one key, found " + keySet.size(), keySet.size() == 1); - - // and now something useless - keySet = tx.queryKeySet(Resource.class.getName(), RES_TYPE_INEXISTANT); - Assert.assertTrue("Expected no keys, found " + keySet, keySet.size() == 0); - - } finally { - if (tx != null) - tx.commit(); - } - } - - @Test - public void testRemoveAll() { - - XmlPersistenceTransaction tx = null; - try { - tx = XmlPersistenceTest.persistenceHandler.openTx(); - - List objects = tx.queryAll(Resource.class.getName(), RES_TYPE); - tx.removeAll(objects); - - } finally { - if (tx != null) - tx.commit(); - } - } - - @Test - public void testSize() { - - XmlPersistenceTransaction tx = null; - try { - tx = XmlPersistenceTest.persistenceHandler.openTx(); - - long size = tx.querySize(Resource.class.getName(), RES_TYPE); - Assert.assertTrue("Expected size = 0, found: " + size, size == 0); - - } finally { - if (tx != null) - tx.commit(); - } - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java index 54ec38957..7df3b66b9 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java @@ -33,6 +33,22 @@ public class Book { private String press; private double price; + /** + * @param id + * @param title + * @param author + * @param press + * @param price + */ + public Book(Long id, String title, String author, String press, double price) { + super(); + this.id = id; + this.title = title; + this.author = author; + this.press = press; + this.price = price; + } + /** * */ diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java index 26f1800d0..a6b1f05bd 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java @@ -21,7 +21,7 @@ */ package ch.eitchnet.xmlpers.test.impl; -import ch.eitchnet.xmlpers.impl.AbstractXmlDao; +import ch.eitchnet.xmlpers.api.AbstractXmlDao; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java new file mode 100644 index 000000000..bf3f3480c --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl; + +import java.io.File; + +import javax.xml.parsers.DocumentBuilder; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.xmlpers.api.DomUtil; +import ch.eitchnet.xmlpers.api.XmlPersistenceDomContextData; + +/** + * @author Robert von Burg + * + */ +public class BookDomDao extends BookDao { + + @Override + protected Book read(File filePath) { + + XmlPersistenceDomContextData cd = new XmlPersistenceDomContextData(); + cd.setFile(filePath); + getFileHandler().read(cd); + Document document = cd.getDocument(); + Book book = parseFromDom(document.getDocumentElement()); + return book; + } + + @Override + protected void write(Book book, File filePath) { + + XmlPersistenceDomContextData cd = new XmlPersistenceDomContextData(); + cd.setFile(filePath); + DocumentBuilder documentBuilder = DomUtil.createDocumentBuilder(); + Document document = documentBuilder.getDOMImplementation().createDocument(null, null, null); + serializeToDom(book, document); + cd.setDocument(document); + getFileHandler().write(cd); + } + + public Element serializeToDom(Book book, Document document) { + + Element element = document.createElement("Book"); + + element.setAttribute("id", Long.toString(book.getId())); + element.setAttribute("title", book.getTitle()); + element.setAttribute("author", book.getAuthor()); + element.setAttribute("press", book.getPress()); + element.setAttribute("price", Double.toString(book.getPrice())); + + return element; + } + + public Book parseFromDom(Element element) { + + String idS = element.getAttribute("id"); + long id = Long.parseLong(idS); + String title = element.getAttribute("title"); + String author = element.getAttribute("author"); + String press = element.getAttribute("press"); + String priceS = element.getAttribute("price"); + double price = Double.parseDouble(priceS); + + Book book = new Book(id, title, author, press, price); + return book; + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java new file mode 100644 index 000000000..65a5fd113 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl; + +import java.io.File; + +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.xmlpers.api.XmlPersistenceSaxContextData; +import ch.eitchnet.xmlpers.api.XmlPersistenceSaxWriter; +import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; + +/** + * @author Robert von Burg + * + */ +public class BookSaxDao extends BookDao { + + @Override + protected Book read(File filePath) { + + XmlPersistenceSaxContextData cd = new XmlPersistenceSaxContextData(); + cd.setFile(filePath); + BookDefaultHandler bookDefaultHandler = new BookDefaultHandler(); + cd.setDefaultHandler(bookDefaultHandler); + + getFileHandler().read(cd); + + return bookDefaultHandler.getBook(); + } + + @Override + protected void write(Book book, File filePath) { + + XmlPersistenceSaxContextData cd = new XmlPersistenceSaxContextData(); + cd.setFile(filePath); + cd.setXmlWriter(new BookSaxWriter(book)); + + getFileHandler().write(cd); + } + + private class BookSaxWriter implements XmlPersistenceSaxWriter { + + private final Book book; + + public BookSaxWriter(Book book) { + this.book = book; + } + + @Override + public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { + writer.writeEmptyElement("Book"); + writer.writeAttribute("id", Long.toString(this.book.getId())); + writer.writeAttribute("title", this.book.getTitle()); + writer.writeAttribute("author", this.book.getAuthor()); + writer.writeAttribute("press", this.book.getPress()); + writer.writeAttribute("price", Double.toString(this.book.getPrice())); + } + } + + private class BookDefaultHandler extends DefaultHandler { + + private Book book; + + public Book getBook() { + return this.book; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (qName) { + case "Book": + String idS = attributes.getValue("id"); + long id = Long.parseLong(idS); + Book book = new Book(id); + book.setTitle(attributes.getValue("title")); + book.setAuthor(attributes.getValue("author")); + book.setPress(attributes.getValue("press")); + String priceS = attributes.getValue("price"); + double price = Double.parseDouble(priceS); + book.setPrice(price); + this.book = book; + break; + default: + throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java index 3f7517370..ce61da6a3 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java @@ -21,7 +21,7 @@ */ package ch.eitchnet.xmlpers.test.impl; -import ch.eitchnet.xmlpers.impl.AbstractXmlDao; +import ch.eitchnet.xmlpers.api.AbstractXmlDao; import ch.eitchnet.xmlpers.test.model.Resource; /** @@ -30,13 +30,21 @@ import ch.eitchnet.xmlpers.test.model.Resource; */ public abstract class ResourceDao extends AbstractXmlDao { + private final String subType; + + public ResourceDao(String subType) { + this.subType = subType; + } + @Override public String getType() { return Resource.class.getSimpleName(); } @Override - public abstract String getSubType(); + public String getSubType() { + return this.subType; + } @Override public String getId(Resource object) { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java index 2a267cbba..f1cac5029 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java @@ -21,41 +21,70 @@ package ch.eitchnet.xmlpers.test.impl; import java.io.File; +import javax.xml.parsers.DocumentBuilder; + import org.w3c.dom.Document; import org.w3c.dom.Element; -import org.w3c.dom.Text; +import org.w3c.dom.NodeList; +import ch.eitchnet.xmlpers.api.DomUtil; +import ch.eitchnet.xmlpers.api.XmlPersistenceDomContextData; +import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; /** * @author Robert von Burg * */ -public abstract class ResourceDomDao extends ResourceDao { +public class ResourceDomDao extends ResourceDao { + + /** + * @param subType + */ + public ResourceDomDao(String subType) { + super(subType); + } @Override protected Resource read(File filePath) { - // TODO Auto-generated method stub - return null; + + XmlPersistenceDomContextData cd = new XmlPersistenceDomContextData(); + cd.setFile(filePath); + getFileHandler().read(cd); + Document document = cd.getDocument(); + Resource resource = parseFromDom(document.getDocumentElement()); + return resource; } @Override - protected void write(Resource object, File filePath) { - // TODO Auto-generated method stub + protected void write(Resource resource, File filePath) { + XmlPersistenceDomContextData cd = new XmlPersistenceDomContextData(); + cd.setFile(filePath); + DocumentBuilder documentBuilder = DomUtil.createDocumentBuilder(); + Document document = documentBuilder.getDOMImplementation().createDocument(null, null, null); + serializeToDom(resource, document); + cd.setDocument(document); + getFileHandler().write(cd); } - public Element serializeToDom(Resource object, Document document) { + public Element serializeToDom(Resource resource, Document document) { Element element = document.createElement("Resource"); - element.setAttribute("id", object.getId()); - element.setAttribute("type", object.getType()); + element.setAttribute("id", resource.getId()); + element.setAttribute("name", resource.getName()); + element.setAttribute("type", resource.getType()); - Element nameElement = document.createElement("Name"); - element.appendChild(nameElement); - Text textNode = document.createTextNode(object.getName()); - nameElement.appendChild(textNode); + for (String paramId : resource.getParameterKeySet()) { + Parameter param = resource.getParameterBy(paramId); + Element paramElement = document.createElement("Parameter"); + + paramElement.setAttribute("id", param.getId()); + paramElement.setAttribute("name", param.getName()); + paramElement.setAttribute("type", param.getType()); + paramElement.setAttribute("value", param.getType()); + } return element; } @@ -63,12 +92,22 @@ public abstract class ResourceDomDao extends ResourceDao { public Resource parseFromDom(Element element) { String id = element.getAttribute("id"); + String name = element.getAttribute("name"); String type = element.getAttribute("type"); - Element nameElement = (Element) element.getElementsByTagName("Name").item(0); - String name = nameElement.getTextContent(); - Resource Resource = new Resource(id, name, type); + Resource resource = new Resource(id, name, type); - return Resource; + NodeList paramElements = element.getElementsByTagName("Parameter"); + for (int i = 0; i < paramElements.getLength(); i++) { + String paramId = element.getAttribute("id"); + String paramName = element.getAttribute("name"); + String paramType = element.getAttribute("type"); + String paramValue = element.getAttribute("value"); + + Parameter param = new Parameter(paramId, paramName, paramType, paramValue); + resource.addParameter(param); + } + + return resource; } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java index c31e9018c..657a54da0 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java @@ -19,10 +19,18 @@ */ package ch.eitchnet.xmlpers.test.impl; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.AttributesImpl; +import java.io.File; +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.xmlpers.api.XmlPersistenceSaxContextData; +import ch.eitchnet.xmlpers.api.XmlPersistenceSaxWriter; +import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; /** @@ -31,35 +39,91 @@ import ch.eitchnet.xmlpers.test.model.Resource; */ public class ResourceSaxDao extends ResourceDao { - public void serializeToSax(Resource object, ContentHandler contentHandler) { + /** + * @param subType + */ + public ResourceSaxDao(String subType) { + super(subType); + } - try { - contentHandler.startDocument(); + @Override + protected Resource read(File filePath) { - // Resource element / root - { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", "id", "", object.getId()); - atts.addAttribute("", "", "type", "", object.getType()); - contentHandler.startElement("", "", "Resource", atts); + XmlPersistenceSaxContextData cd = new XmlPersistenceSaxContextData(); + cd.setFile(filePath); + ResourceDefaultHandler bookDefaultHandler = new ResourceDefaultHandler(); + cd.setDefaultHandler(bookDefaultHandler); - // name element - { - contentHandler.startElement("", "", "Name", null); - char[] nameArr = object.getName().toCharArray(); - contentHandler.characters(nameArr, 0, nameArr.length); - contentHandler.endElement("", "", "name"); - } + getFileHandler().read(cd); - // Resource end - contentHandler.endElement("", "", "Resource"); + return bookDefaultHandler.getResource(); + } + + @Override + protected void write(Resource resource, File filePath) { + + XmlPersistenceSaxContextData cd = new XmlPersistenceSaxContextData(); + cd.setFile(filePath); + cd.setXmlWriter(new ResourceSaxWriter(resource)); + + getFileHandler().write(cd); + } + + private class ResourceSaxWriter implements XmlPersistenceSaxWriter { + + private final Resource resource; + + public ResourceSaxWriter(Resource resource) { + this.resource = resource; + } + + @Override + public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { + writer.writeElement("Resource"); + writer.writeAttribute("id", this.resource.getId()); + writer.writeAttribute("name", this.resource.getName()); + writer.writeAttribute("type", this.resource.getType()); + for (String paramId : this.resource.getParameterKeySet()) { + Parameter param = this.resource.getParameterBy(paramId); + writer.writeElement("Parameter"); + writer.writeAttribute("id", param.getId()); + writer.writeAttribute("name", param.getName()); + writer.writeAttribute("type", param.getType()); + writer.writeAttribute("value", param.getValue()); } + } + } - // end document - contentHandler.endDocument(); + private class ResourceDefaultHandler extends DefaultHandler { - } catch (SAXException e) { - throw new RuntimeException("Failed to serialize " + object + " to SAX", e); + private Resource resource; + + public Resource getResource() { + return this.resource; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (qName) { + case "Resource": + String id = attributes.getValue("id"); + String name = attributes.getValue("name"); + String type = attributes.getValue("type"); + Resource resource = new Resource(id, name, type); + this.resource = resource; + break; + case "Parameter": + id = attributes.getValue("id"); + name = attributes.getValue("name"); + type = attributes.getValue("type"); + String value = attributes.getValue("value"); + Parameter param = new Parameter(id, name, type, value); + this.resource.addParameter(param); + break; + default: + throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); + } } } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java new file mode 100644 index 000000000..5ec56aaa5 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl; + +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class TestConstants { + + public static final String TYPE_RES = Resource.class.getSimpleName(); + public static final String TYPE_BOOK = Book.class.getSimpleName(); +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java index abd7c9e45..43474a8d4 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java @@ -19,59 +19,79 @@ */ package ch.eitchnet.xmlpers.test.impl; -import java.util.Properties; +import java.text.MessageFormat; +import ch.eitchnet.xmlpers.api.AbstractDaoFactory; +import ch.eitchnet.xmlpers.api.XmlIoMode; import ch.eitchnet.xmlpers.api.XmlPersistenceDao; -import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; -import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; import ch.eitchnet.xmlpers.test.model.Resource; /** * @author Robert von Burg * */ -public class TestModelDaoFactory implements XmlPersistenceDaoFactory { +public class TestModelDaoFactory extends AbstractDaoFactory { @Override - public void initialize(XmlPersistenceFileDao fileDao, Properties properties) { - // TODO Auto-generated method stub + public XmlPersistenceDao getDao(T object) { - } - - @Override - public XmlPersistenceDao createDaoInstance(T object) { - - if (object.getClass() != Resource.class) - throw new IllegalArgumentException("The object with class " + object.getClass() + " is not handled!"); - - Resource resource = (Resource) object; - String type = resource.getType(); - XmlPersistenceDao dao; - switch (type) { - case "MyType": - dao = new MyTypeResourceDao(); - break; - default: - throw new IllegalArgumentException("The resource with type " + type + " is not handled!"); + XmlPersistenceDao dao; + if (object instanceof Resource) { + dao = getDaoBy(object.getClass().getSimpleName(), ((Resource) object).getType()); + } else if (object instanceof Book) { + dao = getDaoBy(Book.class.getSimpleName()); + } else { + String msg = "The object with class {0} is not handled!"; + msg = MessageFormat.format(msg, object.getClass()); + throw new IllegalArgumentException(msg); } - // inject the DAO or SAX handler... - - @SuppressWarnings("unchecked") - XmlPersistenceDao xmlDao = (XmlPersistenceDao) dao; - return xmlDao; + return dao; } + @SuppressWarnings("unchecked") @Override - public XmlPersistenceDao createDaoInstance(String type) { - // TODO Auto-generated method stub - return null; + public XmlPersistenceDao getDaoBy(String type) { + + XmlPersistenceDao dao; + if (TestConstants.TYPE_BOOK.equals(type)) { + XmlIoMode ioMode = getXmlIoMode(); + if (ioMode == XmlIoMode.DOM) { + dao = (XmlPersistenceDao) new BookDomDao(); + } else if (ioMode == XmlIoMode.SAX) { + dao = (XmlPersistenceDao) new BookSaxDao(); + } else { + throw new IllegalArgumentException("The XmlIoMode " + ioMode + " is not yet supported!"); + } + } else { + String msg = "The object with type {0} is not handled!"; + msg = MessageFormat.format(msg, type); + throw new IllegalArgumentException(msg); + } + + return initializeDao(dao); } + @SuppressWarnings("unchecked") @Override - public XmlPersistenceDao createDaoInstance(String type, String subType) { - // TODO Auto-generated method stub - return null; - } + public XmlPersistenceDao getDaoBy(String type, String subType) { + XmlPersistenceDao dao; + if (TestConstants.TYPE_RES.equals(type)) { + XmlIoMode ioMode = getXmlIoMode(); + if (ioMode == XmlIoMode.DOM) { + dao = (XmlPersistenceDao) new ResourceDomDao(subType); + } else if (ioMode == XmlIoMode.SAX) { + dao = (XmlPersistenceDao) new ResourceSaxDao(subType); + } else { + throw new IllegalArgumentException("The XmlIoMode " + ioMode + " is not yet supported!"); + } + } else { + String msg = "The object with type {0} and subType {1} is not handled!"; + msg = MessageFormat.format(msg, type, subType); + throw new IllegalArgumentException(msg); + } + + return initializeDao(dao); + } } From 23cbca6f89065922e1aab1da89071be494381de2 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Sep 2013 20:55:16 +0200 Subject: [PATCH 161/457] [Minor] fixed compile error due to wrong import to test classes --- src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java index 09a21cc29..33ad1db95 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java @@ -31,7 +31,6 @@ import ch.eitchnet.xmlpers.impl.MetadataXmlDao; import ch.eitchnet.xmlpers.impl.XmlPersistenceDomHandler; import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; import ch.eitchnet.xmlpers.impl.XmlPersistenceSaxHandler; -import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; /** * @author Robert von Burg @@ -47,7 +46,7 @@ public abstract class AbstractDaoFactory implements XmlPersistenceDaoFactory { public void initialize(XmlPersistenceFileDao fileDao, Properties properties) { this.fileDao = fileDao; // TODO catch and throw proper exception - String xmlIoModeS = PropertiesHelper.getProperty(properties, TestModelDaoFactory.class.getName(), + String xmlIoModeS = PropertiesHelper.getProperty(properties, AbstractDaoFactory.class.getName(), XmlPersistenceConstants.PROP_XML_IO_MOD, XmlIoMode.SAX.name()); this.xmlIoMode = XmlIoMode.valueOf(xmlIoModeS.toUpperCase()); logger.info("Defaut Xml IO Mode is " + this.xmlIoMode.name()); From fed2bffa3693db84c51e49beb4a96a7642e227b8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Sep 2013 21:41:00 +0200 Subject: [PATCH 162/457] [Major] major rewrite, now implemented SAX and DOM methods. Now DOM implementation is working as well with a test scenario --- .../test/AbstractXmlPersistenceTest.java | 8 +++++-- .../xmlpers/test/XmlPersistenceDomTest.java | 1 - .../test/{Main.java => XmlTestMain.java} | 4 ++-- .../xmlpers/test/impl/BookDomDao.java | 2 +- .../xmlpers/test/impl/ResourceDomDao.java | 22 +++++++++++++------ 5 files changed, 24 insertions(+), 13 deletions(-) rename src/test/java/ch/eitchnet/xmlpers/test/{Main.java => XmlTestMain.java} (98%) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java index 706c40441..aaf424ef4 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java @@ -78,7 +78,11 @@ public abstract class AbstractXmlPersistenceTest { * if something goes wrong */ public static void init(Properties props) throws Exception { + try { + + cleanUpDb(); + String userDir = System.getProperty("user.dir"); String basePath = userDir + "/target/testdb"; File basePathF = new File(basePath); @@ -90,14 +94,14 @@ public abstract class AbstractXmlPersistenceTest { AbstractXmlPersistenceTest.logger.info("Initialized persistence handler."); } catch (Exception e) { - AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); + AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); } } @AfterClass - public static void afterClass() { + public static void cleanUpDb() { String userDir = System.getProperty("user.dir"); String basePath = userDir + "/target/testdb"; File basePathF = new File(basePath); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java index 35448c2c0..7cd7e6c17 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java @@ -30,7 +30,6 @@ import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; /** * @author Robert von Burg */ -@Ignore public class XmlPersistenceDomTest extends AbstractXmlPersistenceTest { /** diff --git a/src/test/java/ch/eitchnet/xmlpers/test/Main.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java similarity index 98% rename from src/test/java/ch/eitchnet/xmlpers/test/Main.java rename to src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java index bc61911d5..9045ae228 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/Main.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java @@ -59,9 +59,9 @@ import ch.eitchnet.xmlpers.test.model.Resource; * @author Robert von Burg * */ -public class Main { +public class XmlTestMain { - private static final Logger logger = LoggerFactory.getLogger(Main.class); + private static final Logger logger = LoggerFactory.getLogger(XmlTestMain.class); private static Resource res; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java index bf3f3480c..36a0f098a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java @@ -63,7 +63,7 @@ public class BookDomDao extends BookDao { public Element serializeToDom(Book book, Document document) { Element element = document.createElement("Book"); - + document.appendChild(element); element.setAttribute("id", Long.toString(book.getId())); element.setAttribute("title", book.getTitle()); element.setAttribute("author", book.getAuthor()); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java index f1cac5029..0267b70eb 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java @@ -25,6 +25,7 @@ import javax.xml.parsers.DocumentBuilder; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; import org.w3c.dom.NodeList; import ch.eitchnet.xmlpers.api.DomUtil; @@ -71,6 +72,7 @@ public class ResourceDomDao extends ResourceDao { public Element serializeToDom(Resource resource, Document document) { Element element = document.createElement("Resource"); + document.appendChild(element); element.setAttribute("id", resource.getId()); element.setAttribute("name", resource.getName()); @@ -79,11 +81,12 @@ public class ResourceDomDao extends ResourceDao { for (String paramId : resource.getParameterKeySet()) { Parameter param = resource.getParameterBy(paramId); Element paramElement = document.createElement("Parameter"); + element.appendChild(paramElement); paramElement.setAttribute("id", param.getId()); paramElement.setAttribute("name", param.getName()); paramElement.setAttribute("type", param.getType()); - paramElement.setAttribute("value", param.getType()); + paramElement.setAttribute("value", param.getValue()); } return element; @@ -97,12 +100,17 @@ public class ResourceDomDao extends ResourceDao { Resource resource = new Resource(id, name, type); - NodeList paramElements = element.getElementsByTagName("Parameter"); - for (int i = 0; i < paramElements.getLength(); i++) { - String paramId = element.getAttribute("id"); - String paramName = element.getAttribute("name"); - String paramType = element.getAttribute("type"); - String paramValue = element.getAttribute("value"); + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node item = children.item(i); + if (!item.getNodeName().equals("Parameter")) + continue; + + Element paramElement = (Element) item; + String paramId = paramElement.getAttribute("id"); + String paramName = paramElement.getAttribute("name"); + String paramType = paramElement.getAttribute("type"); + String paramValue = paramElement.getAttribute("value"); Parameter param = new Parameter(paramId, paramName, paramType, paramValue); resource.addParameter(param); From c7ba5054def21fec477aef68d89265e30dec9f56 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Sep 2013 21:42:04 +0200 Subject: [PATCH 163/457] [Minor] fixed compiler warnings --- .../java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java index 7cd7e6c17..2143e62b4 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java @@ -22,7 +22,6 @@ package ch.eitchnet.xmlpers.test; import java.util.Properties; import org.junit.BeforeClass; -import org.junit.Ignore; import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; From f9c613ca46791765548d592fe711d48caafe049c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Sep 2013 21:43:52 +0200 Subject: [PATCH 164/457] [Minor] fixed compiler warnings --- .../staxutils/IndentingXMLStreamWriter.java | 172 ++++++++++-------- .../helpers/StreamWriterDelegate.java | 160 +++++++++------- 2 files changed, 194 insertions(+), 138 deletions(-) diff --git a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java index 9e09a7be0..dd5438e24 100644 --- a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java +++ b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java @@ -101,21 +101,24 @@ public class IndentingXMLStreamWriter extends StreamWriterDelegate implements In /** newLine followed by copies of indent. */ private char[] linePrefix = null; - public void setIndent(String indent) { + @Override + public void setIndent(String indent) { if (!indent.equals(this.indent)) { this.indent = indent; - linePrefix = null; + this.linePrefix = null; } } - public String getIndent() { - return indent; + @Override + public String getIndent() { + return this.indent; } - public void setNewLine(String newLine) { + @Override + public void setNewLine(String newLine) { if (!newLine.equals(this.newLine)) { this.newLine = newLine; - linePrefix = null; + this.linePrefix = null; } } @@ -127,141 +130,162 @@ public class IndentingXMLStreamWriter extends StreamWriterDelegate implements In try { return System.getProperty("line.separator"); } catch (SecurityException ignored) { + // } return NORMAL_END_OF_LINE; } - public String getNewLine() { - return newLine; + @Override + public String getNewLine() { + return this.newLine; } - public void writeStartDocument() throws XMLStreamException { + @Override + public void writeStartDocument() throws XMLStreamException { beforeMarkup(); - out.writeStartDocument(); + this.out.writeStartDocument(); afterMarkup(); } - public void writeStartDocument(String version) throws XMLStreamException { + @Override + public void writeStartDocument(String version) throws XMLStreamException { beforeMarkup(); - out.writeStartDocument(version); + this.out.writeStartDocument(version); afterMarkup(); } - public void writeStartDocument(String encoding, String version) throws XMLStreamException { + @Override + public void writeStartDocument(String encoding, String version) throws XMLStreamException { beforeMarkup(); - out.writeStartDocument(encoding, version); + this.out.writeStartDocument(encoding, version); afterMarkup(); } - public void writeDTD(String dtd) throws XMLStreamException { + @Override + public void writeDTD(String dtd) throws XMLStreamException { beforeMarkup(); - out.writeDTD(dtd); + this.out.writeDTD(dtd); afterMarkup(); } - public void writeProcessingInstruction(String target) throws XMLStreamException { + @Override + public void writeProcessingInstruction(String target) throws XMLStreamException { beforeMarkup(); - out.writeProcessingInstruction(target); + this.out.writeProcessingInstruction(target); afterMarkup(); } - public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + @Override + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { beforeMarkup(); - out.writeProcessingInstruction(target, data); + this.out.writeProcessingInstruction(target, data); afterMarkup(); } - public void writeComment(String data) throws XMLStreamException { + @Override + public void writeComment(String data) throws XMLStreamException { beforeMarkup(); - out.writeComment(data); + this.out.writeComment(data); afterMarkup(); } - public void writeEmptyElement(String localName) throws XMLStreamException { + @Override + public void writeEmptyElement(String localName) throws XMLStreamException { beforeMarkup(); - out.writeEmptyElement(localName); + this.out.writeEmptyElement(localName); afterMarkup(); } - public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + @Override + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { beforeMarkup(); - out.writeEmptyElement(namespaceURI, localName); + this.out.writeEmptyElement(namespaceURI, localName); afterMarkup(); } - public void writeEmptyElement(String prefix, String localName, String namespaceURI) + @Override + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { beforeMarkup(); - out.writeEmptyElement(prefix, localName, namespaceURI); + this.out.writeEmptyElement(prefix, localName, namespaceURI); afterMarkup(); } - public void writeStartElement(String localName) throws XMLStreamException { + @Override + public void writeStartElement(String localName) throws XMLStreamException { beforeStartElement(); - out.writeStartElement(localName); + this.out.writeStartElement(localName); afterStartElement(); } - public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + @Override + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { beforeStartElement(); - out.writeStartElement(namespaceURI, localName); + this.out.writeStartElement(namespaceURI, localName); afterStartElement(); } - public void writeStartElement(String prefix, String localName, String namespaceURI) + @Override + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { beforeStartElement(); - out.writeStartElement(prefix, localName, namespaceURI); + this.out.writeStartElement(prefix, localName, namespaceURI); afterStartElement(); } - public void writeCharacters(String text) throws XMLStreamException { - out.writeCharacters(text); + @Override + public void writeCharacters(String text) throws XMLStreamException { + this.out.writeCharacters(text); afterData(); } - public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { - out.writeCharacters(text, start, len); + @Override + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + this.out.writeCharacters(text, start, len); afterData(); } - public void writeCData(String data) throws XMLStreamException { - out.writeCData(data); + @Override + public void writeCData(String data) throws XMLStreamException { + this.out.writeCData(data); afterData(); } - public void writeEntityRef(String name) throws XMLStreamException { - out.writeEntityRef(name); + @Override + public void writeEntityRef(String name) throws XMLStreamException { + this.out.writeEntityRef(name); afterData(); } - public void writeEndElement() throws XMLStreamException { + @Override + public void writeEndElement() throws XMLStreamException { beforeEndElement(); - out.writeEndElement(); + this.out.writeEndElement(); afterEndElement(); } - public void writeEndDocument() throws XMLStreamException { + @Override + public void writeEndDocument() throws XMLStreamException { try { - while (depth > 0) { + while (this.depth > 0) { writeEndElement(); // indented } } catch (Exception ignored) { ignored.printStackTrace(); } - out.writeEndDocument(); + this.out.writeEndDocument(); afterEndDocument(); } /** Prepare to write markup, by writing a new line and indentation. */ protected void beforeMarkup() { - int soFar = stack[depth]; + int soFar = this.stack[this.depth]; if ((soFar & WROTE_DATA) == 0 // no data in this scope - && (depth > 0 || soFar != 0)) // not the first line + && (this.depth > 0 || soFar != 0)) // not the first line { try { - writeNewLine(depth); - if (depth > 0 && getIndent().length() > 0) { + writeNewLine(this.depth); + if (this.depth > 0 && getIndent().length() > 0) { afterMarkup(); // indentation was written } } catch (Exception e) { @@ -272,37 +296,37 @@ public class IndentingXMLStreamWriter extends StreamWriterDelegate implements In /** Note that markup or indentation was written. */ protected void afterMarkup() { - stack[depth] |= WROTE_MARKUP; + this.stack[this.depth] |= WROTE_MARKUP; } /** Note that data were written. */ protected void afterData() { - stack[depth] |= WROTE_DATA; + this.stack[this.depth] |= WROTE_DATA; } /** Prepare to start an element, by allocating stack space. */ protected void beforeStartElement() { beforeMarkup(); - if (stack.length <= depth + 1) { + if (this.stack.length <= this.depth + 1) { // Allocate more space for the stack: - int[] newStack = new int[stack.length * 2]; - System.arraycopy(stack, 0, newStack, 0, stack.length); - stack = newStack; + int[] newStack = new int[this.stack.length * 2]; + System.arraycopy(this.stack, 0, newStack, 0, this.stack.length); + this.stack = newStack; } - stack[depth + 1] = 0; // nothing written yet + this.stack[this.depth + 1] = 0; // nothing written yet } /** Note that an element was started. */ protected void afterStartElement() { afterMarkup(); - ++depth; + ++this.depth; } /** Prepare to end an element, by writing a new line and indentation. */ protected void beforeEndElement() { - if (depth > 0 && stack[depth] == WROTE_MARKUP) { // but not data + if (this.depth > 0 && this.stack[this.depth] == WROTE_MARKUP) { // but not data try { - writeNewLine(depth - 1); + writeNewLine(this.depth - 1); } catch (Exception ignored) { ignored.printStackTrace(); } @@ -311,21 +335,21 @@ public class IndentingXMLStreamWriter extends StreamWriterDelegate implements In /** Note that an element was ended. */ protected void afterEndElement() { - if (depth > 0) { - --depth; + if (this.depth > 0) { + --this.depth; } } /** Note that a document was ended. */ protected void afterEndDocument() { - if (stack[depth = 0] == WROTE_MARKUP) { // but not data + if (this.stack[this.depth = 0] == WROTE_MARKUP) { // but not data try { writeNewLine(0); } catch (Exception ignored) { ignored.printStackTrace(); } } - stack[depth] = 0; // start fresh + this.stack[this.depth] = 0; // start fresh } /** Write a line separator followed by indentation. */ @@ -333,19 +357,19 @@ public class IndentingXMLStreamWriter extends StreamWriterDelegate implements In final int newLineLength = getNewLine().length(); final int prefixLength = newLineLength + (getIndent().length() * indentation); if (prefixLength > 0) { - if (linePrefix == null) { - linePrefix = (getNewLine() + getIndent()).toCharArray(); + if (this.linePrefix == null) { + this.linePrefix = (getNewLine() + getIndent()).toCharArray(); } - while (prefixLength > linePrefix.length) { + while (prefixLength > this.linePrefix.length) { // make linePrefix longer: char[] newPrefix = new char[newLineLength - + ((linePrefix.length - newLineLength) * 2)]; - System.arraycopy(linePrefix, 0, newPrefix, 0, linePrefix.length); - System.arraycopy(linePrefix, newLineLength, newPrefix, linePrefix.length, - linePrefix.length - newLineLength); - linePrefix = newPrefix; + + ((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; } - out.writeCharacters(linePrefix, 0, prefixLength); + this.out.writeCharacters(this.linePrefix, 0, prefixLength); } } diff --git a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java index edba1f7f5..49e70d88e 100644 --- a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java +++ b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java @@ -50,136 +50,168 @@ public abstract class StreamWriterDelegate implements XMLStreamWriter { protected XMLStreamWriter out; - public Object getProperty(String name) throws IllegalArgumentException { - return out.getProperty(name); + @Override + public Object getProperty(String name) throws IllegalArgumentException { + return this.out.getProperty(name); } - public NamespaceContext getNamespaceContext() { - return out.getNamespaceContext(); + @Override + public NamespaceContext getNamespaceContext() { + return this.out.getNamespaceContext(); } - public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { - out.setNamespaceContext(context); + @Override + public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { + this.out.setNamespaceContext(context); } - public void setDefaultNamespace(String uri) throws XMLStreamException { - out.setDefaultNamespace(uri); + @Override + public void setDefaultNamespace(String uri) throws XMLStreamException { + this.out.setDefaultNamespace(uri); } - public void writeStartDocument() throws XMLStreamException { - out.writeStartDocument(); + @Override + public void writeStartDocument() throws XMLStreamException { + this.out.writeStartDocument(); } - public void writeStartDocument(String version) throws XMLStreamException { - out.writeStartDocument(version); + @Override + public void writeStartDocument(String version) throws XMLStreamException { + this.out.writeStartDocument(version); } - public void writeStartDocument(String encoding, String version) throws XMLStreamException { - out.writeStartDocument(encoding, version); + @Override + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + this.out.writeStartDocument(encoding, version); } - public void writeDTD(String dtd) throws XMLStreamException { - out.writeDTD(dtd); + @Override + public void writeDTD(String dtd) throws XMLStreamException { + this.out.writeDTD(dtd); } - public void writeProcessingInstruction(String target) throws XMLStreamException { - out.writeProcessingInstruction(target); + @Override + public void writeProcessingInstruction(String target) throws XMLStreamException { + this.out.writeProcessingInstruction(target); } - public void writeProcessingInstruction(String target, String data) throws XMLStreamException { - out.writeProcessingInstruction(target, data); + @Override + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + this.out.writeProcessingInstruction(target, data); } - public void writeComment(String data) throws XMLStreamException { - out.writeComment(data); + @Override + public void writeComment(String data) throws XMLStreamException { + this.out.writeComment(data); } - public void writeEmptyElement(String localName) throws XMLStreamException { - out.writeEmptyElement(localName); + @Override + public void writeEmptyElement(String localName) throws XMLStreamException { + this.out.writeEmptyElement(localName); } - public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { - out.writeEmptyElement(namespaceURI, localName); + @Override + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + this.out.writeEmptyElement(namespaceURI, localName); } - public void writeEmptyElement(String prefix, String localName, String namespaceURI) + @Override + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { - out.writeEmptyElement(prefix, localName, namespaceURI); + this.out.writeEmptyElement(prefix, localName, namespaceURI); } - public void writeStartElement(String localName) throws XMLStreamException { - out.writeStartElement(localName); + @Override + public void writeStartElement(String localName) throws XMLStreamException { + this.out.writeStartElement(localName); } - public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { - out.writeStartElement(namespaceURI, localName); + @Override + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + this.out.writeStartElement(namespaceURI, localName); } - public void writeStartElement(String prefix, String localName, String namespaceURI) + @Override + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { - out.writeStartElement(prefix, localName, namespaceURI); + this.out.writeStartElement(prefix, localName, namespaceURI); } - public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { - out.writeDefaultNamespace(namespaceURI); + @Override + public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { + this.out.writeDefaultNamespace(namespaceURI); } - public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { - out.writeNamespace(prefix, namespaceURI); + @Override + public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { + this.out.writeNamespace(prefix, namespaceURI); } - public String getPrefix(String uri) throws XMLStreamException { - return out.getPrefix(uri); + @Override + public String getPrefix(String uri) throws XMLStreamException { + return this.out.getPrefix(uri); } - public void setPrefix(String prefix, String uri) throws XMLStreamException { - out.setPrefix(prefix, uri); + @Override + public void setPrefix(String prefix, String uri) throws XMLStreamException { + this.out.setPrefix(prefix, uri); } - public void writeAttribute(String localName, String value) throws XMLStreamException { - out.writeAttribute(localName, value); + @Override + public void writeAttribute(String localName, String value) throws XMLStreamException { + this.out.writeAttribute(localName, value); } - public void writeAttribute(String namespaceURI, String localName, String value) + @Override + public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { - out.writeAttribute(namespaceURI, localName, value); + this.out.writeAttribute(namespaceURI, localName, value); } - public void writeAttribute(String prefix, String namespaceURI, String localName, String value) + @Override + public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException { - out.writeAttribute(prefix, namespaceURI, localName, value); + this.out.writeAttribute(prefix, namespaceURI, localName, value); } - public void writeCharacters(String text) throws XMLStreamException { - out.writeCharacters(text); + @Override + public void writeCharacters(String text) throws XMLStreamException { + this.out.writeCharacters(text); } - public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { - out.writeCharacters(text, start, len); + @Override + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + this.out.writeCharacters(text, start, len); } - public void writeCData(String data) throws XMLStreamException { - out.writeCData(data); + @Override + public void writeCData(String data) throws XMLStreamException { + this.out.writeCData(data); } - public void writeEntityRef(String name) throws XMLStreamException { - out.writeEntityRef(name); + @Override + public void writeEntityRef(String name) throws XMLStreamException { + this.out.writeEntityRef(name); } - public void writeEndElement() throws XMLStreamException { - out.writeEndElement(); + @Override + public void writeEndElement() throws XMLStreamException { + this.out.writeEndElement(); } - public void writeEndDocument() throws XMLStreamException { - out.writeEndDocument(); + @Override + public void writeEndDocument() throws XMLStreamException { + this.out.writeEndDocument(); } - public void flush() throws XMLStreamException { - out.flush(); + @Override + public void flush() throws XMLStreamException { + this.out.flush(); } - public void close() throws XMLStreamException { - out.close(); + @Override + public void close() throws XMLStreamException { + this.out.close(); } } From c9cbb534755f02a9764e89a071b395d627941c01 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Sep 2013 21:44:50 +0200 Subject: [PATCH 165/457] [Minor] changed map declaration to be its interface --- .../java/ch/eitchnet/utils/objectfilter/ObjectFilter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index bd9cd4ff2..9eacf63f8 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -25,6 +25,7 @@ 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; @@ -84,8 +85,8 @@ public class ObjectFilter { private static long id = ObjectCache.UNSET; - private final HashMap cache; - private final HashSet keySet; + private final Map cache; + private final Set keySet; /** * Default constructor initializing the filter From e6f8343af77c725c5d5f3d880edbd7672974ea0b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Sep 2013 21:45:40 +0200 Subject: [PATCH 166/457] [Minor] fixed compiler warnings --- .../utils/objectfilter/ObjectFilterTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java index ef30ddee4..ea0b7e6f9 100644 --- a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java +++ b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java @@ -19,14 +19,14 @@ */ package ch.eitchnet.utils.objectfilter; -import java.util.List; - -import org.junit.Test; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.List; + +import org.junit.Test; + /** * @author Robert von Burg * @@ -412,7 +412,7 @@ public class ObjectFilterTest { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); - result = prime * result + id; + result = prime * result + this.id; return result; } @@ -427,7 +427,7 @@ public class ObjectFilterTest { TestObject other = (TestObject) obj; if (!getOuterType().equals(other.getOuterType())) return false; - if (id != other.id) + if (this.id != other.id) return false; return true; } From 31c6acec6c335468793a79fecec7767011716e1d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Sep 2013 21:57:42 +0200 Subject: [PATCH 167/457] [Minor] fixed failing test due to different formatting of xml writing --- .../java/ch/eitchnet/privilege/test/XmlTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 13c684834..9ff57422d 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -33,6 +33,7 @@ import java.util.Set; import junit.framework.Assert; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; @@ -72,7 +73,8 @@ public class XmlTest { */ @BeforeClass public static void init() throws Exception { - destroy(); + + cleanUp(); File tmpDir = new File("target/test"); if (tmpDir.exists()) @@ -80,8 +82,8 @@ public class XmlTest { tmpDir.mkdirs(); } - //@AfterClass - public static void destroy() throws Exception { + @AfterClass + public static void cleanUp() throws Exception { File tmpDir = new File("target/test"); if (!tmpDir.exists()) @@ -135,7 +137,6 @@ public class XmlTest { Map encryptionHandlerParameterMap = new HashMap(); Map persistenceHandlerParameterMap = new HashMap(); - // TODO ask other questions... parameterMap.put("autoPersistOnPasswordChange", "true"); encryptionHandlerParameterMap.put("hashAlgorithm", "SHA-256"); persistenceHandlerParameterMap.put("basePath", TARGET_TEST); @@ -155,7 +156,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - Assert.assertEquals("22D4BA39605D49C758184D9BD63BEAE5CCF8786F3DABBAB45CD9F59C2AFBCBD0", fileHash); + Assert.assertEquals("2ABD3442EEC8BCEC5BEE365AAB6DB2FD4E1789325425CB1E017E900582525685", fileHash); } @Test @@ -217,6 +218,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - Assert.assertEquals("9007F172BBD7BA51BA3E67199CE0AFCBC8645AF0AC02028ABE54BA6A2FC134B0", fileHash); + Assert.assertEquals("A2127D20A61E00BCDBB61569CD2B200C4F0F111C972BAC3B1E54DF3B2FCDC8BE", fileHash); } } From daf34735ae00c60738c761293cdc2be4fa1068c0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 16 Sep 2013 07:46:57 +0200 Subject: [PATCH 168/457] [Project] changed Java version frm 1.6 to 1.7 in pom.xml --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 456a807fb..1806f64b9 100644 --- a/pom.xml +++ b/pom.xml @@ -124,8 +124,8 @@ maven-compiler-plugin 3.0 - 1.6 - 1.6 + 1.7 + 1.7 From 3d8fa992d41cbe2ba330837a150e8b9384b9d6dc Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 21 Sep 2013 09:03:05 +0200 Subject: [PATCH 169/457] [Project] moved some project details to parent ch.eitchnet.parent This simplifies project setup and maintenance --- pom.xml | 127 ++++---------------------------------------------------- 1 file changed, 7 insertions(+), 120 deletions(-) diff --git a/pom.xml b/pom.xml index fdef06dff..6a32d001d 100644 --- a/pom.xml +++ b/pom.xml @@ -2,113 +2,34 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - ch.eitchnet + + ch.eitchnet + ch.eitchnet.parent + 0.0.1-SNAPSHOT + ../ch.eitchnet.parent/pom.xml + + ch.eitchnet.privilege jar 0.2.0-SNAPSHOT ch.eitchnet.privilege https://github.com/eitch/ch.eitchnet.privilege - - UTF-8 - - - - - 2011 - - - GNU Lesser General Public License - http://www.gnu.org/licenses/lgpl.html - repo - - - - eitchnet.ch - http://blog.eitchnet.ch - - - - eitch - Robert von Vurg - eitch@eitchnet.ch - http://blog.eitchnet.ch - eitchnet.ch - http://blog.eitchnet.ch - - architect - developer - - +1 - - http://localhost - - - - Github Issues https://github.com/eitch/ch.eitchnet.privilege/issues - - scm:git:https://github.com/eitch/ch.eitchnet.privilege.git scm:git:git@github.com:eitch/ch.eitchnet.privilege.git https://github.com/eitch/ch.eitchnet.privilege - - - - - deployment - Internal Releases - http://nexus.eitchnet.ch/content/repositories/releases/ - - - deployment - Internal Releases - http://nexus.eitchnet.ch/content/repositories/snapshots/ - - - - - junit - junit - 4.10 - test - ch.eitchnet ch.eitchnet.utils - 0.2.0-SNAPSHOT - - - org.slf4j - slf4j-api - 1.7.2 - - - org.slf4j - slf4j-log4j12 - 1.7.2 - test @@ -117,60 +38,26 @@ org.apache.maven.plugins maven-eclipse-plugin - 2.9 - - true - true - org.apache.maven.plugins maven-compiler-plugin - 3.0 - - 1.7 - 1.7 - org.apache.maven.plugins maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar-no-fork - - - org.apache.maven.plugins maven-jar-plugin - 2.4 - - - - true - true - - - - org.apache.maven.plugins maven-site-plugin - 2.3 - - UTF-8 - From a68c44c9fdf3212004a33b838bcc9b72a12f1e95 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 21 Sep 2013 09:11:34 +0200 Subject: [PATCH 170/457] [Project] moved some project details to parent ch.eitchnet.parent This simplifies project setup and maintenance --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 6a32d001d..bf615f1ef 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ 0.2.0-SNAPSHOT ch.eitchnet.privilege https://github.com/eitch/ch.eitchnet.privilege + 2011 Github Issues From a5ffe0a6cc4022b85331b207e3e6a882486f8482 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 21 Sep 2013 09:11:40 +0200 Subject: [PATCH 171/457] [Project] moved some project details to parent ch.eitchnet.parent This simplifies project setup and maintenance --- pom.xml | 140 +++----------------------------------------------------- 1 file changed, 7 insertions(+), 133 deletions(-) diff --git a/pom.xml b/pom.xml index 1806f64b9..03a84e647 100644 --- a/pom.xml +++ b/pom.xml @@ -2,110 +2,33 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - ch.eitchnet + + ch.eitchnet + ch.eitchnet.parent + 0.0.1-SNAPSHOT + ../ch.eitchnet.parent/pom.xml + + ch.eitchnet.utils jar 0.2.0-SNAPSHOT ch.eitchnet.utils These utils contain project independent helper classes and utilities for reuse https://github.com/eitch/ch.eitchnet.utils - - - UTF-8 - - - - 2011 - - - GNU Lesser General Public License - http://www.gnu.org/licenses/lgpl.html - repo - - - - eitchnet.ch - http://blog.eitchnet.ch - - - - eitch - Robert von Vurg - eitch@eitchnet.ch - http://blog.eitchnet.ch - eitchnet.ch - http://blog.eitchnet.ch - - architect - developer - - +1 - - http://localhost - - - Github Issues https://github.com/eitch/ch.eitchnet.utils/issues - - scm:git:https://github.com/eitch/ch.eitchnet.utils.git scm:git:git@github.com:eitch/ch.eitchnet.utils.git https://github.com/eitch/ch.eitchnet.utils - - - - - deployment - Internal Releases - http://nexus.eitchnet.ch/content/repositories/releases/ - - - deployment - Internal Releases - http://nexus.eitchnet.ch/content/repositories/snapshots/ - - - - - junit - junit - 4.10 - test - - - org.slf4j - slf4j-api - 1.7.2 - - - org.slf4j - slf4j-log4j12 - 1.7.2 - test - @@ -113,80 +36,31 @@ org.apache.maven.plugins maven-eclipse-plugin - 2.9 - - true - true - org.apache.maven.plugins maven-compiler-plugin - 3.0 - - 1.7 - 1.7 - org.apache.maven.plugins maven-source-plugin - 2.1.2 - - - attach-sources - package - - jar - - - org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - deploy - jar - - org.apache.maven.plugins maven-jar-plugin - 2.4 - - - - true - true - - - org.apache.maven.plugins maven-site-plugin - 2.3 - - UTF-8 - org.apache.maven.plugins maven-deploy-plugin - 2.7 - - - deploy - deploy - deploy - - From b7a4762a059da9dd04bfd6677a29ce61b3f7b12d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 21 Sep 2013 10:03:27 +0200 Subject: [PATCH 172/457] [Minor] Made Base encoding/decoding performance tests configurable To enable them, set the system property ch.eitchnet.utils.test.runPerfTests=true --- .../utils/helper/BaseDecodingTest.java | 116 +++++++++++++----- .../utils/helper/BaseEncodingTest.java | 109 +++++++++++----- 2 files changed, 157 insertions(+), 68 deletions(-) diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java index 78a9ea566..fee6c668e 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java @@ -30,6 +30,7 @@ import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; import junit.framework.Assert; import org.junit.Test; +import org.junit.runners.JUnit4; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,8 +39,16 @@ import org.slf4j.LoggerFactory; * */ public class BaseDecodingTest { + public static final String PROP_RUN_PERF_TESTS = "ch.eitchnet.utils.test.runPerfTests"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(BaseDecodingTest.class); + public static boolean isSkipPerfTests() { + String context = BaseDecodingTest.class.getSimpleName(); + String key = PROP_RUN_PERF_TESTS; + boolean runPerfTests = PropertiesHelper.getPropertyBool(context, key, Boolean.FALSE); + return !runPerfTests; + } + @Test public void testBase64() { Assert.assertEquals("", fromBase64("")); @@ -49,6 +58,59 @@ public class BaseDecodingTest { Assert.assertEquals("foob", fromBase64("Zm9vYg==")); Assert.assertEquals("fooba", fromBase64("Zm9vYmE=")); Assert.assertEquals("foobar", fromBase64("Zm9vYmFy")); + } + + @Test + public void testBase32() { + Assert.assertEquals("", fromBase32("")); + Assert.assertEquals("f", fromBase32("MY======")); + Assert.assertEquals("fo", fromBase32("MZXQ====")); + Assert.assertEquals("foo", fromBase32("MZXW6===")); + Assert.assertEquals("foob", fromBase32("MZXW6YQ=")); + Assert.assertEquals("fooba", fromBase32("MZXW6YTB")); + Assert.assertEquals("foobar", fromBase32("MZXW6YTBOI======")); + } + + @Test + public void testBase32Hex() { + Assert.assertEquals("", fromBase32Hex("")); + Assert.assertEquals("f", fromBase32Hex("CO======")); + Assert.assertEquals("fo", fromBase32Hex("CPNG====")); + Assert.assertEquals("foo", fromBase32Hex("CPNMU===")); + Assert.assertEquals("foob", fromBase32Hex("CPNMUOG=")); + Assert.assertEquals("fooba", fromBase32Hex("CPNMUOJ1")); + Assert.assertEquals("foobar", fromBase32Hex("CPNMUOJ1E8======")); + } + + @Test + public void testBase32Dmedia() { + + Assert.assertEquals("", fromBase32Dmedia("")); + Assert.assertEquals("binary foo", fromBase32Dmedia("FCNPVRELI7J9FUUI")); + Assert.assertEquals("f", fromBase32Dmedia("FR======")); + Assert.assertEquals("fo", fromBase32Dmedia("FSQJ====")); + Assert.assertEquals("foo", fromBase32Dmedia("FSQPX===")); + Assert.assertEquals("foob", fromBase32Dmedia("FSQPXRJ=")); + Assert.assertEquals("fooba", fromBase32Dmedia("FSQPXRM4")); + Assert.assertEquals("foobar", fromBase32Dmedia("FSQPXRM4HB======")); + } + + @Test + public void testBase16() { + Assert.assertEquals("", fromBase16("")); + Assert.assertEquals("f", fromBase16("66")); + Assert.assertEquals("fo", fromBase16("666F")); + Assert.assertEquals("foo", fromBase16("666F6F")); + Assert.assertEquals("foob", fromBase16("666F6F62")); + Assert.assertEquals("fooba", fromBase16("666F6F6261")); + Assert.assertEquals("foobar", fromBase16("666F6F626172")); + } + + @Test + public void testBase64Perf() { + if (isSkipPerfTests()) { + return; + } byte[] bytes = new byte[1024 * 1024]; for (int i = 0; i < bytes.length; i++) { @@ -63,14 +125,11 @@ public class BaseDecodingTest { } @Test - public void testBase32() { - Assert.assertEquals("", fromBase32("")); - Assert.assertEquals("f", fromBase32("MY======")); - Assert.assertEquals("fo", fromBase32("MZXQ====")); - Assert.assertEquals("foo", fromBase32("MZXW6===")); - Assert.assertEquals("foob", fromBase32("MZXW6YQ=")); - Assert.assertEquals("fooba", fromBase32("MZXW6YTB")); - Assert.assertEquals("foobar", fromBase32("MZXW6YTBOI======")); + public void testBase32Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } byte[] bytes = new byte[1024 * 1024]; for (int i = 0; i < bytes.length; i++) { @@ -85,14 +144,11 @@ public class BaseDecodingTest { } @Test - public void testBase32Hex() { - Assert.assertEquals("", fromBase32Hex("")); - Assert.assertEquals("f", fromBase32Hex("CO======")); - Assert.assertEquals("fo", fromBase32Hex("CPNG====")); - Assert.assertEquals("foo", fromBase32Hex("CPNMU===")); - Assert.assertEquals("foob", fromBase32Hex("CPNMUOG=")); - Assert.assertEquals("fooba", fromBase32Hex("CPNMUOJ1")); - Assert.assertEquals("foobar", fromBase32Hex("CPNMUOJ1E8======")); + public void testBase32HexPerf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } byte[] bytes = new byte[1024 * 1024]; for (int i = 0; i < bytes.length; i++) { @@ -107,16 +163,11 @@ public class BaseDecodingTest { } @Test - public void testBase32Dmedia() { - - Assert.assertEquals("", fromBase32Dmedia("")); - Assert.assertEquals("binary foo", fromBase32Dmedia("FCNPVRELI7J9FUUI")); - Assert.assertEquals("f", fromBase32Dmedia("FR======")); - Assert.assertEquals("fo", fromBase32Dmedia("FSQJ====")); - Assert.assertEquals("foo", fromBase32Dmedia("FSQPX===")); - Assert.assertEquals("foob", fromBase32Dmedia("FSQPXRJ=")); - Assert.assertEquals("fooba", fromBase32Dmedia("FSQPXRM4")); - Assert.assertEquals("foobar", fromBase32Dmedia("FSQPXRM4HB======")); + public void testBase32DmediaPerf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } long start = System.nanoTime(); byte[] bytes = new byte[1024 * 1024]; @@ -128,14 +179,11 @@ public class BaseDecodingTest { } @Test - public void testBase16() { - Assert.assertEquals("", fromBase16("")); - Assert.assertEquals("f", fromBase16("66")); - Assert.assertEquals("fo", fromBase16("666F")); - Assert.assertEquals("foo", fromBase16("666F6F")); - Assert.assertEquals("foob", fromBase16("666F6F62")); - Assert.assertEquals("fooba", fromBase16("666F6F6261")); - Assert.assertEquals("foobar", fromBase16("666F6F626172")); + public void testBase16Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } byte[] bytes = new byte[1024 * 1024]; for (int i = 0; i < bytes.length; i++) { diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java index 7a2b3052f..7bca92e75 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -21,10 +21,12 @@ */ package ch.eitchnet.utils.helper; +import static ch.eitchnet.utils.helper.BaseDecodingTest.PROP_RUN_PERF_TESTS; +import static ch.eitchnet.utils.helper.BaseDecodingTest.isSkipPerfTests; import static ch.eitchnet.utils.helper.BaseEncoding.toBase16; import static ch.eitchnet.utils.helper.BaseEncoding.toBase32; -import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Dmedia; +import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; import static ch.eitchnet.utils.helper.BaseEncoding.toBase64; import junit.framework.Assert; @@ -49,14 +51,6 @@ public class BaseEncodingTest { Assert.assertEquals("Zm9vYg==", toBase64("foob")); Assert.assertEquals("Zm9vYmE=", toBase64("fooba")); Assert.assertEquals("Zm9vYmFy", toBase64("foobar")); - - long start = System.nanoTime(); - byte[] bytes = new byte[1024 * 1024]; - for (int i = 0; i < 200; i++) { - toBase64(bytes); - } - long end = System.nanoTime(); - logger.info("Encoding 200MB Base64 took " + StringHelper.formatNanoDuration(end - start)); } @Test @@ -68,14 +62,6 @@ public class BaseEncodingTest { Assert.assertEquals("MZXW6YQ=", toBase32("foob")); Assert.assertEquals("MZXW6YTB", toBase32("fooba")); Assert.assertEquals("MZXW6YTBOI======", toBase32("foobar")); - - long start = System.nanoTime(); - byte[] bytes = new byte[1024 * 1024]; - for (int i = 0; i < 200; i++) { - toBase32(bytes); - } - long end = System.nanoTime(); - logger.info("Encoding 200MB Base32 took " + StringHelper.formatNanoDuration(end - start)); } @Test @@ -87,19 +73,10 @@ public class BaseEncodingTest { Assert.assertEquals("CPNMUOG=", toBase32Hex("foob")); Assert.assertEquals("CPNMUOJ1", toBase32Hex("fooba")); Assert.assertEquals("CPNMUOJ1E8======", toBase32Hex("foobar")); - - long start = System.nanoTime(); - byte[] bytes = new byte[1024 * 1024]; - for (int i = 0; i < 200; i++) { - toBase32Hex(bytes); - } - long end = System.nanoTime(); - logger.info("Encoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); } @Test public void testBase32Dmedia() { - Assert.assertEquals("", toBase32Dmedia("")); Assert.assertEquals("FCNPVRELI7J9FUUI", toBase32Dmedia("binary foo")); Assert.assertEquals("FR======", toBase32Dmedia("f")); @@ -108,14 +85,6 @@ public class BaseEncodingTest { Assert.assertEquals("FSQPXRJ=", toBase32Dmedia("foob")); Assert.assertEquals("FSQPXRM4", toBase32Dmedia("fooba")); Assert.assertEquals("FSQPXRM4HB======", toBase32Dmedia("foobar")); - - long start = System.nanoTime(); - byte[] bytes = new byte[1024 * 1024]; - for (int i = 0; i < 200; i++) { - toBase32Hex(bytes); - } - long end = System.nanoTime(); - logger.info("Encoding 200MB Base32Dmedia took " + StringHelper.formatNanoDuration(end - start)); } @Test @@ -127,6 +96,78 @@ public class BaseEncodingTest { Assert.assertEquals("666F6F62", toBase16("foob")); Assert.assertEquals("666F6F6261", toBase16("fooba")); Assert.assertEquals("666F6F626172", toBase16("foobar")); + } + + @Test + public void testBase64Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase64(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base64 took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32 took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32HexPerf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32Hex took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase32DmediaPerf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } + + long start = System.nanoTime(); + byte[] bytes = new byte[1024 * 1024]; + for (int i = 0; i < 200; i++) { + toBase32Hex(bytes); + } + long end = System.nanoTime(); + logger.info("Encoding 200MB Base32Dmedia took " + StringHelper.formatNanoDuration(end - start)); + } + + @Test + public void testBase16Perf() { + if (isSkipPerfTests()) { + logger.info("Not running performance tests as not enabled by system property " + PROP_RUN_PERF_TESTS); + return; + } long start = System.nanoTime(); byte[] bytes = new byte[1024 * 1024]; From 6f0c623d20848d0c22579e8d653b654ebcd18af6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 21 Sep 2013 10:07:31 +0200 Subject: [PATCH 173/457] [Project] added dependency of ch.eitchnet.utils:0.2.0-SNAPSHOT --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index bf615f1ef..bd92a91a2 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ ch.eitchnet ch.eitchnet.utils + 0.2.0-SNAPSHOT From 15a118e0e592cac38a18981852b36e18fa0270a4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 27 Sep 2013 20:23:48 +0200 Subject: [PATCH 174/457] [Major] major rewrite, re-thinking the API it is still not really nice how one has to use the persistence layer, thus in the test folder i am currently implementing a completely new API where simple use is central - but not forgetting SAX and DOM being used transparently. --- .../ch/eitchnet/xmlpers/api/DaoContext.java | 42 ++++ .../ch/eitchnet/xmlpers/api/IoContext.java | 31 +++ .../ch/eitchnet/xmlpers/api/XmlIoMode.java | 2 +- .../impl/XmlPersistenceFileIoHandler.java | 37 +++ .../impl/XmlPersistencePathBuilder.java | 101 ++++++++ .../test/AbstractXmlPersistenceTest.java | 96 ++------ .../ch/eitchnet/xmlpers/test/XmlTestMain.java | 27 +-- .../xmlpers/test/impl/rewrite/DomParser.java | 35 +++ .../xmlpers/test/impl/rewrite/FileDao.java | 61 +++++ .../test/impl/rewrite/FileDaoTest.java | 142 +++++++++++ .../xmlpers/test/impl/rewrite/FileIo.java | 222 ++++++++++++++++++ .../xmlpers/test/impl/rewrite/ObjectDao.java | 51 ++++ .../test/impl/rewrite/ParserFactory.java | 29 +++ .../test/impl/rewrite/PersistenceContext.java | 83 +++++++ .../test/impl/rewrite/ResourceDomParser.java | 105 +++++++++ .../impl/rewrite/ResourceParserFactory.java | 37 +++ .../test/impl/rewrite/ResourceSaxParser.java | 93 ++++++++ .../xmlpers/test/impl/rewrite/SaxParser.java | 39 +++ .../xmlpers/test/model/ModelBuilder.java | 117 +++++++++ 19 files changed, 1243 insertions(+), 107 deletions(-) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/DaoContext.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/IoContext.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileIoHandler.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DomParser.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ParserFactory.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContext.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/SaxParser.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/DaoContext.java b/src/main/java/ch/eitchnet/xmlpers/api/DaoContext.java new file mode 100644 index 000000000..0128f7bed --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/DaoContext.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +/** + * @author Robert von Burg + * + * @param + */ +public interface DaoContext { + + public String getType(); + + public String getSubType(); + + public String getId(); + + public boolean hasSubType(); + + public T getObject(); + + public U getIoContext(); +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/IoContext.java b/src/main/java/ch/eitchnet/xmlpers/api/IoContext.java new file mode 100644 index 000000000..641eb3914 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/IoContext.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +/** + * @author Robert von Burg + * + */ +public interface IoContext { + + // marker interface +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java index e0a89f617..99592ff6c 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java @@ -27,5 +27,5 @@ package ch.eitchnet.xmlpers.api; */ public enum XmlIoMode { - DOM, SAX; + DEFAULT, DOM, SAX; } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileIoHandler.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileIoHandler.java new file mode 100644 index 000000000..35f377dd9 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileIoHandler.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.impl; + +import java.io.File; + +import ch.eitchnet.xmlpers.api.DaoContext; + +/** + * @author Robert von Burg + * + */ +public interface XmlPersistenceFileIoHandler { + + public void read(DaoContext context, File file); + + public void write(DaoContext context, File file); +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java index 4c0b3c9af..3e0c84535 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java @@ -31,6 +31,7 @@ import ch.eitchnet.utils.helper.PropertiesHelper; import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.test.impl.rewrite.PersistenceContext; /** * @author Robert von Burg @@ -44,6 +45,8 @@ public class XmlPersistencePathBuilder { private final boolean verbose; private final String basePath; + private File path; + public XmlPersistencePathBuilder(Properties properties) { // get properties @@ -290,4 +293,102 @@ public class XmlPersistencePathBuilder { throw new XmlPersistenceException(MessageFormat.format(msg, args)); } } + + private void logPath(String operation, File path, PersistenceContext context) { + if (this.verbose) { + String msg; + if (StringHelper.isEmpty(context.getSubType())) { + msg = "Path for operation {0} for {1} / {2} / is at {3}"; + msg = MessageFormat.format(msg, operation, context.getType(), context.getId(), path.getAbsolutePath()); + } else { + msg = "Path for operation {0} for {1} / {2} / {3} / is at {4}"; + msg = MessageFormat.format(msg, operation, context.getType(), context.getSubType(), context.getId(), + path.getAbsolutePath()); + } + } + } + + private void createMissingParents(File path, PersistenceContext context) { + File parentFile = path.getParentFile(); + if (!parentFile.exists() && !parentFile.mkdirs()) { + String msg; + if (StringHelper.isEmpty(context.getSubType())) { + msg = "Could not create parent path for {0} / {1} / at {2}"; + msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); + } else { + msg = "Could not create parent path for {0} / {1} / {2} at {3}"; + msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), + path.getAbsolutePath()); + } + + throw new XmlPersistenceException(msg); + } + } + + private void assertPathExists(File path, PersistenceContext context) { + if (!path.exists()) { + String msg; + if (StringHelper.isEmpty(context.getSubType())) { + msg = "Persistence unit does not exist for {0} / {1} at {2}"; + msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); + } else { + msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; + msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), + path.getAbsolutePath()); + } + + throw new XmlPersistenceException(msg); + } + } + + private void assertPathNotExists(File path, PersistenceContext context) { + if (path.exists()) { + String msg; + if (StringHelper.isEmpty(context.getSubType())) { + msg = "Persistence unit already exists for {0} / {1} at {2}"; + msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); + } else { + msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; + msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), + path.getAbsolutePath()); + } + + throw new XmlPersistenceException(msg); + } + } + + public File getCreatePath(PersistenceContext context) { + File path = getPath(context); + logPath("CREATE", path, context); + assertPathNotExists(path, context); + createMissingParents(path, context); + return path; + } + + public File getDeletePath(PersistenceContext context) { + File path = getPath(context); + logPath("DELETE", path, context); + assertPathExists(path, context); + return path; + } + + public File getUpdatePath(PersistenceContext context) { + File path = getPath(context); + logPath("UPDATE", path, context); + assertPathExists(path, context); + return path; + } + + public File getReadPath(PersistenceContext context) { + File path = getPath(context); + logPath("READ", path, context); + if (!path.exists()) + return null; + return path; + } + + private File getPath(PersistenceContext context) { + File path = new File(getPathAsString(context.getType(), context.getSubType(), context.getId())); + return path; + } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java index aaf424ef4..a5224b98a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java @@ -19,6 +19,18 @@ */ package ch.eitchnet.xmlpers.test; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.BOOK_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE_INEXISTANT; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBook; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBookUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createBook; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateBook; + import java.io.File; import java.util.List; import java.util.Properties; @@ -40,7 +52,7 @@ import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; import ch.eitchnet.xmlpers.impl.XmlPersistenceHandlerImpl; import ch.eitchnet.xmlpers.test.impl.Book; import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.Parameter; +import ch.eitchnet.xmlpers.test.model.ModelBuilder; import ch.eitchnet.xmlpers.test.model.Resource; /** @@ -49,28 +61,7 @@ import ch.eitchnet.xmlpers.test.model.Resource; public abstract class AbstractXmlPersistenceTest { protected static final Logger logger = LoggerFactory.getLogger(AbstractXmlPersistenceTest.class.getName()); - - protected static final String RES_TYPE = "@subType"; - protected static final String RES_TYPE_INEXISTANT = "@inexistant"; - protected static final String RES_NAME = "@name"; - protected static final String RES_NAME_MODIFIED = "@name_modified"; - protected static final String RES_ID = "@id"; - - protected static final String PARAM_TYPE = "@paramType"; - protected static final String PARAM_NAME = "@paramName"; - protected static final String PARAM_ID = "@paramId"; - protected static final String PARAM_VALUE_1 = "@paramValue1"; - protected static final String PARAM_VALUE_2 = "@paramValue2"; - - protected static final long BOOK_ID = 10L; - protected static final String BOOK_TITLE = "Nick Hornby"; - protected static final String BOOK_AUTHOR = "A long way down"; - protected static final String BOOK_PRESS_1 = "Some press"; - protected static final String BOOK_PRESS_2 = "Another press"; - protected static final double BOOK_PRICE = 45.55D; - protected static XmlPersistenceHandler persistenceHandler; - protected XmlPersistenceTransaction tx; /** @@ -141,7 +132,7 @@ public abstract class AbstractXmlPersistenceTest { assertBook(persistedBook); // update - persistedBook.setPress(BOOK_PRESS_2); + updateBook(persistedBook); this.tx.update(persistedBook); this.tx.commit(); @@ -236,8 +227,7 @@ public abstract class AbstractXmlPersistenceTest { assertResource(resource); // modify the instance - resource.setName(RES_NAME_MODIFIED); - resource.getParameterBy(PARAM_ID).setValue(PARAM_VALUE_2); + ModelBuilder.updateResource(resource); // update the instance this.tx.update(resource); @@ -379,60 +369,4 @@ public abstract class AbstractXmlPersistenceTest { this.tx.commit(); Assert.assertEquals("Expected size = 0, found: " + size, 0, size); } - - private Book createBook() { - Book book = new Book(BOOK_ID, BOOK_TITLE, BOOK_AUTHOR, BOOK_PRESS_1, BOOK_PRICE); - return book; - } - - private void assertBook(Book book) { - Assert.assertNotNull(book); - Assert.assertEquals(BOOK_ID, book.getId().longValue()); - Assert.assertEquals(BOOK_TITLE, book.getTitle()); - Assert.assertEquals(BOOK_AUTHOR, book.getAuthor()); - Assert.assertEquals(BOOK_PRESS_1, book.getPress()); - Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); - } - - private void assertBookUpdated(Book book) { - Assert.assertNotNull(book); - Assert.assertEquals(BOOK_ID, book.getId().longValue()); - Assert.assertEquals(BOOK_TITLE, book.getTitle()); - Assert.assertEquals(BOOK_AUTHOR, book.getAuthor()); - Assert.assertEquals(BOOK_PRESS_2, book.getPress()); - Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); - } - - private Resource createResource() { - Resource resource = new Resource(RES_ID, RES_NAME, RES_TYPE); - Parameter param = new Parameter(PARAM_ID, PARAM_NAME, PARAM_TYPE, PARAM_VALUE_1); - resource.addParameter(param); - return resource; - } - - private void assertResource(Resource resource) { - Assert.assertNotNull(resource); - Assert.assertEquals(RES_ID, resource.getId()); - Assert.assertEquals(RES_NAME, resource.getName()); - Assert.assertEquals(RES_TYPE, resource.getType()); - Parameter param = resource.getParameterBy(PARAM_ID); - Assert.assertNotNull(param); - Assert.assertEquals(PARAM_ID, param.getId()); - Assert.assertEquals(PARAM_NAME, param.getName()); - Assert.assertEquals(PARAM_TYPE, param.getType()); - Assert.assertEquals(PARAM_VALUE_1, param.getValue()); - } - - private void assertResourceUpdated(Resource resource) { - Assert.assertNotNull(resource); - Assert.assertEquals(RES_ID, resource.getId()); - Assert.assertEquals(RES_NAME_MODIFIED, resource.getName()); - Assert.assertEquals(RES_TYPE, resource.getType()); - Parameter param = resource.getParameterBy(PARAM_ID); - Assert.assertNotNull(param); - Assert.assertEquals(PARAM_ID, param.getId()); - Assert.assertEquals(PARAM_NAME, param.getName()); - Assert.assertEquals(PARAM_TYPE, param.getType()); - Assert.assertEquals(PARAM_VALUE_2, param.getValue()); - } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java index 9045ae228..2575aa3c1 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java @@ -52,6 +52,7 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.xmlpers.test.model.ModelBuilder; import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; @@ -67,31 +68,7 @@ public class XmlTestMain { public static void main(String[] args) throws Exception { - res = new Resource(); - res.setId("id1"); - res.setName("name1"); - res.setType("type1"); - - Parameter param1 = new Parameter(); - param1.setId("paramId1"); - param1.setName("paramName1"); - param1.setType("paramType1"); - param1.setValue("paramValue1"); - res.addParameter(param1); - - Parameter param2 = new Parameter(); - param2.setId("paramId2"); - param2.setName("paramName2"); - param2.setType("paramType2"); - param2.setValue("paramValue2"); - res.addParameter(param2); - - Parameter param3 = new Parameter(); - param3.setId("paramId3"); - param3.setName("paramName3"); - param3.setType("paramType3"); - param3.setValue("paramValue3"); - res.addParameter(param3); + res = ModelBuilder.createResource(); logger.info("Writing Res:\n" + res); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DomParser.java new file mode 100644 index 000000000..d5850ef4b --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DomParser.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import org.w3c.dom.Document; + +public interface DomParser { + + public T getObject(); + + public void setObject(T object); + + public Document toDom(); + + public void fromDom(Document document); +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java new file mode 100644 index 000000000..b0fadcc77 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.io.File; + +import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; + +public class FileDao { + private XmlPersistencePathBuilder pathBuilder; + + public FileDao(XmlPersistencePathBuilder pathBuilder) { + this.pathBuilder = pathBuilder; + } + + void performCreate(PersistenceContext context) { + File path = this.pathBuilder.getCreatePath(context); + FileIo fileIo = new FileIo(path); + fileIo.write(context); + } + + void performRead(PersistenceContext context) { + File path = this.pathBuilder.getReadPath(context); + if (path == null) { + context.setObject(null); + } else { + FileIo fileIo = new FileIo(path); + fileIo.read(context); + } + } + + void performUpdate(PersistenceContext context) { + File path = this.pathBuilder.getUpdatePath(context); + FileIo fileIo = new FileIo(path); + fileIo.write(context); + } + + void performDelete(PersistenceContext context) { + File path = this.pathBuilder.getDeletePath(context); + path.delete(); + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java new file mode 100644 index 000000000..e8f51a178 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.util.Properties; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.xmlpers.api.XmlIoMode; +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class FileDaoTest { + + private static final String TEST_PATH = "target/dbTest"; + + private FileDao fileDao; + private Properties properties; + + @BeforeClass + public static void beforeClass() { + File file = new File(TEST_PATH).getAbsoluteFile(); + if (file.exists() && file.isDirectory()) + if (!FileHelper.deleteFiles(file.listFiles(), true)) + throw new RuntimeException("Could not clean up path " + file.getAbsolutePath()); + + if (!file.exists() && !file.mkdir()) + throw new RuntimeException("Failed to create path " + file); + + File domFile = new File(file, "dom"); + if (!domFile.mkdir()) + throw new RuntimeException("Failed to create path " + domFile); + + File saxFile = new File(file, "sax"); + if (!saxFile.mkdir()) + throw new RuntimeException("Failed to create path " + saxFile); + } + + @Before + public void setUp() { + this.properties = new Properties(); + this.properties.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); + } + + @Test + public void testCrudSax() { + this.properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, TEST_PATH + "/sax/"); + XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(this.properties); + this.fileDao = new FileDao(pathBuilder); + + Resource resource = createResource(); + assertResource(resource); + XmlIoMode ioMode = XmlIoMode.SAX; + PersistenceContext context = createPersistenceContext(resource, ioMode); + testCrud(context); + } + + @Test + public void testCrudDom() { + this.properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, TEST_PATH + "/dom/"); + XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(this.properties); + this.fileDao = new FileDao(pathBuilder); + + Resource resource = createResource(); + assertResource(resource); + XmlIoMode ioMode = XmlIoMode.DOM; + PersistenceContext context = createPersistenceContext(resource, ioMode); + testCrud(context); + } + + private void testCrud(PersistenceContext context) { + + this.fileDao.performCreate(context); + + context.setObject(null); + this.fileDao.performRead(context); + assertResource(context.getObject()); + + updateResource(context.getObject()); + this.fileDao.performUpdate(context); + + context.setObject(null); + this.fileDao.performRead(context); + assertResourceUpdated(context.getObject()); + + this.fileDao.performDelete(context); + + context.setObject(null); + this.fileDao.performRead(context); + assertNull(context.getObject()); + + context.setObject(createResource()); + this.fileDao.performCreate(context); + } + + private PersistenceContext createPersistenceContext(Resource resource, XmlIoMode ioMode) { + PersistenceContext context = new PersistenceContext(); + context.setId(resource.getId()); + context.setType(TestConstants.TYPE_RES); + context.setSubType(resource.getType()); + context.setObject(resource); + context.setIoMode(ioMode); + context.setParserFactory(new ResourceParserFactory()); + + return context; + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java new file mode 100644 index 000000000..06caa3a9b --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.MessageFormat; + +import javanet.staxutils.IndentingXMLStreamWriter; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +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.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.utils.exceptions.XmlException; +import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.xmlpers.api.DomUtil; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; + +public class FileIo { + + private static final Logger logger = LoggerFactory.getLogger(FileIo.class); + + private final File path; + + public FileIo(File path) { + this.path = path; + } + + public void write(PersistenceContext context) { + switch (context.getIoMode()) { + case DOM: + writeDom(context); + break; + case SAX: + writeSax(context); + break; + case DEFAULT: + logger.info("Using default XML IO Handler SAX"); + writeSax(context); + break; + default: + String msg = "The Xml IO Mode {0} is not supported!"; + msg = MessageFormat.format(msg, context.getIoMode()); + throw new UnsupportedOperationException(msg); + } + } + + public void read(PersistenceContext context) { + switch (context.getIoMode()) { + case DOM: + readDom(context); + break; + case SAX: + readSax(context); + break; + case DEFAULT: + logger.info("Using default XML IO Handler SAX"); + readSax(context); + break; + default: + String msg = "The Xml IO Mode {0} is not supported!"; + msg = MessageFormat.format(msg, context.getIoMode()); + throw new UnsupportedOperationException(msg); + } + } + + private void writeSax(PersistenceContext context) { + + XMLStreamWriter writer = null; + try { + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + writer = factory.createXMLStreamWriter(new FileWriter(this.path)); + writer = new IndentingXMLStreamWriter(writer); + + // start document + writer.writeStartDocument("utf-8", "1.0"); + + // then delegate object writing to caller + XmlPersistenceStreamWriter xmlWriter = new XmlPersistenceStreamWriter(writer); + SaxParser saxParser = context.getParserFactor().getSaxParser(); + saxParser.setObject(context.getObject()); + saxParser.write(xmlWriter); + + // and now end + writer.writeEndDocument(); + writer.flush(); + + } catch (FactoryConfigurationError | XMLStreamException | IOException e) { + if (this.path.exists()) + this.path.delete(); + throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (Exception e) { + logger.error("Failed to close stream: " + e.getMessage()); + } + } + } + + logger.info("Wrote SAX to " + this.path.getAbsolutePath()); + } + + private void readSax(PersistenceContext context) { + + try { + + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser sp = spf.newSAXParser(); + + SaxParser saxParser = context.getParserFactor().getSaxParser(); + DefaultHandler defaultHandler = saxParser.getDefaultHandler(); + sp.parse(this.path, defaultHandler); + context.setObject(saxParser.getObject()); + + } catch (ParserConfigurationException | SAXException | IOException e) { + + throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); + } + + logger.info("SAX parsed file " + this.path.getAbsolutePath()); + } + + private void writeDom(PersistenceContext context) { + String lineSep = System.getProperty(XmlHelper.PROP_LINE_SEPARATOR); + try { + DomParser domParser = context.getParserFactor().getDomParser(); + domParser.setObject(context.getObject()); + Document document = domParser.toDom(); + String encoding = document.getInputEncoding(); + if (encoding == null || encoding.isEmpty()) { + // logger.info("No encoding passed. Using default encoding " + XmlHelper.DEFAULT_ENCODING); + encoding = XmlHelper.DEFAULT_ENCODING; + } + + if (!lineSep.equals("\n")) { + logger.info("Overriding line separator to \\n"); + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, "\n"); + } + + // Set up a transformer + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer transformer = transfac.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + + // Transform to file + StreamResult result = new StreamResult(this.path); + Source xmlSource = new DOMSource(document); + transformer.transform(xmlSource, result); + + logger.info("Wrote DOM to " + this.path.getAbsolutePath()); + + } catch (TransformerFactoryConfigurationError | TransformerException e) { + if (this.path.exists()) + this.path.delete(); + throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); + } finally { + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, lineSep); + } + } + + private void readDom(PersistenceContext context) { + try { + DocumentBuilder docBuilder = DomUtil.createDocumentBuilder(); + Document document = docBuilder.parse(this.path); + DomParser domParser = context.getParserFactor().getDomParser(); + domParser.fromDom(document); + context.setObject(domParser.getObject()); + } catch (SAXException | IOException e) { + throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); + } + + logger.info("DOM parsed file " + this.path.getAbsolutePath()); + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java new file mode 100644 index 000000000..c5bc9684d --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.util.List; +import java.util.Set; + +/** + * @author Robert von Burg + * + */ +public class ObjectDao { + + public void add(PersistenceContext context){} + + public void update(PersistenceContext context){} + + public void remove(PersistenceContext context){} + + public void removeById(PersistenceContext context){} + + public void removeAll(PersistenceContext context){} + + public T queryById(PersistenceContext context){return null;} + + public List queryAll(PersistenceContext context){return null;} + + public Set queryKeySet(PersistenceContext context){return null;} + + public long querySize(PersistenceContext context){return 0L;} + +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ParserFactory.java new file mode 100644 index 000000000..1a7bcc396 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ParserFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +public interface ParserFactory { + + public DomParser getDomParser(); + + public SaxParser getSaxParser(); +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContext.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContext.java new file mode 100644 index 000000000..1b0d1ad42 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContext.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import ch.eitchnet.xmlpers.api.XmlIoMode; + +public class PersistenceContext { + + private T object; + private String type; + private String subType; + private String id; + + private XmlIoMode ioMode; + private ParserFactory parserFactory; + + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public String getSubType() { + return this.subType; + } + + public void setSubType(String subType) { + this.subType = subType; + } + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public T getObject() { + return this.object; + } + + public void setObject(T object) { + this.object = object; + } + + public XmlIoMode getIoMode() { + return this.ioMode; + } + + public void setIoMode(XmlIoMode ioMode) { + this.ioMode = ioMode; + } + + public ParserFactory getParserFactor() { + return this.parserFactory; + } + + public void setParserFactory(ParserFactory parserFactory) { + this.parserFactory = parserFactory; + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java new file mode 100644 index 000000000..ea5ea516d --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import javax.xml.parsers.DocumentBuilder; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import ch.eitchnet.xmlpers.api.DomUtil; +import ch.eitchnet.xmlpers.test.model.Parameter; +import ch.eitchnet.xmlpers.test.model.Resource; + +public class ResourceDomParser implements DomParser { + + private Resource resource; + + @Override + public Resource getObject() { + return this.resource; + } + + @Override + public void setObject(Resource resource) { + this.resource = resource; + } + + @Override + public Document toDom() { + + DocumentBuilder documentBuilder = DomUtil.createDocumentBuilder(); + Document document = documentBuilder.getDOMImplementation().createDocument(null, null, null); + + Element element = document.createElement("Resource"); + document.appendChild(element); + + element.setAttribute("id", this.resource.getId()); + element.setAttribute("name", this.resource.getName()); + element.setAttribute("type", this.resource.getType()); + + for (String paramId : this.resource.getParameterKeySet()) { + Parameter param = this.resource.getParameterBy(paramId); + Element paramElement = document.createElement("Parameter"); + element.appendChild(paramElement); + + paramElement.setAttribute("id", param.getId()); + paramElement.setAttribute("name", param.getName()); + paramElement.setAttribute("type", param.getType()); + paramElement.setAttribute("value", param.getValue()); + } + + return document; + } + + @Override + public void fromDom(Document document) { + + Element rootElement = document.getDocumentElement(); + + String id = rootElement.getAttribute("id"); + String name = rootElement.getAttribute("name"); + String type = rootElement.getAttribute("type"); + + Resource resource = new Resource(id, name, type); + + NodeList children = rootElement.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node item = children.item(i); + if (!item.getNodeName().equals("Parameter")) + continue; + + Element paramElement = (Element) item; + String paramId = paramElement.getAttribute("id"); + String paramName = paramElement.getAttribute("name"); + String paramType = paramElement.getAttribute("type"); + String paramValue = paramElement.getAttribute("value"); + + Parameter param = new Parameter(paramId, paramName, paramType, paramValue); + resource.addParameter(param); + } + + this.resource = resource; + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java new file mode 100644 index 000000000..041dcabc3 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import ch.eitchnet.xmlpers.test.model.Resource; + +public class ResourceParserFactory implements ParserFactory { + + @Override + public DomParser getDomParser() { + return new ResourceDomParser(); + } + + @Override + public SaxParser getSaxParser() { + return new ResourceSaxParser(); + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java new file mode 100644 index 000000000..8a1fa6335 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.test.model.Parameter; +import ch.eitchnet.xmlpers.test.model.Resource; + +class ResourceSaxParser extends DefaultHandler implements SaxParser { + + private Resource resource; + + @Override + public Resource getObject() { + return this.resource; + } + + @Override + public void setObject(Resource object) { + this.resource = object; + } + + @Override + public DefaultHandler getDefaultHandler() { + return this; + } + + @Override + public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { + + writer.writeElement("Resource"); + writer.writeAttribute("id", this.resource.getId()); + writer.writeAttribute("name", this.resource.getName()); + writer.writeAttribute("type", this.resource.getType()); + for (String paramId : this.resource.getParameterKeySet()) { + Parameter param = this.resource.getParameterBy(paramId); + writer.writeElement("Parameter"); + writer.writeAttribute("id", param.getId()); + writer.writeAttribute("name", param.getName()); + writer.writeAttribute("type", param.getType()); + writer.writeAttribute("value", param.getValue()); + } + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (qName) { + case "Resource": + String id = attributes.getValue("id"); + String name = attributes.getValue("name"); + String type = attributes.getValue("type"); + Resource resource = new Resource(id, name, type); + this.resource = resource; + break; + case "Parameter": + id = attributes.getValue("id"); + name = attributes.getValue("name"); + type = attributes.getValue("type"); + String value = attributes.getValue("value"); + Parameter param = new Parameter(id, name, type, value); + this.resource.addParameter(param); + break; + default: + throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); + } + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/SaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/SaxParser.java new file mode 100644 index 000000000..31b311717 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/SaxParser.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; + +public interface SaxParser { + + public T getObject(); + + public void setObject(T object); + + public DefaultHandler getDefaultHandler(); + + public void write(XmlPersistenceStreamWriter xmlWriter) throws XMLStreamException; +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java new file mode 100644 index 000000000..bc7fd5a4a --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.model; + +import org.junit.Assert; + +import ch.eitchnet.xmlpers.test.impl.Book; + +/** + * @author Robert von Burg + * + */ +public class ModelBuilder { + + public static final String RES_TYPE = "@subType"; + public static final String RES_TYPE_INEXISTANT = "@inexistant"; + public static final String RES_NAME = "@name"; + public static final String RES_NAME_MODIFIED = "@name_modified"; + public static final String RES_ID = "@id"; + + public static final String PARAM_TYPE = "@paramType"; + public static final String PARAM_NAME = "@paramName"; + public static final String PARAM_ID = "@paramId"; + public static final String PARAM_VALUE_1 = "@paramValue1"; + public static final String PARAM_VALUE_2 = "@paramValue2"; + + public static final long BOOK_ID = 10L; + public static final String BOOK_TITLE = "Nick Hornby"; + public static final String BOOK_AUTHOR = "A long way down"; + public static final String BOOK_PRESS_1 = "Some press"; + public static final String BOOK_PRESS_2 = "Another press"; + public static final double BOOK_PRICE = 45.55D; + + public static Resource createResource() { + Resource resource = new Resource(RES_ID, RES_NAME, RES_TYPE); + Parameter param = new Parameter(PARAM_ID, PARAM_NAME, PARAM_TYPE, PARAM_VALUE_1); + resource.addParameter(param); + return resource; + } + + public static void updateResource(Resource resource) { + resource.setName(RES_NAME_MODIFIED); + resource.getParameterBy(PARAM_ID).setValue(PARAM_VALUE_2); + } + + public static Book createBook() { + Book book = new Book(BOOK_ID, BOOK_TITLE, BOOK_AUTHOR, BOOK_PRESS_1, BOOK_PRICE); + return book; + } + + public static void updateBook(Book book) { + book.setPress(BOOK_PRESS_2); + } + + public static void assertBook(Book book) { + Assert.assertNotNull(book); + Assert.assertEquals(BOOK_ID, book.getId().longValue()); + Assert.assertEquals(BOOK_TITLE, book.getTitle()); + Assert.assertEquals(BOOK_AUTHOR, book.getAuthor()); + Assert.assertEquals(BOOK_PRESS_1, book.getPress()); + Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); + } + + public static void assertBookUpdated(Book book) { + Assert.assertNotNull(book); + Assert.assertEquals(BOOK_ID, book.getId().longValue()); + Assert.assertEquals(BOOK_TITLE, book.getTitle()); + Assert.assertEquals(BOOK_AUTHOR, book.getAuthor()); + Assert.assertEquals(BOOK_PRESS_2, book.getPress()); + Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); + } + + public static void assertResource(Resource resource) { + Assert.assertNotNull(resource); + Assert.assertEquals(RES_ID, resource.getId()); + Assert.assertEquals(RES_NAME, resource.getName()); + Assert.assertEquals(RES_TYPE, resource.getType()); + Parameter param = resource.getParameterBy(PARAM_ID); + Assert.assertNotNull(param); + Assert.assertEquals(PARAM_ID, param.getId()); + Assert.assertEquals(PARAM_NAME, param.getName()); + Assert.assertEquals(PARAM_TYPE, param.getType()); + Assert.assertEquals(PARAM_VALUE_1, param.getValue()); + } + + public static void assertResourceUpdated(Resource resource) { + Assert.assertNotNull(resource); + Assert.assertEquals(RES_ID, resource.getId()); + Assert.assertEquals(RES_NAME_MODIFIED, resource.getName()); + Assert.assertEquals(RES_TYPE, resource.getType()); + Parameter param = resource.getParameterBy(PARAM_ID); + Assert.assertNotNull(param); + Assert.assertEquals(PARAM_ID, param.getId()); + Assert.assertEquals(PARAM_NAME, param.getName()); + Assert.assertEquals(PARAM_TYPE, param.getType()); + Assert.assertEquals(PARAM_VALUE_2, param.getValue()); + } +} From 403f59b82ca97f6238095fd05869b86d896c4d37 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 27 Sep 2013 20:30:55 +0200 Subject: [PATCH 175/457] [Project] fixed reference of src/main/ to /src/test --- .../java/ch/eitchnet/xmlpers/api}/PersistenceContext.java | 4 ++-- .../ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java | 2 +- .../java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java | 1 + .../ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java | 1 + .../java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java | 1 + .../java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java | 2 ++ 6 files changed, 8 insertions(+), 3 deletions(-) rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/api}/PersistenceContext.java (94%) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContext.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java similarity index 94% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContext.java rename to src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java index 1b0d1ad42..bf132775f 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContext.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java @@ -19,9 +19,9 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; -import ch.eitchnet.xmlpers.api.XmlIoMode; +import ch.eitchnet.xmlpers.test.impl.rewrite.ParserFactory; public class PersistenceContext { diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java index 3e0c84535..43b0f9aff 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java @@ -29,9 +29,9 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.PropertiesHelper; import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.xmlpers.api.PersistenceContext; import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.test.impl.rewrite.PersistenceContext; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java index b0fadcc77..8774d2549 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java @@ -23,6 +23,7 @@ package ch.eitchnet.xmlpers.test.impl.rewrite; import java.io.File; +import ch.eitchnet.xmlpers.api.PersistenceContext; import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; public class FileDao { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java index e8f51a178..11ac0036d 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java @@ -35,6 +35,7 @@ import org.junit.BeforeClass; import org.junit.Test; import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.xmlpers.api.PersistenceContext; import ch.eitchnet.xmlpers.api.XmlIoMode; import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java index 06caa3a9b..759b55cec 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java @@ -54,6 +54,7 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; import ch.eitchnet.utils.helper.XmlHelper; import ch.eitchnet.xmlpers.api.DomUtil; +import ch.eitchnet.xmlpers.api.PersistenceContext; import ch.eitchnet.xmlpers.api.XmlPersistenceException; import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java index c5bc9684d..cb5a9581b 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java @@ -24,6 +24,8 @@ package ch.eitchnet.xmlpers.test.impl.rewrite; import java.util.List; import java.util.Set; +import ch.eitchnet.xmlpers.api.PersistenceContext; + /** * @author Robert von Burg * From bacc6d72fd918f45dbfe16369108637fb5ea6ef1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 27 Sep 2013 20:40:02 +0200 Subject: [PATCH 176/457] [Project] fixed /src/main to /src/test reference --- .../java/ch/eitchnet/xmlpers/api}/DomParser.java | 2 +- .../java/ch/eitchnet/xmlpers/api}/ParserFactory.java | 3 ++- src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java | 1 - .../java/ch/eitchnet/xmlpers/api}/SaxParser.java | 2 +- .../java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java | 2 ++ .../eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java | 1 + .../xmlpers/test/impl/rewrite/ResourceParserFactory.java | 3 +++ .../eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java | 1 + 8 files changed, 11 insertions(+), 4 deletions(-) rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/api}/DomParser.java (95%) rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/api}/ParserFactory.java (94%) rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/api}/SaxParser.java (95%) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DomParser.java b/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java similarity index 95% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DomParser.java rename to src/main/java/ch/eitchnet/xmlpers/api/DomParser.java index d5850ef4b..1c78ff5d3 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DomParser.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; import org.w3c.dom.Document; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ParserFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java similarity index 94% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ParserFactory.java rename to src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java index 1a7bcc396..5a4d8a2ec 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ParserFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java @@ -19,7 +19,8 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; + public interface ParserFactory { diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java index bf132775f..17e49e9bb 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java @@ -21,7 +21,6 @@ */ package ch.eitchnet.xmlpers.api; -import ch.eitchnet.xmlpers.test.impl.rewrite.ParserFactory; public class PersistenceContext { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/SaxParser.java b/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java similarity index 95% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/SaxParser.java rename to src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java index 31b311717..9fc8d1dd9 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/SaxParser.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; import javax.xml.stream.XMLStreamException; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java index 759b55cec..f23a3361d 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java @@ -53,8 +53,10 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.xmlpers.api.DomParser; import ch.eitchnet.xmlpers.api.DomUtil; import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.SaxParser; import ch.eitchnet.xmlpers.api.XmlPersistenceException; import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java index ea5ea516d..869664596 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java @@ -28,6 +28,7 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import ch.eitchnet.xmlpers.api.DomParser; import ch.eitchnet.xmlpers.api.DomUtil; import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java index 041dcabc3..0e062b1ae 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java @@ -21,6 +21,9 @@ */ package ch.eitchnet.xmlpers.test.impl.rewrite; +import ch.eitchnet.xmlpers.api.DomParser; +import ch.eitchnet.xmlpers.api.ParserFactory; +import ch.eitchnet.xmlpers.api.SaxParser; import ch.eitchnet.xmlpers.test.model.Resource; public class ResourceParserFactory implements ParserFactory { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java index 8a1fa6335..bf69b3e20 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java @@ -27,6 +27,7 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import ch.eitchnet.xmlpers.api.SaxParser; import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; From f81dec893979662ca3855a5cc263e7d5667d6a12 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 6 Oct 2013 12:32:57 +0200 Subject: [PATCH 177/457] [New] working on rewrite --- .../xmlpers/api/PersistenceContext.java | 17 ++ .../xmlpers/api/XmlPersistenceConstants.java | 6 +- .../impl/XmlPersistenceTransactionImpl.java | 5 +- .../test/impl/rewrite/BookDomParser.java | 59 +++++++ .../test/impl/rewrite/BookParserFactory.java | 45 +++++ .../test/impl/rewrite/BookSaxParser.java | 62 +++++++ .../DefaultPersistenceTransaction.java | 148 +++++++++++++++++ .../rewrite/DefaultXmlPersistenceManager.java | 63 +++++++ .../test/impl/rewrite/MetadataDao.java | 84 ++++++++++ .../xmlpers/test/impl/rewrite/ObjectDao.java | 155 ++++++++++++++++-- .../test/impl/rewrite/ObjectDaoTest.java | 81 +++++++++ .../rewrite/PersistenceContextFactory.java | 39 +++++ .../impl/rewrite/PersistenceTransaction.java | 39 +++++ .../TestPersistenceContextFactory.java | 101 ++++++++++++ .../impl/rewrite/XmlPersistenceManager.java | 32 ++++ .../rewrite/XmlPersistenceManagerLoader.java | 38 +++++ 16 files changed, 958 insertions(+), 16 deletions(-) create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookDomParser.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookParserFactory.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookSaxParser.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManager.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManagerLoader.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java index 17e49e9bb..e50021892 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java @@ -21,6 +21,7 @@ */ package ch.eitchnet.xmlpers.api; +import ch.eitchnet.utils.helper.StringHelper; public class PersistenceContext { @@ -79,4 +80,20 @@ public class PersistenceContext { public void setParserFactory(ParserFactory parserFactory) { this.parserFactory = parserFactory; } + + public boolean hasSubType() { + return !StringHelper.isEmpty(this.subType); + } + + @Override + public PersistenceContext clone() { + PersistenceContext clone = new PersistenceContext<>(); + clone.type = this.type; + clone.subType = this.subType; + clone.id = this.id; + clone.ioMode = this.ioMode; + clone.parserFactory = this.parserFactory; + clone.object = this.object; + return clone; + } } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java index 3fd3266e4..f1968952e 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java @@ -28,8 +28,8 @@ package ch.eitchnet.xmlpers.api; public class XmlPersistenceConstants { private static final String PROP_PREFIX = "ch.eitchnet.xmlpers."; - public static final String PROP_VERBOSE = "ch.eitchnet.xmlpers." + "verbose"; - public static final String PROP_BASEPATH = "ch.eitchnet.xmlpers." + "basePath"; - public static final String PROP_DAO_FACTORY_CLASS = "ch.eitchnet.xmlpers." + "daoFactoryClass"; + public static final String PROP_VERBOSE = PROP_PREFIX + "verbose"; + public static final String PROP_BASEPATH = PROP_PREFIX + "basePath"; + public static final String PROP_DAO_FACTORY_CLASS = PROP_PREFIX + "daoFactoryClass"; public static final String PROP_XML_IO_MOD = PROP_PREFIX + "ioMode"; } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java index a8cdbb7ac..6b27aa0bd 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java @@ -25,6 +25,7 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.objectfilter.ObjectFilter; import ch.eitchnet.xmlpers.api.XmlPersistenceDao; import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; @@ -126,6 +127,7 @@ public class XmlPersistenceTransactionImpl implements XmlPersistenceTransaction public void commit() { try { + long start = System.nanoTime(); if (this.verbose) XmlPersistenceTransactionImpl.logger.info("Committing TX..."); Set keySet = this.objectFilter.keySet(); @@ -178,7 +180,8 @@ public class XmlPersistenceTransactionImpl implements XmlPersistenceTransaction } } - XmlPersistenceTransactionImpl.logger.info("Completed TX"); + long end = System.nanoTime(); + logger.info("Completed TX in " + StringHelper.formatNanoDuration(end - start)); //$NON-NLS-1$ } finally { // clean up diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookDomParser.java new file mode 100644 index 000000000..35c584904 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookDomParser.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import org.w3c.dom.Document; + +import ch.eitchnet.xmlpers.api.DomParser; +import ch.eitchnet.xmlpers.test.impl.Book; + +/** + * @author Robert von Burg + * + */ +public class BookDomParser implements DomParser { + + @Override + public Book getObject() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setObject(Book object) { + // TODO Auto-generated method stub + + } + + @Override + public Document toDom() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void fromDom(Document document) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookParserFactory.java new file mode 100644 index 000000000..01ab00143 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookParserFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import ch.eitchnet.xmlpers.api.DomParser; +import ch.eitchnet.xmlpers.api.ParserFactory; +import ch.eitchnet.xmlpers.api.SaxParser; +import ch.eitchnet.xmlpers.test.impl.Book; + +/** + * @author Robert von Burg + * + */ +public class BookParserFactory implements ParserFactory { + + @Override + public DomParser getDomParser() { + return new BookDomParser(); + } + + @Override + public SaxParser getSaxParser() { + return new BookSaxParser(); + } + +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookSaxParser.java new file mode 100644 index 000000000..ab4d012f5 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookSaxParser.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.xmlpers.api.SaxParser; +import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.test.impl.Book; + +/** + * @author Robert von Burg + * + */ +public class BookSaxParser implements SaxParser { + + @Override + public Book getObject() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setObject(Book object) { + // TODO Auto-generated method stub + + } + + @Override + public DefaultHandler getDefaultHandler() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void write(XmlPersistenceStreamWriter xmlWriter) throws XMLStreamException { + // TODO Auto-generated method stub + + } + +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java new file mode 100644 index 000000000..27bdaf396 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.objectfilter.ObjectFilter; +import ch.eitchnet.xmlpers.api.PersistenceContext; + +/** + * @author Robert von Burg + * + */ +public class DefaultPersistenceTransaction implements PersistenceTransaction { + + private static final Logger logger = LoggerFactory.getLogger(DefaultPersistenceTransaction.class); + + private final FileDao fileDao; + private final ObjectDao objectDao; + private final MetadataDao metadataDao; + private final ObjectFilter objectFilter; + private final boolean verbose; + + private boolean closed; + + public DefaultPersistenceTransaction(FileDao fileDao, boolean verbose) { + this.fileDao = fileDao; + this.verbose = verbose; + this.objectFilter = new ObjectFilter(); + this.objectDao = new ObjectDao(this, this.fileDao, this.objectFilter); + this.metadataDao = new MetadataDao(this, this.fileDao); + } + + @Override + public ObjectDao getObjectDao() { + return this.objectDao; + } + + @Override + public MetadataDao getMetadataDao() { + return this.metadataDao; + } + + @Override + public void rollback() { + this.closed = true; + this.objectDao.rollback(); + this.objectFilter.clearCache(); + } + + @Override + public void commit(PersistenceContextFactory persistenceContextFactory) { + + try { + + long start = System.nanoTime(); + if (this.verbose) + logger.info("Committing TX..."); //$NON-NLS-1$ + + Set keySet = this.objectFilter.keySet(); + if (keySet.isEmpty()) + return; + for (String key : keySet) { + + List removed = this.objectFilter.getRemoved(key); + if (removed.isEmpty()) { + if (this.verbose) + logger.info("No objects removed in this tx."); //$NON-NLS-1$ + } else { + if (this.verbose) + logger.info(removed.size() + " objects removed in this tx."); //$NON-NLS-1$ + + for (Object object : removed) { + PersistenceContext context = persistenceContextFactory.createPersistenceContext(object); + this.fileDao.performDelete(context); + } + } + + List updated = this.objectFilter.getUpdated(key); + if (updated.isEmpty()) { + if (this.verbose) + logger.info("No objects updated in this tx."); //$NON-NLS-1$ + } else { + if (this.verbose) + logger.info(updated.size() + " objects updated in this tx."); //$NON-NLS-1$ + + for (Object object : updated) { + + PersistenceContext context = persistenceContextFactory.createPersistenceContext(object); + this.fileDao.performUpdate(context); + } + } + + List added = this.objectFilter.getAdded(key); + if (added.isEmpty()) { + if (this.verbose) + logger.info("No objects added in this tx."); //$NON-NLS-1$ + } else { + if (this.verbose) + logger.info(added.size() + " objects added in this tx."); //$NON-NLS-1$ + + for (Object object : added) { + + PersistenceContext context = persistenceContextFactory.createPersistenceContext(object); + this.fileDao.performCreate(context); + } + } + } + + long end = System.nanoTime(); + logger.info("Completed TX in " + StringHelper.formatNanoDuration(end - start)); //$NON-NLS-1$ + + } finally { + // clean up + this.objectFilter.clearCache(); + this.closed = true; + } + } + + @Override + public boolean isClosed() { + return this.closed; + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java new file mode 100644 index 000000000..eb5963ff2 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.impl.XmlPersistenceHandlerImpl; + +/** + * @author Robert von Burg + * + */ +public class DefaultXmlPersistenceManager implements XmlPersistenceManager { + + protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandlerImpl.class); + + protected boolean initialized; + protected boolean verbose; + + public void initialize(Properties properties) { + if (this.initialized) + throw new IllegalStateException("Already initialized!"); //$NON-NLS-1$ + + // get properties + String context = XmlPersistenceHandlerImpl.class.getSimpleName(); + boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, + Boolean.FALSE).booleanValue(); + + this.verbose = verbose; + } + + @Override + public PersistenceTransaction openTx() { + + PersistenceTransaction tx = null; + + return tx; + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java new file mode 100644 index 000000000..617057bda --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.util.Set; + +/** + * @author Robert von Burg + * + */ +public class MetadataDao { + + private final DefaultPersistenceTransaction tx; + private final FileDao fileDao; + + public MetadataDao(DefaultPersistenceTransaction tx, FileDao fileDao) { + this.tx = tx; + this.fileDao = fileDao; + } + + public Set queryTypeSet() { + assertNotClosed(); + throw new UnsupportedOperationException("Not yet implemented!"); + } + + public Set queryTypeSet(String type) { + assertNotClosed(); + throw new UnsupportedOperationException("Not yet implemented!"); + } + + public Set queryKeySet(String type) { + assertNotClosed(); + throw new UnsupportedOperationException("Not yet implemented!"); + } + + public Set queryKeySet(String type, String subType) { + assertNotClosed(); + throw new UnsupportedOperationException("Not yet implemented!"); + } + + public long queryTypeSize() { + assertNotClosed(); + throw new UnsupportedOperationException("Not yet implemented!"); + } + + public long querySubTypeSize(String type) { + assertNotClosed(); + throw new UnsupportedOperationException("Not yet implemented!"); + } + + public long querySize(String type) { + assertNotClosed(); + throw new UnsupportedOperationException("Not yet implemented!"); + } + + public long querySize(String type, String subType) { + assertNotClosed(); + throw new UnsupportedOperationException("Not yet implemented!"); + } + + private void assertNotClosed() { + if (this.tx.isClosed()) + throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java index cb5a9581b..6f554efcd 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java @@ -21,33 +21,164 @@ */ package ch.eitchnet.xmlpers.test.impl.rewrite; +import java.text.MessageFormat; +import java.util.ArrayList; import java.util.List; import java.util.Set; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.objectfilter.ObjectFilter; import ch.eitchnet.xmlpers.api.PersistenceContext; /** * @author Robert von Burg - * + * */ -public class ObjectDao { - - public void add(PersistenceContext context){} +public class ObjectDao { - public void update(PersistenceContext context){} + private final PersistenceTransaction tx; + private final ObjectFilter objectFilter; + private final FileDao fileDao; + private boolean closed; - public void remove(PersistenceContext context){} + public ObjectDao(PersistenceTransaction tx, FileDao fileDao, ObjectFilter objectFilter) { + this.tx = tx; + this.fileDao = fileDao; + this.objectFilter = objectFilter; + } - public void removeById(PersistenceContext context){} + public void add(Object object) { + assertNotClosed(); + assertNotNull(object); + this.objectFilter.add(object); + } - public void removeAll(PersistenceContext context){} + public void update(Object object) { + assertNotClosed(); + assertNotNull(object); + this.objectFilter.update(object); + } - public T queryById(PersistenceContext context){return null;} + public void remove(Object object) { + assertNotClosed(); + assertNotNull(object); + this.objectFilter.remove(object); + } - public List queryAll(PersistenceContext context){return null;} + public void removeById(PersistenceContext context) { + assertNotClosed(); + assertHasType(context); + assertHasId(context); + this.objectFilter.remove(context.clone()); + } - public Set queryKeySet(PersistenceContext context){return null;} + public void removeAll(PersistenceContext context) { + assertNotClosed(); + assertHasType(context); - public long querySize(PersistenceContext context){return 0L;} + Set keySet = queryKeySet(context); + for (String id : keySet) { + PersistenceContext clone = context.clone(); + clone.setId(id); + this.objectFilter.remove(clone); + } + } + public T queryById(PersistenceContext context) { + assertNotClosed(); + assertHasType(context); + assertHasId(context); + this.fileDao.performRead(context); + return context.getObject(); + } + + public List queryAll(PersistenceContext context) { + assertNotClosed(); + assertHasType(context); + + MetadataDao metadataDao = this.tx.getMetadataDao(); + Set keySet; + if (context.hasSubType()) + keySet = metadataDao.queryKeySet(context.getType(), context.getSubType()); + else + keySet = metadataDao.queryKeySet(context.getType()); + + List result = new ArrayList<>(); + PersistenceContext readContext = context.clone(); + for (String id : keySet) { + readContext.setId(id); + this.fileDao.performRead(readContext); + assertObjectRead(readContext); + result.add(readContext.getObject()); + } + + return result; + } + + public Set queryKeySet(PersistenceContext context) { + assertNotClosed(); + assertHasType(context); + + assertNotClosed(); + assertHasType(context); + + MetadataDao metadataDao = this.tx.getMetadataDao(); + Set keySet; + if (context.hasSubType()) + keySet = metadataDao.queryKeySet(context.getType(), context.getSubType()); + else + keySet = metadataDao.queryKeySet(context.getType()); + + return keySet; + } + + public long querySize(PersistenceContext context) { + assertNotClosed(); + assertHasType(context); + + assertNotClosed(); + assertHasType(context); + + MetadataDao metadataDao = this.tx.getMetadataDao(); + long size; + if (context.hasSubType()) + size = metadataDao.querySize(context.getType(), context.getSubType()); + else + size = metadataDao.querySize(context.getType()); + + return size; + } + + public void rollback() { + this.objectFilter.clearCache(); + this.closed = true; + } + + private void assertNotNull(Object object) { + if (object == null) + throw new RuntimeException("Object may not be null!"); //$NON-NLS-1$ + } + + private void assertObjectRead(PersistenceContext context) { + if (context.getObject() == null) { + String msg = "Failed to read object with for {0} / {1} / {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId()); + throw new RuntimeException(msg); + } + } + + private void assertHasType(PersistenceContext context) { + if (StringHelper.isEmpty(context.getType())) + throw new RuntimeException("Type is always needed on a persistence context!"); //$NON-NLS-1$ + } + + private void assertHasId(PersistenceContext context) { + if (StringHelper.isEmpty(context.getId())) + throw new RuntimeException("Id is not set on persistence context!"); //$NON-NLS-1$ + } + + private void assertNotClosed() { + if (this.closed || this.tx.isClosed()) + throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); //$NON-NLS-1$ + } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java new file mode 100644 index 000000000..07c21d77d --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; + +import java.util.Properties; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class ObjectDaoTest { + + private static final Logger logger = LoggerFactory.getLogger(ObjectDaoTest.class); + + private PersistenceContextFactory persistenceContextFactory; + private XmlPersistenceManager persistenceManager; + private PersistenceTransaction tx; + + @BeforeClass + public static void beforeClass() { + } + + @Before + public void setUp() { + + this.persistenceContextFactory = new TestPersistenceContextFactory(); + + Properties properties = new Properties(); + this.persistenceManager = XmlPersistenceManagerLoader.load(properties); + } + + @After + public void tearDown() { + if (this.tx != null) { + this.tx.rollback(); + } + } + + @Test + @Ignore + public void testObjectDao() { + + this.tx = this.persistenceManager.openTx(); + + Resource resource = createResource(); + + this.tx.getObjectDao().add(resource); + this.tx.commit(this.persistenceContextFactory); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java new file mode 100644 index 000000000..8c299be3b --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import ch.eitchnet.xmlpers.api.PersistenceContext; + +/** + * @author Robert von Burg + * + */ +public interface PersistenceContextFactory { + + public PersistenceContext createPersistenceContext(String type, String subType, String id); + + public PersistenceContext createPersistenceContext(String type, String subType); + + public PersistenceContext createPersistenceContext(String type); + + public PersistenceContext createPersistenceContext(T t); +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java new file mode 100644 index 000000000..388b6a0f9 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +/** + * @author Robert von Burg + * + */ +public interface PersistenceTransaction { + + public void commit(PersistenceContextFactory persistenceContextFactory); + + public void rollback(); + + public boolean isClosed(); + + public ObjectDao getObjectDao(); + + public MetadataDao getMetadataDao(); +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java new file mode 100644 index 000000000..f83531824 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import ch.eitchnet.xmlpers.api.ParserFactory; +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.test.impl.Book; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class TestPersistenceContextFactory implements PersistenceContextFactory { + + @Override + public PersistenceContext createPersistenceContext(String type, String subType, String id) { + return buildPersistenceContext(type, subType, id); + } + + @Override + public PersistenceContext createPersistenceContext(String type, String subType) { + return buildPersistenceContext(type, subType, null); + } + + @Override + public PersistenceContext createPersistenceContext(String type) { + return buildPersistenceContext(type, null, null); + } + + @Override + public PersistenceContext createPersistenceContext(T t) { + if (t == null) + throw new RuntimeException("T may not be null!"); + + PersistenceContext context; + + if (t instanceof Resource) { + Resource resource = (Resource) t; + context = buildPersistenceContext(TestConstants.TYPE_RES, resource.getType(), resource.getId()); + } else if (t instanceof Book) { + context = buildPersistenceContext(TestConstants.TYPE_BOOK, null, ((Book) t).getId().toString()); + } else { + throw new UnsupportedOperationException("Handling of " + t.getClass().getName() + " is not implemented!"); + } + + context.setObject(t); + return context; + } + + private PersistenceContext buildPersistenceContext(String type, String subType, String id) { + + PersistenceContext context = new PersistenceContext<>(); + + context.setType(type); + context.setSubType(subType); + context.setId(id); + + context.setParserFactory(this. getParserFactoryInstance(type)); + + return context; + } + + /** + * @param type + * @return + */ + @SuppressWarnings("unchecked") + private ParserFactory getParserFactoryInstance(String type) { + + ParserFactory parserFactory; + if (type.equals(TestConstants.TYPE_RES)) + parserFactory = (ParserFactory) new ResourceParserFactory(); + else if (type.equals(TestConstants.TYPE_BOOK)) + parserFactory = (ParserFactory) new BookParserFactory(); + else + throw new UnsupportedOperationException("No ParserFactory can be returned for type " + type); + + return parserFactory; + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManager.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManager.java new file mode 100644 index 000000000..e57d02ca8 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManager.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + + +/** + * @author Robert von Burg + * + */ +public interface XmlPersistenceManager { + + public PersistenceTransaction openTx(); +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManagerLoader.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManagerLoader.java new file mode 100644 index 000000000..4fbc34527 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManagerLoader.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.util.Properties; + +/** + * @author Robert von Burg + * + */ +public class XmlPersistenceManagerLoader { + + public static XmlPersistenceManager load(Properties properties) { + + DefaultXmlPersistenceManager persistenceManager = new DefaultXmlPersistenceManager(); + persistenceManager.initialize(properties); + return persistenceManager; + } +} From ca9c21e0e2604189f2ff086826e9ed6ca0f7ce11 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 6 Oct 2013 18:42:55 +0200 Subject: [PATCH 178/457] [New] rewrite is now working as test. --- .../xmlpers/api/PersistenceContext.java | 4 + .../impl/XmlPersistencePathBuilder.java | 92 ++++++++------- .../xmlpers/test/impl/BookSaxDao.java | 4 + .../xmlpers/test/impl/ResourceSaxDao.java | 4 + .../DefaultPersistenceTransaction.java | 36 ++++-- .../rewrite/DefaultXmlPersistenceManager.java | 8 +- .../test/impl/rewrite/MetadataDao.java | 2 +- .../xmlpers/test/impl/rewrite/ObjectDao.java | 2 +- .../test/impl/rewrite/ObjectDaoTest.java | 111 +++++++++++++++--- .../rewrite/PersistenceContextFactory.java | 8 +- .../impl/rewrite/PersistenceTransaction.java | 10 +- .../test/impl/rewrite/ResourceSaxParser.java | 2 +- .../TestPersistenceContextFactory.java | 37 +++--- 13 files changed, 225 insertions(+), 95 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java index e50021892..7ce34f7c7 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java @@ -32,6 +32,10 @@ public class PersistenceContext { private XmlIoMode ioMode; private ParserFactory parserFactory; + + public PersistenceContext() { + this.ioMode = XmlIoMode.DEFAULT; + } public String getType() { return this.type; diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java index 43b0f9aff..51d6f1791 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java @@ -37,16 +37,16 @@ import ch.eitchnet.xmlpers.api.XmlPersistenceException; * @author Robert von Burg */ public class XmlPersistencePathBuilder { + private static final String SLASH = "/"; //$NON-NLS-1$ + private static final Logger logger = LoggerFactory.getLogger(XmlPersistencePathBuilder.class); - public static final String FILE_EXT = ".xml"; + public static final String FILE_EXT = ".xml"; //$NON-NLS-1$ public static final int EXT_LENGTH = XmlPersistencePathBuilder.FILE_EXT.length(); private final boolean verbose; private final String basePath; - private File path; - public XmlPersistencePathBuilder(Properties properties) { // get properties @@ -59,10 +59,10 @@ public class XmlPersistencePathBuilder { // validate base path exists and is writable File basePathF = new File(basePath); if (!basePathF.exists()) - throw new XmlPersistenceException(MessageFormat.format("The database store path does not exist at {0}", + throw new XmlPersistenceException(MessageFormat.format("The database store path does not exist at {0}", //$NON-NLS-1$ basePathF.getAbsolutePath())); if (!basePathF.canWrite()) - throw new XmlPersistenceException(MessageFormat.format("The database store path is not writeable at {0}", + throw new XmlPersistenceException(MessageFormat.format("The database store path is not writeable at {0}", //$NON-NLS-1$ basePathF.getAbsolutePath())); // we want a clean base path @@ -71,14 +71,14 @@ public class XmlPersistencePathBuilder { canonicalBasePath = basePathF.getCanonicalPath(); } catch (IOException e) { throw new XmlPersistenceException( - MessageFormat.format("Failed to build canonical path from {0}", basePath), e); + MessageFormat.format("Failed to build canonical path from {0}", basePath), e); //$NON-NLS-1$ } // this.basePathF = basePathF; this.basePath = canonicalBasePath; this.verbose = verbose; - logger.info(MessageFormat.format("Using base path {0}", basePath)); + logger.info(MessageFormat.format("Using base path {0}", this.basePath)); //$NON-NLS-1$ } String getFilename(String id) { @@ -94,15 +94,15 @@ public class XmlPersistencePathBuilder { String getPathAsString(String type, String subType, String id) { StringBuilder sb = new StringBuilder(this.basePath); if (!StringHelper.isEmpty(type)) { - sb.append("/"); + sb.append(SLASH); sb.append(type); } if (!StringHelper.isEmpty(subType)) { - sb.append("/"); + sb.append(SLASH); sb.append(subType); } if (!StringHelper.isEmpty(id)) { - sb.append("/"); + sb.append(SLASH); sb.append(getFilename(id)); } @@ -116,11 +116,11 @@ public class XmlPersistencePathBuilder { File path = new File(getPathAsString(type, null, id)); // assert path exists - String msg = "Persistence unit already exists for {0} / {1} at {2}"; + String msg = "Persistence unit already exists for {0} / {1} at {2}"; //$NON-NLS-1$ assertPathNotExists(path, msg, type, id, path.getAbsolutePath()); // check if parent path exists - msg = "Could not create parent path for {0} / {1} at {2}"; + msg = "Could not create parent path for {0} / {1} at {2}"; //$NON-NLS-1$ createMissingParents(path, msg, type, id, path.getAbsolutePath()); return path; @@ -134,11 +134,11 @@ public class XmlPersistencePathBuilder { File path = new File(getPathAsString(type, subType, id)); // assert path exists - String msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; + String msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ assertPathNotExists(path, msg, type, subType, id, path.getAbsolutePath()); // check if parent path exists - msg = "Could not create parent path for {0} / {1} / {2} at {3}"; + msg = "Could not create parent path for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ createMissingParents(path, msg, type, subType, id, path.getAbsolutePath()); return path; @@ -149,7 +149,7 @@ public class XmlPersistencePathBuilder { assertId(id); File path = new File(getPathAsString(type, null, id)); if (this.verbose) { - String msg = "Query path for {0} / {1} is {2}..."; + String msg = "Query path for {0} / {1} is {2}..."; //$NON-NLS-1$ logger.info(MessageFormat.format(msg, type, id, path.getAbsolutePath())); } return path; @@ -161,7 +161,7 @@ public class XmlPersistencePathBuilder { assertId(id); File path = new File(getPathAsString(type, subType, id)); if (this.verbose) { - String msg = "Query path for {0} / {1} / {2} is {3}..."; + String msg = "Query path for {0} / {1} / {2} is {3}..."; //$NON-NLS-1$ logger.info(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); } return path; @@ -174,7 +174,7 @@ public class XmlPersistencePathBuilder { File path = new File(getPathAsString(type, null, id)); if (!path.exists()) { - String msg = "Persistence unit does not exist for {0} / {1} at {2}"; + String msg = "Persistence unit does not exist for {0} / {1} at {2}"; //$NON-NLS-1$ throw new XmlPersistenceException(MessageFormat.format(msg, type, id, path.getAbsolutePath())); } @@ -189,7 +189,7 @@ public class XmlPersistencePathBuilder { File path = new File(getPathAsString(type, subType, id)); if (!path.exists()) { - String msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; + String msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); } @@ -202,12 +202,12 @@ public class XmlPersistencePathBuilder { File path = new File(getPathAsString(type, null, id)); if (!path.exists()) { - String msg = "No Persistence units exist for {0} / {1} at {2}"; + String msg = "No Persistence units exist for {0} / {1} at {2}"; //$NON-NLS-1$ throw new XmlPersistenceException(MessageFormat.format(msg, type, id, path.getAbsolutePath())); } if (this.verbose) { - String msg = "Remove path for {0} / {1} is {2}..."; + String msg = "Remove path for {0} / {1} is {2}..."; //$NON-NLS-1$ logger.info(MessageFormat.format(msg, type, id, path.getAbsolutePath())); } @@ -221,12 +221,12 @@ public class XmlPersistencePathBuilder { File path = new File(getPathAsString(type, subType, id)); if (!path.exists()) { - String msg = "Persistence unit for {0} / {1} / {2} does not exist at {3}"; + String msg = "Persistence unit for {0} / {1} / {2} does not exist at {3}"; //$NON-NLS-1$ throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); } if (this.verbose) { - String msg = "Remove path for {0} / {1} / {2} is {3}..."; + String msg = "Remove path for {0} / {1} / {2} is {3}..."; //$NON-NLS-1$ logger.info(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); } @@ -241,7 +241,7 @@ public class XmlPersistencePathBuilder { assertType(type); File path = new File(getPathAsString(type, null, null)); if (this.verbose) { - String msg = "Query path for {0} is {1}..."; + String msg = "Query path for {0} is {1}..."; //$NON-NLS-1$ logger.info(MessageFormat.format(msg, type, path.getAbsolutePath())); } return path; @@ -252,7 +252,7 @@ public class XmlPersistencePathBuilder { assertSubType(subType); File path = new File(getPathAsString(type, subType, null)); if (this.verbose) { - String msg = "Query path for {0} / {1} is {2}..."; + String msg = "Query path for {0} / {1} is {2}..."; //$NON-NLS-1$ logger.info(MessageFormat.format(msg, type, subType, path.getAbsolutePath())); } return path; @@ -261,24 +261,26 @@ public class XmlPersistencePathBuilder { private void assertId(String id) { if (StringHelper.isEmpty(id)) throw new XmlPersistenceException( - "The id can not be empty! An object must always be handled with at least the type and id!"); + "The id can not be empty! An object must always be handled with at least the type and id!"); //$NON-NLS-1$ } private void assertType(String type) { if (StringHelper.isEmpty(type)) throw new XmlPersistenceException( - "The type can not be empty! An object must always be handled with at least the type and id!"); + "The type can not be empty! An object must always be handled with at least the type and id!"); //$NON-NLS-1$ } private void assertSubType(String subType) { if (StringHelper.isEmpty(subType)) - throw new XmlPersistenceException("The subType can not be empty!"); + throw new XmlPersistenceException("The subType can not be empty!"); //$NON-NLS-1$ } private void assertFilename(String filename) { - if (filename.charAt(filename.length() - XmlPersistencePathBuilder.EXT_LENGTH) != '.') - throw new XmlPersistenceException("The filename does not have a . at index " - + (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); + if (filename.charAt(filename.length() - XmlPersistencePathBuilder.EXT_LENGTH) != '.') { + String msg = "The filename does not have a . (dot) at index {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); + throw new XmlPersistenceException(msg); + } } private void assertPathNotExists(File path, String msg, Object... args) { @@ -298,10 +300,10 @@ public class XmlPersistencePathBuilder { if (this.verbose) { String msg; if (StringHelper.isEmpty(context.getSubType())) { - msg = "Path for operation {0} for {1} / {2} / is at {3}"; + msg = "Path for operation {0} for {1} / {2} / is at {3}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, operation, context.getType(), context.getId(), path.getAbsolutePath()); } else { - msg = "Path for operation {0} for {1} / {2} / {3} / is at {4}"; + msg = "Path for operation {0} for {1} / {2} / {3} / is at {4}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, operation, context.getType(), context.getSubType(), context.getId(), path.getAbsolutePath()); } @@ -313,14 +315,14 @@ public class XmlPersistencePathBuilder { if (!parentFile.exists() && !parentFile.mkdirs()) { String msg; if (StringHelper.isEmpty(context.getSubType())) { - msg = "Could not create parent path for {0} / {1} / at {2}"; + msg = "Could not create parent path for {0} / {1} / at {2}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); } else { - msg = "Could not create parent path for {0} / {1} / {2} at {3}"; + msg = "Could not create parent path for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), path.getAbsolutePath()); } - + throw new XmlPersistenceException(msg); } } @@ -329,14 +331,14 @@ public class XmlPersistencePathBuilder { if (!path.exists()) { String msg; if (StringHelper.isEmpty(context.getSubType())) { - msg = "Persistence unit does not exist for {0} / {1} at {2}"; + msg = "Persistence unit does not exist for {0} / {1} at {2}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); } else { - msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; + msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), path.getAbsolutePath()); } - + throw new XmlPersistenceException(msg); } } @@ -345,21 +347,21 @@ public class XmlPersistencePathBuilder { if (path.exists()) { String msg; if (StringHelper.isEmpty(context.getSubType())) { - msg = "Persistence unit already exists for {0} / {1} at {2}"; + msg = "Persistence unit already exists for {0} / {1} at {2}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); } else { - msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; + msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), path.getAbsolutePath()); } - + throw new XmlPersistenceException(msg); } } public File getCreatePath(PersistenceContext context) { File path = getPath(context); - logPath("CREATE", path, context); + logPath("CREATE", path, context); //$NON-NLS-1$ assertPathNotExists(path, context); createMissingParents(path, context); return path; @@ -367,21 +369,21 @@ public class XmlPersistencePathBuilder { public File getDeletePath(PersistenceContext context) { File path = getPath(context); - logPath("DELETE", path, context); + logPath("DELETE", path, context); //$NON-NLS-1$ assertPathExists(path, context); return path; } public File getUpdatePath(PersistenceContext context) { File path = getPath(context); - logPath("UPDATE", path, context); + logPath("UPDATE", path, context); //$NON-NLS-1$ assertPathExists(path, context); return path; } public File getReadPath(PersistenceContext context) { File path = getPath(context); - logPath("READ", path, context); + logPath("READ", path, context); //$NON-NLS-1$ if (!path.exists()) return null; return path; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java index 65a5fd113..ad151c351 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java @@ -85,6 +85,10 @@ public class BookSaxDao extends BookDao { private Book book; + public BookDefaultHandler() { + // default constructor + } + public Book getBook() { return this.book; } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java index 657a54da0..5db3afe5b 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java @@ -98,6 +98,10 @@ public class ResourceSaxDao extends ResourceDao { private Resource resource; + public ResourceDefaultHandler() { + // default constructor + } + public Resource getResource() { return this.resource; } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java index 27bdaf396..b156c7d05 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java @@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.objectfilter.ObjectFilter; import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.XmlIoMode; /** * @author Robert von Burg @@ -45,8 +46,11 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { private final ObjectFilter objectFilter; private final boolean verbose; + private boolean committed; private boolean closed; + private XmlIoMode ioMode; + public DefaultPersistenceTransaction(FileDao fileDao, boolean verbose) { this.fileDao = fileDao; this.verbose = verbose; @@ -67,9 +71,13 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { @Override public void rollback() { - this.closed = true; - this.objectDao.rollback(); - this.objectFilter.clearCache(); + if (this.committed) + throw new IllegalStateException("Transaction has already been committed!"); //$NON-NLS-1$ + if (!this.closed) { + this.closed = true; + this.objectDao.rollback(); + this.objectFilter.clearCache(); + } } @Override @@ -95,7 +103,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { logger.info(removed.size() + " objects removed in this tx."); //$NON-NLS-1$ for (Object object : removed) { - PersistenceContext context = persistenceContextFactory.createPersistenceContext(object); + PersistenceContext context = persistenceContextFactory.createCtx(this, object); this.fileDao.performDelete(context); } } @@ -110,7 +118,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { for (Object object : updated) { - PersistenceContext context = persistenceContextFactory.createPersistenceContext(object); + PersistenceContext context = persistenceContextFactory.createCtx(this, object); this.fileDao.performUpdate(context); } } @@ -125,7 +133,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { for (Object object : added) { - PersistenceContext context = persistenceContextFactory.createPersistenceContext(object); + PersistenceContext context = persistenceContextFactory.createCtx(this, object); this.fileDao.performCreate(context); } } @@ -137,12 +145,22 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { } finally { // clean up this.objectFilter.clearCache(); - this.closed = true; + this.committed = true; } } @Override - public boolean isClosed() { - return this.closed; + public boolean isOpen() { + return !this.closed && !this.committed; + } + + @Override + public void setIoMode(XmlIoMode ioMode) { + this.ioMode = ioMode; + } + + @Override + public XmlIoMode getIoMode() { + return this.ioMode; } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java index eb5963ff2..4cc86086c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.PropertiesHelper; import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; import ch.eitchnet.xmlpers.impl.XmlPersistenceHandlerImpl; +import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; /** * @author Robert von Burg @@ -41,6 +42,8 @@ public class DefaultXmlPersistenceManager implements XmlPersistenceManager { protected boolean initialized; protected boolean verbose; + private XmlPersistencePathBuilder pathBuilder; + public void initialize(Properties properties) { if (this.initialized) throw new IllegalStateException("Already initialized!"); //$NON-NLS-1$ @@ -51,12 +54,15 @@ public class DefaultXmlPersistenceManager implements XmlPersistenceManager { Boolean.FALSE).booleanValue(); this.verbose = verbose; + + this.pathBuilder = new XmlPersistencePathBuilder(properties); } @Override public PersistenceTransaction openTx() { - PersistenceTransaction tx = null; + FileDao fileDao = new FileDao(this.pathBuilder); + PersistenceTransaction tx = new DefaultPersistenceTransaction(fileDao, this.verbose); return tx; } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java index 617057bda..eb98cd728 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java @@ -78,7 +78,7 @@ public class MetadataDao { } private void assertNotClosed() { - if (this.tx.isClosed()) + if (!this.tx.isOpen()) throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java index 6f554efcd..b16d44f46 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java @@ -178,7 +178,7 @@ public class ObjectDao { } private void assertNotClosed() { - if (this.closed || this.tx.isClosed()) + if (this.closed || !this.tx.isOpen()) throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); //$NON-NLS-1$ } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java index 07c21d77d..1357af29d 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java @@ -21,18 +21,26 @@ */ package ch.eitchnet.xmlpers.test.impl.rewrite; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.junit.Assert.assertNull; +import java.io.File; import java.util.Properties; import org.junit.After; -import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.XmlIoMode; +import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; +import ch.eitchnet.xmlpers.test.impl.TestConstants; import ch.eitchnet.xmlpers.test.model.Resource; /** @@ -41,41 +49,110 @@ import ch.eitchnet.xmlpers.test.model.Resource; */ public class ObjectDaoTest { - private static final Logger logger = LoggerFactory.getLogger(ObjectDaoTest.class); + private static final String BASEPATH = "target/dbTest/rewrite"; //$NON-NLS-1$ - private PersistenceContextFactory persistenceContextFactory; + private PersistenceContextFactory ctxFactory; private XmlPersistenceManager persistenceManager; private PersistenceTransaction tx; @BeforeClass public static void beforeClass() { - } - @Before - public void setUp() { + File basePath = new File(BASEPATH); + if (basePath.exists()) { + if (!FileHelper.deleteFile(basePath, true)) { + throw new RuntimeException("Faile to delete base path " + BASEPATH); //$NON-NLS-1$ + } + } - this.persistenceContextFactory = new TestPersistenceContextFactory(); + if (!basePath.mkdirs()) { + throw new RuntimeException("Failed to create base path " + BASEPATH); //$NON-NLS-1$ + } - Properties properties = new Properties(); - this.persistenceManager = XmlPersistenceManagerLoader.load(properties); + new File(BASEPATH + "/sax").mkdir(); //$NON-NLS-1$ + new File(BASEPATH + "/dom").mkdir(); //$NON-NLS-1$ } @After public void tearDown() { - if (this.tx != null) { + if (this.tx != null && this.tx.isOpen()) { this.tx.rollback(); } } @Test - @Ignore - public void testObjectDao() { + public void testSaxObjectDao() { + this.ctxFactory = new TestPersistenceContextFactory(); + Properties properties = new Properties(); + properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/sax"); //$NON-NLS-1$ + this.persistenceManager = XmlPersistenceManagerLoader.load(properties); + + testCrud(XmlIoMode.SAX); + } + + @Test + public void testDomObjectDao() { + this.ctxFactory = new TestPersistenceContextFactory(); + Properties properties = new Properties(); + properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/dom"); //$NON-NLS-1$ + this.persistenceManager = XmlPersistenceManagerLoader.load(properties); + + testCrud(XmlIoMode.DOM); + } + + private PersistenceTransaction freshTx(XmlIoMode ioMode) { + if (this.tx != null && this.tx.isOpen()) + this.tx.rollback(); this.tx = this.persistenceManager.openTx(); + this.tx.setIoMode(ioMode); + return this.tx; + } + + private void testCrud(XmlIoMode ioMode) { + + ObjectDao objectDao; + + // create new resource Resource resource = createResource(); + objectDao = freshTx(ioMode).getObjectDao(); + objectDao.add(resource); + this.tx.commit(this.ctxFactory); - this.tx.getObjectDao().add(resource); - this.tx.commit(this.persistenceContextFactory); + // read resource + PersistenceContext ctx = this.ctxFactory.createCtx(this.tx, TestConstants.TYPE_RES, RES_TYPE, RES_ID); + objectDao = freshTx(ioMode).getObjectDao(); + resource = objectDao.queryById(ctx); + assertResource(resource); + + // modify resource + updateResource(resource); + objectDao = freshTx(ioMode).getObjectDao(); + objectDao.update(resource); + this.tx.commit(this.ctxFactory); + + // read modified resource + objectDao = freshTx(ioMode).getObjectDao(); + resource = objectDao.queryById(ctx); + assertResourceUpdated(resource); + this.tx.commit(this.ctxFactory); + + // delete resource + objectDao = freshTx(ioMode).getObjectDao(); + objectDao.remove(resource); + this.tx.commit(this.ctxFactory); + + // fail to read + objectDao = freshTx(ioMode).getObjectDao(); + resource = objectDao.queryById(ctx); + assertNull(resource); + + // and create again + resource = createResource(); + assertResource(resource); + objectDao = freshTx(ioMode).getObjectDao(); + objectDao.add(resource); + this.tx.commit(this.ctxFactory); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java index 8c299be3b..5f1a20900 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java @@ -29,11 +29,11 @@ import ch.eitchnet.xmlpers.api.PersistenceContext; */ public interface PersistenceContextFactory { - public PersistenceContext createPersistenceContext(String type, String subType, String id); + public PersistenceContext createCtx(PersistenceTransaction tx, String type, String subType, String id); - public PersistenceContext createPersistenceContext(String type, String subType); + public PersistenceContext createCtx(PersistenceTransaction tx, String type, String subType); - public PersistenceContext createPersistenceContext(String type); + public PersistenceContext createCtx(PersistenceTransaction tx, String type); - public PersistenceContext createPersistenceContext(T t); + public PersistenceContext createCtx(PersistenceTransaction tx, T t); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java index 388b6a0f9..b123d3c04 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java @@ -21,6 +21,8 @@ */ package ch.eitchnet.xmlpers.test.impl.rewrite; +import ch.eitchnet.xmlpers.api.XmlIoMode; + /** * @author Robert von Burg * @@ -31,9 +33,13 @@ public interface PersistenceTransaction { public void rollback(); - public boolean isClosed(); + public boolean isOpen(); public ObjectDao getObjectDao(); - + public MetadataDao getMetadataDao(); + + public XmlIoMode getIoMode(); + + public void setIoMode(XmlIoMode ioMode); } \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java index bf69b3e20..1bc512827 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java @@ -60,7 +60,7 @@ class ResourceSaxParser extends DefaultHandler implements SaxParser { writer.writeAttribute("type", this.resource.getType()); for (String paramId : this.resource.getParameterKeySet()) { Parameter param = this.resource.getParameterBy(paramId); - writer.writeElement("Parameter"); + writer.writeEmptyElement("Parameter"); writer.writeAttribute("id", param.getId()); writer.writeAttribute("name", param.getName()); writer.writeAttribute("type", param.getType()); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java index f83531824..bdb02f306 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java @@ -21,6 +21,8 @@ */ package ch.eitchnet.xmlpers.test.impl.rewrite; +import java.text.MessageFormat; + import ch.eitchnet.xmlpers.api.ParserFactory; import ch.eitchnet.xmlpers.api.PersistenceContext; import ch.eitchnet.xmlpers.test.impl.Book; @@ -34,41 +36,44 @@ import ch.eitchnet.xmlpers.test.model.Resource; public class TestPersistenceContextFactory implements PersistenceContextFactory { @Override - public PersistenceContext createPersistenceContext(String type, String subType, String id) { - return buildPersistenceContext(type, subType, id); + public PersistenceContext createCtx(PersistenceTransaction tx, String type, String subType, String id) { + return buildPersistenceContext(tx, type, subType, id); } @Override - public PersistenceContext createPersistenceContext(String type, String subType) { - return buildPersistenceContext(type, subType, null); + public PersistenceContext createCtx(PersistenceTransaction tx, String type, String subType) { + return buildPersistenceContext(tx, type, subType, null); } @Override - public PersistenceContext createPersistenceContext(String type) { - return buildPersistenceContext(type, null, null); + public PersistenceContext createCtx(PersistenceTransaction tx, String type) { + return buildPersistenceContext(tx, type, null, null); } @Override - public PersistenceContext createPersistenceContext(T t) { + public PersistenceContext createCtx(PersistenceTransaction tx, T t) { if (t == null) - throw new RuntimeException("T may not be null!"); + throw new RuntimeException("T may not be null!"); //$NON-NLS-1$ PersistenceContext context; if (t instanceof Resource) { Resource resource = (Resource) t; - context = buildPersistenceContext(TestConstants.TYPE_RES, resource.getType(), resource.getId()); + context = buildPersistenceContext(tx, TestConstants.TYPE_RES, resource.getType(), resource.getId()); } else if (t instanceof Book) { - context = buildPersistenceContext(TestConstants.TYPE_BOOK, null, ((Book) t).getId().toString()); + context = buildPersistenceContext(tx, TestConstants.TYPE_BOOK, null, ((Book) t).getId().toString()); } else { - throw new UnsupportedOperationException("Handling of " + t.getClass().getName() + " is not implemented!"); + String msg = "Handling of {0} is not implemented!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, t.getClass().getName()); + throw new UnsupportedOperationException(msg); } context.setObject(t); return context; } - private PersistenceContext buildPersistenceContext(String type, String subType, String id) { + private PersistenceContext buildPersistenceContext(PersistenceTransaction tx, String type, String subType, + String id) { PersistenceContext context = new PersistenceContext<>(); @@ -76,6 +81,7 @@ public class TestPersistenceContextFactory implements PersistenceContextFactory context.setSubType(subType); context.setId(id); + context.setIoMode(tx.getIoMode()); context.setParserFactory(this. getParserFactoryInstance(type)); return context; @@ -93,8 +99,11 @@ public class TestPersistenceContextFactory implements PersistenceContextFactory parserFactory = (ParserFactory) new ResourceParserFactory(); else if (type.equals(TestConstants.TYPE_BOOK)) parserFactory = (ParserFactory) new BookParserFactory(); - else - throw new UnsupportedOperationException("No ParserFactory can be returned for type " + type); + else { + String msg = "No ParserFactory can be returned for type {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, type); + throw new UnsupportedOperationException(msg); + } return parserFactory; } From 3357787f32f2865359656afa6196ec22cc3ee26b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 8 Oct 2013 18:49:46 +0200 Subject: [PATCH 179/457] [Major] rewrite is working with ObjectDao with including operations --- .../xmlpers/api/PersistenceContext.java | 10 +- .../xmlpers/impl/XmlPersistenceFileDao.java | 5 +- .../impl/XmlPersistencePathBuilder.java | 36 +-- .../test/impl/rewrite/AssertionUtil.java | 33 +++ .../DefaultPersistenceTransaction.java | 12 +- .../rewrite/DefaultXmlPersistenceManager.java | 2 +- .../xmlpers/test/impl/rewrite/FileDao.java | 79 +++++- .../test/impl/rewrite/FileDaoTest.java | 6 +- .../test/impl/rewrite/FilenameUtility.java | 23 ++ .../test/impl/rewrite/MetadataDao.java | 234 ++++++++++++++++-- .../xmlpers/test/impl/rewrite/ObjectDao.java | 80 +++--- .../test/impl/rewrite/ObjectDaoTest.java | 74 +++++- .../impl/rewrite/PersistenceTransaction.java | 2 +- .../xmlpers/test/model/ModelBuilder.java | 6 +- src/test/resources/log4j.xml | 4 +- 15 files changed, 498 insertions(+), 108 deletions(-) create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/AssertionUtil.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FilenameUtility.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java index 7ce34f7c7..42d36b99c 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java @@ -32,7 +32,7 @@ public class PersistenceContext { private XmlIoMode ioMode; private ParserFactory parserFactory; - + public PersistenceContext() { this.ioMode = XmlIoMode.DEFAULT; } @@ -85,10 +85,18 @@ public class PersistenceContext { this.parserFactory = parserFactory; } + public boolean hasType() { + return !StringHelper.isEmpty(this.type); + } + public boolean hasSubType() { return !StringHelper.isEmpty(this.subType); } + public boolean hasId() { + return !StringHelper.isEmpty(this.id); + } + @Override public PersistenceContext clone() { PersistenceContext clone = new PersistenceContext<>(); diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java index 58ec57411..ae905546a 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.PropertiesHelper; import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.test.impl.rewrite.FilenameUtility; /** * @author Robert von Burg @@ -254,7 +255,7 @@ public class XmlPersistenceFileDao { for (File subTypeFile : subTypeFiles) { if (subTypeFile.isFile()) { String filename = subTypeFile.getName(); - String id = this.pathBuilder.getId(filename); + String id = FilenameUtility.getId(filename); keySet.add(id); } } @@ -283,7 +284,7 @@ public class XmlPersistenceFileDao { for (File subTypeFile : subTypeFiles) { if (subTypeFile.isFile()) { String filename = subTypeFile.getName(); - String id = this.pathBuilder.getId(filename); + String id = FilenameUtility.getId(filename); keySet.add(id); } } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java index 51d6f1791..a70d09444 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java @@ -85,12 +85,6 @@ public class XmlPersistencePathBuilder { return id.concat(XmlPersistencePathBuilder.FILE_EXT); } - String getId(String filename) { - assertFilename(filename); - - return filename.substring(0, filename.length() - XmlPersistencePathBuilder.EXT_LENGTH); - } - String getPathAsString(String type, String subType, String id) { StringBuilder sb = new StringBuilder(this.basePath); if (!StringHelper.isEmpty(type)) { @@ -275,14 +269,6 @@ public class XmlPersistencePathBuilder { throw new XmlPersistenceException("The subType can not be empty!"); //$NON-NLS-1$ } - private void assertFilename(String filename) { - if (filename.charAt(filename.length() - XmlPersistencePathBuilder.EXT_LENGTH) != '.') { - String msg = "The filename does not have a . (dot) at index {0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); - throw new XmlPersistenceException(msg); - } - } - private void assertPathNotExists(File path, String msg, Object... args) { if (path.exists()) { throw new XmlPersistenceException(MessageFormat.format(msg, args)); @@ -327,7 +313,7 @@ public class XmlPersistencePathBuilder { } } - private void assertPathExists(File path, PersistenceContext context) { + private void assertPathIsFileAndWritable(File path, PersistenceContext context) { if (!path.exists()) { String msg; if (StringHelper.isEmpty(context.getSubType())) { @@ -341,6 +327,20 @@ public class XmlPersistencePathBuilder { throw new XmlPersistenceException(msg); } + + if (!path.isFile() || !path.canWrite()) { + String msg; + if (StringHelper.isEmpty(context.getSubType())) { + msg = "Persistence unit is not a file or is not readable for {0} / {1} at {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); + } else { + msg = "Persistence unit is not a file or is not readable for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), + path.getAbsolutePath()); + } + + throw new XmlPersistenceException(msg); + } } private void assertPathNotExists(File path, PersistenceContext context) { @@ -370,14 +370,14 @@ public class XmlPersistencePathBuilder { public File getDeletePath(PersistenceContext context) { File path = getPath(context); logPath("DELETE", path, context); //$NON-NLS-1$ - assertPathExists(path, context); + assertPathIsFileAndWritable(path, context); return path; } public File getUpdatePath(PersistenceContext context) { File path = getPath(context); logPath("UPDATE", path, context); //$NON-NLS-1$ - assertPathExists(path, context); + assertPathIsFileAndWritable(path, context); return path; } @@ -389,7 +389,7 @@ public class XmlPersistencePathBuilder { return path; } - private File getPath(PersistenceContext context) { + public File getPath(PersistenceContext context) { File path = new File(getPathAsString(context.getType(), context.getSubType(), context.getId())); return path; } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/AssertionUtil.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/AssertionUtil.java new file mode 100644 index 000000000..d83f5edeb --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/AssertionUtil.java @@ -0,0 +1,33 @@ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.text.MessageFormat; + +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.xmlpers.api.PersistenceContext; + +public class AssertionUtil { + + public static void assertNotNull(Object object) { + if (object == null) + throw new RuntimeException("Object may not be null!"); //$NON-NLS-1$ + } + + public static void assertObjectRead(PersistenceContext context) { + if (context.getObject() == null) { + String msg = "Failed to read object with for {0} / {1} / {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId()); + throw new RuntimeException(msg); + } + } + + public static void assertHasType(PersistenceContext context) { + if (StringHelper.isEmpty(context.getType())) + throw new RuntimeException("Type is always needed on a persistence context!"); //$NON-NLS-1$ + } + + public static void assertHasId(PersistenceContext context) { + if (StringHelper.isEmpty(context.getId())) + throw new RuntimeException("Id is not set on persistence context!"); //$NON-NLS-1$ + } + +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java index b156c7d05..598ab4dd1 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java @@ -56,7 +56,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { this.verbose = verbose; this.objectFilter = new ObjectFilter(); this.objectDao = new ObjectDao(this, this.fileDao, this.objectFilter); - this.metadataDao = new MetadataDao(this, this.fileDao); + this.metadataDao = new MetadataDao(this, this.fileDao, verbose); } @Override @@ -81,7 +81,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { } @Override - public void commit(PersistenceContextFactory persistenceContextFactory) { + public void commit(PersistenceContextFactory ctxFactory) { try { @@ -90,8 +90,6 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { logger.info("Committing TX..."); //$NON-NLS-1$ Set keySet = this.objectFilter.keySet(); - if (keySet.isEmpty()) - return; for (String key : keySet) { List removed = this.objectFilter.getRemoved(key); @@ -103,7 +101,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { logger.info(removed.size() + " objects removed in this tx."); //$NON-NLS-1$ for (Object object : removed) { - PersistenceContext context = persistenceContextFactory.createCtx(this, object); + PersistenceContext context = ctxFactory.createCtx(this, object); this.fileDao.performDelete(context); } } @@ -118,7 +116,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { for (Object object : updated) { - PersistenceContext context = persistenceContextFactory.createCtx(this, object); + PersistenceContext context = ctxFactory.createCtx(this, object); this.fileDao.performUpdate(context); } } @@ -133,7 +131,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { for (Object object : added) { - PersistenceContext context = persistenceContextFactory.createCtx(this, object); + PersistenceContext context = ctxFactory.createCtx(this, object); this.fileDao.performCreate(context); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java index 4cc86086c..30ee6cac0 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java @@ -61,7 +61,7 @@ public class DefaultXmlPersistenceManager implements XmlPersistenceManager { @Override public PersistenceTransaction openTx() { - FileDao fileDao = new FileDao(this.pathBuilder); + FileDao fileDao = new FileDao(this.pathBuilder, this.verbose); PersistenceTransaction tx = new DefaultPersistenceTransaction(fileDao, this.verbose); return tx; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java index 8774d2549..1e8898bfa 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java @@ -22,15 +22,29 @@ package ch.eitchnet.xmlpers.test.impl.rewrite; import java.io.File; +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; public class FileDao { - private XmlPersistencePathBuilder pathBuilder; - public FileDao(XmlPersistencePathBuilder pathBuilder) { + private static final Logger logger = LoggerFactory.getLogger(FileDao.class); + + private final boolean verbose; + private final XmlPersistencePathBuilder pathBuilder; + + public FileDao(XmlPersistencePathBuilder pathBuilder, boolean verbose) { this.pathBuilder = pathBuilder; + this.verbose = verbose; + } + + File getPath(PersistenceContext context) { + return this.pathBuilder.getPath(context); } void performCreate(PersistenceContext context) { @@ -57,6 +71,65 @@ public class FileDao { void performDelete(PersistenceContext context) { File path = this.pathBuilder.getDeletePath(context); - path.delete(); + if (!path.delete()) { + String msg = "Failed to delete file {0}"; //$NON-NLS-1$ + throw new RuntimeException(MessageFormat.format(msg, path.getAbsolutePath())); + } + + deleteEmptyDirectory(path.getParentFile(), context); + } + + private void deleteEmptyDirectory(File directoryPath, PersistenceContext ctx) { + if (!directoryPath.isDirectory()) { + String msg = "The given path for deletion when empty is not a directory:{0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, directoryPath.getAbsolutePath()); + throw new IllegalArgumentException(msg); + } + if (directoryPath.list().length == 0) { + if (!ctx.hasSubType()) { + if (!directoryPath.delete()) { + throw failedToDelete(directoryPath, ctx); + } + if (this.verbose) { + String msg = "Deleted empty directory for type {0} at {1}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, ctx.getType(), directoryPath)); + } + } else { + + if (!directoryPath.delete()) { + throw failedToDelete(directoryPath, ctx); + } + if (this.verbose) { + String msg = "Deleted empty directory for subType {0} of type {1} at {2}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, ctx.getSubType(), ctx.getType(), directoryPath)); + } + + File typePath = directoryPath.getParentFile(); + if (typePath.list().length == 0) { + + if (!typePath.delete()) { + throw failedToDelete(typePath, ctx); + } + if (this.verbose) { + String msg = "Deleted empty directory for type {0} at {1}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, ctx.getType(), typePath)); + } + } + } + } + } + + private XmlPersistenceException failedToDelete(File directoryPath, PersistenceContext ctx) { + String msg; + if (ctx.hasSubType()) { + msg = "Deletion of empty directory for {0} / {1} / {2} at {3} failed! Check file permissions!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, ctx.getType(), ctx.getSubType(), ctx.getId(), + directoryPath.getAbsolutePath()); + } else { + + msg = "Deletion of empty directory for {0} / {1} / at {2} failed! Check file permissions!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, ctx.getType(), ctx.getId(), directoryPath.getAbsolutePath()); + } + return new XmlPersistenceException(msg); } } \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java index 11ac0036d..b5d980543 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java @@ -46,9 +46,11 @@ import ch.eitchnet.xmlpers.test.model.Resource; * @author Robert von Burg * */ +@SuppressWarnings("nls") public class FileDaoTest { private static final String TEST_PATH = "target/dbTest"; + private static final boolean VERBOSE = true; private FileDao fileDao; private Properties properties; @@ -82,7 +84,7 @@ public class FileDaoTest { public void testCrudSax() { this.properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, TEST_PATH + "/sax/"); XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(this.properties); - this.fileDao = new FileDao(pathBuilder); + this.fileDao = new FileDao(pathBuilder, VERBOSE); Resource resource = createResource(); assertResource(resource); @@ -95,7 +97,7 @@ public class FileDaoTest { public void testCrudDom() { this.properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, TEST_PATH + "/dom/"); XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(this.properties); - this.fileDao = new FileDao(pathBuilder); + this.fileDao = new FileDao(pathBuilder, VERBOSE); Resource resource = createResource(); assertResource(resource); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FilenameUtility.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FilenameUtility.java new file mode 100644 index 000000000..30cd558fc --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FilenameUtility.java @@ -0,0 +1,23 @@ +package ch.eitchnet.xmlpers.test.impl.rewrite; + +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; + +public class FilenameUtility { + + public static String getId(String filename) { + assertFilename(filename); + return filename.substring(0, filename.length() - XmlPersistencePathBuilder.EXT_LENGTH); + } + + public static void assertFilename(String filename) { + if (filename.charAt(filename.length() - XmlPersistencePathBuilder.EXT_LENGTH) != '.') { + String msg = "The filename does not have a . (dot) at index {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); + throw new XmlPersistenceException(msg); + } + } + +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java index eb98cd728..4cf2d287b 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java @@ -21,64 +21,252 @@ */ package ch.eitchnet.xmlpers.test.impl.rewrite; +import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertHasType; + +import java.io.File; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashSet; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.xmlpers.api.PersistenceContext; + /** * @author Robert von Burg * */ public class MetadataDao { + private static final Logger logger = LoggerFactory.getLogger(MetadataDao.class); + private final DefaultPersistenceTransaction tx; private final FileDao fileDao; + private final boolean verbose; - public MetadataDao(DefaultPersistenceTransaction tx, FileDao fileDao) { + public MetadataDao(DefaultPersistenceTransaction tx, FileDao fileDao, boolean verbose) { this.tx = tx; this.fileDao = fileDao; + this.verbose = verbose; } - public Set queryTypeSet() { + public Set queryTypeSet(PersistenceContext ctx) { assertNotClosed(); - throw new UnsupportedOperationException("Not yet implemented!"); + assertHasNoSubType(ctx); + + File queryPath = this.fileDao.getPath(ctx); + Set keySet = queryTypeSet(queryPath); + + if (this.verbose) { + String msg; + if (!ctx.hasType()) { + msg = "Found {0} types"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, keySet.size()); + } else { + msg = "Found {0} subTypes of type {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, keySet.size(), ctx.getType()); + } + + logger.info(msg); + } + + return keySet; } - public Set queryTypeSet(String type) { - assertNotClosed(); - throw new UnsupportedOperationException("Not yet implemented!"); + private void assertHasNoSubType(PersistenceContext ctx) { + if (ctx.hasSubType()) { + throw new RuntimeException("Illegal query: sub type may not be set!"); //$NON-NLS-1$ + } } - public Set queryKeySet(String type) { + public Set queryKeySet(PersistenceContext ctx) { assertNotClosed(); - throw new UnsupportedOperationException("Not yet implemented!"); + assertHasType(ctx); + + File queryPath = this.fileDao.getPath(ctx); + Set keySet = queryKeySet(queryPath); + + if (this.verbose) { + String msg; + if (!ctx.hasSubType()) { + msg = "Found {0} objects of type {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, keySet.size(), ctx.getType()); + } else { + msg = "Found {0} objects of type {1} and subtType {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, keySet.size(), ctx.getType(), ctx.getSubType()); + } + + logger.info(msg); + } + + return keySet; } - public Set queryKeySet(String type, String subType) { + public long queryTypeSize(PersistenceContext ctx) { assertNotClosed(); - throw new UnsupportedOperationException("Not yet implemented!"); + assertHasNoSubType(ctx); + + File queryPath = this.fileDao.getPath(ctx); + long numberOfFiles = queryTypeSize(queryPath); + + if (this.verbose) { + String msg; + if (!ctx.hasType()) { + msg = "Found {0} types"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, numberOfFiles); + } else { + msg = "Found {0} subTypes of type {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, numberOfFiles, ctx.getType()); + } + + logger.info(msg); + } + + return numberOfFiles; } - public long queryTypeSize() { + public long querySize(PersistenceContext ctx) { assertNotClosed(); - throw new UnsupportedOperationException("Not yet implemented!"); + assertHasType(ctx); + + File queryPath = this.fileDao.getPath(ctx); + long numberOfFiles = querySize(queryPath); + + if (this.verbose) { + String msg; + if (!ctx.hasSubType()) { + msg = "Found {0} objects of type {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, numberOfFiles, ctx.getType()); + } else { + msg = "Found {0} objects of type {1} and subtType {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, numberOfFiles, ctx.getType(), ctx.getSubType()); + } + + logger.info(msg); + } + + return numberOfFiles; } - public long querySubTypeSize(String type) { - assertNotClosed(); - throw new UnsupportedOperationException("Not yet implemented!"); + /** + * Returns the types, i.e. directories in the given query path + * + * @param queryPath + * the path for which the types should be gathered + * + * @return a set of types in the given query path + */ + private Set queryTypeSet(File queryPath) { + Set keySet = new HashSet(); + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + if (subTypeFile.isFile()) { + String filename = subTypeFile.getName(); + String id = FilenameUtility.getId(filename); + keySet.add(id); + } + } + + return keySet; } - public long querySize(String type) { - assertNotClosed(); - throw new UnsupportedOperationException("Not yet implemented!"); + /** + * Returns the ids of all objects in the given query path, i.e. the id part of all the files in the given query path + * + * @param queryPath + * the path for which the ids should be gathered + * + * @return a set of ids for the objects in the given query path + */ + private Set queryKeySet(File queryPath) { + if (!queryPath.exists()) + return Collections.emptySet(); + + if (!queryPath.isDirectory()) { + String msg = "The path is not a directory, thus can not query key set for it: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, queryPath.getAbsolutePath()); + throw new IllegalArgumentException(msg); + } + + Set keySet = new HashSet(); + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + if (subTypeFile.isFile()) { + String filename = subTypeFile.getName(); + String id = FilenameUtility.getId(filename); + keySet.add(id); + } + } + + return keySet; } - public long querySize(String type, String subType) { - assertNotClosed(); - throw new UnsupportedOperationException("Not yet implemented!"); + /** + * Returns the number of all types, i.e. directories in the given query path + * + * @param queryPath + * the path in which to count the types + * + * @return the number of types in the given query path + */ + private long queryTypeSize(File queryPath) { + if (!queryPath.exists()) + return 0L; + + if (!queryPath.isDirectory()) { + String msg = "The path is not a directory, thus can not query type size for it: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, queryPath.getAbsolutePath()); + throw new IllegalArgumentException(msg); + } + + long numberOfFiles = 0l; + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + + if (subTypeFile.isDirectory()) + numberOfFiles++; + } + return numberOfFiles; + } + + /** + * Returns the number of all objects in the given query path + * + * @param queryPath + * the path in which to count the objects + * + * @return the number of objects in the given query path + */ + private long querySize(File queryPath) { + if (!queryPath.exists()) + return 0L; + + if (!queryPath.isDirectory()) { + String msg = "The path is not a directory, thus can not query key size for it: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, queryPath.getAbsolutePath()); + throw new IllegalArgumentException(msg); + } + + long numberOfFiles = 0l; + + File[] subTypeFiles = queryPath.listFiles(); + for (File subTypeFile : subTypeFiles) { + + if (subTypeFile.isFile()) + numberOfFiles++; + } + return numberOfFiles; } private void assertNotClosed() { - if (!this.tx.isOpen()) - throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); + if (!this.tx.isOpen()) { + String msg = "Transaction has been closed and thus no operation can be performed!"; //$NON-NLS-1$ + throw new IllegalStateException(msg); + } } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java index b16d44f46..44957ff8a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java @@ -21,12 +21,15 @@ */ package ch.eitchnet.xmlpers.test.impl.rewrite; -import java.text.MessageFormat; +import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertHasId; +import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertHasType; +import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertNotNull; +import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertObjectRead; + import java.util.ArrayList; import java.util.List; import java.util.Set; -import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.objectfilter.ObjectFilter; import ch.eitchnet.xmlpers.api.PersistenceContext; @@ -47,24 +50,48 @@ public class ObjectDao { this.objectFilter = objectFilter; } - public void add(Object object) { + public void add(T object) { assertNotClosed(); assertNotNull(object); this.objectFilter.add(object); } - public void update(Object object) { + @SuppressWarnings("unchecked") + public void addAll(List objects) { + assertNotClosed(); + assertNotNull(objects); + if (!objects.isEmpty()) + this.objectFilter.addAll((List) objects); + } + + public void update(T object) { assertNotClosed(); assertNotNull(object); this.objectFilter.update(object); } - public void remove(Object object) { + @SuppressWarnings("unchecked") + public void updateAll(List objects) { + assertNotClosed(); + assertNotNull(objects); + if (!objects.isEmpty()) + this.objectFilter.updateAll((List) objects); + } + + public void remove(T object) { assertNotClosed(); assertNotNull(object); this.objectFilter.remove(object); } + @SuppressWarnings("unchecked") + public void removeAll(List objects) { + assertNotClosed(); + assertNotNull(objects); + if (!objects.isEmpty()) + this.objectFilter.removeAll((List) objects); + } + public void removeById(PersistenceContext context) { assertNotClosed(); assertHasType(context); @@ -97,11 +124,7 @@ public class ObjectDao { assertHasType(context); MetadataDao metadataDao = this.tx.getMetadataDao(); - Set keySet; - if (context.hasSubType()) - keySet = metadataDao.queryKeySet(context.getType(), context.getSubType()); - else - keySet = metadataDao.queryKeySet(context.getType()); + Set keySet = metadataDao.queryKeySet(context); List result = new ArrayList<>(); PersistenceContext readContext = context.clone(); @@ -123,12 +146,7 @@ public class ObjectDao { assertHasType(context); MetadataDao metadataDao = this.tx.getMetadataDao(); - Set keySet; - if (context.hasSubType()) - keySet = metadataDao.queryKeySet(context.getType(), context.getSubType()); - else - keySet = metadataDao.queryKeySet(context.getType()); - + Set keySet = metadataDao.queryKeySet(context); return keySet; } @@ -140,12 +158,7 @@ public class ObjectDao { assertHasType(context); MetadataDao metadataDao = this.tx.getMetadataDao(); - long size; - if (context.hasSubType()) - size = metadataDao.querySize(context.getType(), context.getSubType()); - else - size = metadataDao.querySize(context.getType()); - + long size = metadataDao.querySize(context); return size; } @@ -154,29 +167,6 @@ public class ObjectDao { this.closed = true; } - private void assertNotNull(Object object) { - if (object == null) - throw new RuntimeException("Object may not be null!"); //$NON-NLS-1$ - } - - private void assertObjectRead(PersistenceContext context) { - if (context.getObject() == null) { - String msg = "Failed to read object with for {0} / {1} / {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId()); - throw new RuntimeException(msg); - } - } - - private void assertHasType(PersistenceContext context) { - if (StringHelper.isEmpty(context.getType())) - throw new RuntimeException("Type is always needed on a persistence context!"); //$NON-NLS-1$ - } - - private void assertHasId(PersistenceContext context) { - if (StringHelper.isEmpty(context.getId())) - throw new RuntimeException("Id is not set on persistence context!"); //$NON-NLS-1$ - } - private void assertNotClosed() { if (this.closed || !this.tx.isOpen()) throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); //$NON-NLS-1$ diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java index 1357af29d..d5e6e9c59 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java @@ -27,9 +27,12 @@ import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import org.junit.After; @@ -81,7 +84,7 @@ public class ObjectDaoTest { } @Test - public void testSaxObjectDao() { + public void testCrudSax() { this.ctxFactory = new TestPersistenceContextFactory(); Properties properties = new Properties(); properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/sax"); //$NON-NLS-1$ @@ -91,7 +94,7 @@ public class ObjectDaoTest { } @Test - public void testDomObjectDao() { + public void testCrudDom() { this.ctxFactory = new TestPersistenceContextFactory(); Properties properties = new Properties(); properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/dom"); //$NON-NLS-1$ @@ -155,4 +158,71 @@ public class ObjectDaoTest { objectDao.add(resource); this.tx.commit(this.ctxFactory); } + + @Test + public void testBulkSax() { + + this.ctxFactory = new TestPersistenceContextFactory(); + Properties properties = new Properties(); + properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/sax"); //$NON-NLS-1$ + properties.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ + this.persistenceManager = XmlPersistenceManagerLoader.load(properties); + + XmlIoMode ioMode = XmlIoMode.SAX; + testBulk(ioMode); + } + + @Test + public void testBulkDom() { + + this.ctxFactory = new TestPersistenceContextFactory(); + Properties properties = new Properties(); + properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/sax"); //$NON-NLS-1$ + properties.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ + this.persistenceManager = XmlPersistenceManagerLoader.load(properties); + + XmlIoMode ioMode = XmlIoMode.DOM; + testBulk(ioMode); + } + + private void testBulk(XmlIoMode ioMode) { + + // context + String type = "testBulk" + ioMode.name(); //$NON-NLS-1$ + + // create a list of resources + List resources = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + String id = RES_ID + "_" + i; //$NON-NLS-1$ + String name = "Bulk Test Object. " + i; //$NON-NLS-1$ + + Resource resource = createResource(id, name, type); + resources.add(resource); + } + + ObjectDao objectDao; + + // save all + objectDao = freshTx(ioMode).getObjectDao(); + objectDao.addAll(resources); + resources.clear(); + this.tx.commit(this.ctxFactory); + + // query all + objectDao = freshTx(ioMode).getObjectDao(); + PersistenceContext ctx = this.ctxFactory.createCtx(this.tx, TestConstants.TYPE_RES, type); + resources = objectDao.queryAll(ctx); + assertEquals("Expected to find 10 entries!", 10, resources.size()); //$NON-NLS-1$ + + // delete them all + objectDao.removeAll(resources); + this.tx.commit(this.ctxFactory); + + // now query them again + objectDao = freshTx(ioMode).getObjectDao(); + ctx = this.ctxFactory.createCtx(this.tx, TestConstants.TYPE_RES, type); + resources = objectDao.queryAll(ctx); + assertEquals("Expected to find 0 entries!", 0, resources.size()); //$NON-NLS-1$ + this.tx.commit(this.ctxFactory); + } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java index b123d3c04..1109d65d1 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java @@ -29,7 +29,7 @@ import ch.eitchnet.xmlpers.api.XmlIoMode; */ public interface PersistenceTransaction { - public void commit(PersistenceContextFactory persistenceContextFactory); + public void commit(PersistenceContextFactory ctxFactory); public void rollback(); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java index bc7fd5a4a..f44cc6590 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -51,7 +51,11 @@ public class ModelBuilder { public static final double BOOK_PRICE = 45.55D; public static Resource createResource() { - Resource resource = new Resource(RES_ID, RES_NAME, RES_TYPE); + return createResource(RES_ID, RES_NAME, RES_TYPE); + } + + public static Resource createResource(String id, String name, String type) { + Resource resource = new Resource(id, name, type); Parameter param = new Parameter(PARAM_ID, PARAM_NAME, PARAM_TYPE, PARAM_VALUE_1); resource.addParameter(param); return resource; diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml index a35a3c351..2f49b0dca 100644 --- a/src/test/resources/log4j.xml +++ b/src/test/resources/log4j.xml @@ -17,11 +17,11 @@ - + - + From 41a7198cdd9965b3290d4ba130e06bf913b2fd2d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 8 Oct 2013 18:57:27 +0200 Subject: [PATCH 180/457] [New] added newline constant to StringHelper --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index c6cb8841b..4646fd8e9 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -34,6 +34,8 @@ import org.slf4j.LoggerFactory; */ public class StringHelper { + public static final String NEW_LINE = "\n"; //$NON-NLS-1$ + private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); /** From d3e77afef27ed678d1efb073469a63bcbb6b1d76 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 8 Oct 2013 19:00:27 +0200 Subject: [PATCH 181/457] [Minor] cleaned up compiler warnings --- .../xmlpers/test/impl/rewrite/FileIo.java | 91 +++++++++++-------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java index f23a3361d..7e64e45c0 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java @@ -52,6 +52,7 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; +import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.helper.XmlHelper; import ch.eitchnet.xmlpers.api.DomParser; import ch.eitchnet.xmlpers.api.DomUtil; @@ -62,6 +63,9 @@ import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; public class FileIo { + public static final String DEFAULT_XML_VERSION = "1.0"; //$NON-NLS-1$ + public static final String DEFAULT_ENCODING = "utf-8"; //$NON-NLS-1$ + private static final Logger logger = LoggerFactory.getLogger(FileIo.class); private final File path; @@ -79,11 +83,11 @@ public class FileIo { writeSax(context); break; case DEFAULT: - logger.info("Using default XML IO Handler SAX"); + logger.info("Using default XML IO Handler SAX"); //$NON-NLS-1$ writeSax(context); break; default: - String msg = "The Xml IO Mode {0} is not supported!"; + String msg = "The Xml IO Mode {0} is not supported!"; //$NON-NLS-1$ msg = MessageFormat.format(msg, context.getIoMode()); throw new UnsupportedOperationException(msg); } @@ -98,11 +102,11 @@ public class FileIo { readSax(context); break; case DEFAULT: - logger.info("Using default XML IO Handler SAX"); + logger.info("Using default XML IO Handler SAX"); //$NON-NLS-1$ readSax(context); break; default: - String msg = "The Xml IO Mode {0} is not supported!"; + String msg = "The Xml IO Mode {0} is not supported!"; //$NON-NLS-1$ msg = MessageFormat.format(msg, context.getIoMode()); throw new UnsupportedOperationException(msg); } @@ -112,38 +116,36 @@ public class FileIo { XMLStreamWriter writer = null; try { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - writer = factory.createXMLStreamWriter(new FileWriter(this.path)); - writer = new IndentingXMLStreamWriter(writer); + try (FileWriter fileWriter = new FileWriter(this.path);) { - // start document - writer.writeStartDocument("utf-8", "1.0"); + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + writer = factory.createXMLStreamWriter(fileWriter); + writer = new IndentingXMLStreamWriter(writer); - // then delegate object writing to caller - XmlPersistenceStreamWriter xmlWriter = new XmlPersistenceStreamWriter(writer); - SaxParser saxParser = context.getParserFactor().getSaxParser(); - saxParser.setObject(context.getObject()); - saxParser.write(xmlWriter); + // start document + writer.writeStartDocument(DEFAULT_ENCODING, DEFAULT_XML_VERSION); - // and now end - writer.writeEndDocument(); - writer.flush(); + // then delegate object writing to caller + XmlPersistenceStreamWriter xmlWriter = new XmlPersistenceStreamWriter(writer); + SaxParser saxParser = context.getParserFactor().getSaxParser(); + saxParser.setObject(context.getObject()); + saxParser.write(xmlWriter); + + // and now end + writer.writeEndDocument(); + writer.flush(); + } } catch (FactoryConfigurationError | XMLStreamException | IOException e) { if (this.path.exists()) this.path.delete(); - throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (Exception e) { - logger.error("Failed to close stream: " + e.getMessage()); - } - } + String msg = "Writing to file failed due to internal error: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, e.getMessage()); + throw new XmlException(msg, e); } - logger.info("Wrote SAX to " + this.path.getAbsolutePath()); + String msg = "Wrote SAX to {0}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); } private void readSax(PersistenceContext context) { @@ -160,10 +162,12 @@ public class FileIo { } catch (ParserConfigurationException | SAXException | IOException e) { - throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); + String msg = "Parsing failed due to internal error: {0}"; //$NON-NLS-1$ + throw new XmlPersistenceException(MessageFormat.format(msg, e.getMessage()), e); } - logger.info("SAX parsed file " + this.path.getAbsolutePath()); + String msg = "SAX parsed file {0}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); } private void writeDom(PersistenceContext context) { @@ -178,19 +182,19 @@ public class FileIo { encoding = XmlHelper.DEFAULT_ENCODING; } - if (!lineSep.equals("\n")) { - logger.info("Overriding line separator to \\n"); - System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, "\n"); + if (!lineSep.equals(StringHelper.NEW_LINE)) { + logger.info("Overriding line separator to \\n"); //$NON-NLS-1$ + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, StringHelper.NEW_LINE); } // Set up a transformer TransformerFactory transfac = TransformerFactory.newInstance(); Transformer transformer = transfac.newTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + 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, encoding); - transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + 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 @@ -198,12 +202,15 @@ public class FileIo { Source xmlSource = new DOMSource(document); transformer.transform(xmlSource, result); - logger.info("Wrote DOM to " + this.path.getAbsolutePath()); + String msg = MessageFormat.format("Wrote DOM to {0}", this.path.getAbsolutePath()); //$NON-NLS-1$ + logger.info(msg); } catch (TransformerFactoryConfigurationError | TransformerException e) { if (this.path.exists()) this.path.delete(); - throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); + String msg = "Writing to file failed due to internal error: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, e.getMessage()); + throw new XmlException(msg, e); } finally { System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, lineSep); } @@ -217,9 +224,13 @@ public class FileIo { domParser.fromDom(document); context.setObject(domParser.getObject()); } catch (SAXException | IOException e) { - throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); + String msg = "Parsing failed due to internal error: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, e.getMessage()); + throw new XmlPersistenceException(msg, e); } - logger.info("DOM parsed file " + this.path.getAbsolutePath()); + String msg = "DOM parsed file {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.path.getAbsolutePath()); + logger.info(msg); } } \ No newline at end of file From 601692d3d9b70520c870383397925334838739fc Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 8 Oct 2013 19:57:40 +0200 Subject: [PATCH 182/457] [New] added further constants to StringHelper --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 4646fd8e9..df8f094f4 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -35,6 +35,8 @@ import org.slf4j.LoggerFactory; 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 NULL = "null"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); From 200f2081ec02e5498dd14af2395fcab892873a3c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 8 Oct 2013 22:02:42 +0200 Subject: [PATCH 183/457] [New] added a new ClassHelper utility class --- .../ch/eitchnet/utils/helper/ClassHelper.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/helper/ClassHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java b/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java new file mode 100644 index 000000000..bcd1d496d --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java @@ -0,0 +1,24 @@ +package ch.eitchnet.utils.helper; + +import java.text.MessageFormat; + +/** + * Utility class for working with {@link Class Classes} + * + * @author Robert von Burg + */ +public class ClassHelper { + + @SuppressWarnings("unchecked") + public static T instantiateClass(String className) { + + try { + Class clazz = Class.forName(className); + return (T) clazz.newInstance(); + } catch (Exception e) { + String msg = "Failed to load class {0} due to error: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, className, e.getMessage()); + throw new IllegalArgumentException(msg); + } + } +} From 4c7f0fc46085ad340f5d5ad210431ec8e94ab7a3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 15 Oct 2013 22:26:58 +0200 Subject: [PATCH 184/457] [Major] implemented new ObjectRef referencing of objects with locking Locking is not yet implemented, but is now prepared. This is once again a major rewrite but this has lead to easier use and less if/else constructs but far more object oriented delegation and double dispatching where possible --- pom.xml | 2 +- .../xmlpers/api/AbstractDaoFactory.java | 90 ---- .../eitchnet/xmlpers/api/AbstractXmlDao.java | 219 ---------- .../ch/eitchnet/xmlpers/api/DaoContext.java | 42 -- .../java/ch/eitchnet/xmlpers/api/FileDao.java | 183 ++++++++ .../java/ch/eitchnet/xmlpers/api}/FileIo.java | 96 ++--- .../ch/eitchnet/xmlpers/api/IoContext.java | 31 -- ...istenceDomContextData.java => IoMode.java} | 43 +- .../ch/eitchnet/xmlpers/api/IoOperation.java | 6 + .../ch/eitchnet/xmlpers/api}/MetadataDao.java | 129 +++--- .../ch/eitchnet/xmlpers/api}/ObjectDao.java | 91 ++-- ...nstants.java => PersistenceConstants.java} | 3 +- .../xmlpers/api/PersistenceContext.java | 68 +-- .../api/PersistenceContextFactory.java | 12 + .../PersistenceContextFactoryDelegator.java | 73 ++++ .../xmlpers/api/PersistenceManager.java} | 10 +- .../api/PersistenceManagerLoader.java} | 8 +- .../xmlpers/api/PersistenceRealm.java | 24 ++ .../xmlpers/api}/PersistenceTransaction.java | 23 +- .../ch/eitchnet/xmlpers/api/SaxParser.java | 2 - .../xmlpers/api/TransactionCloseStrategy.java | 21 + .../ch/eitchnet/xmlpers/api/XmlIoMode.java | 31 -- .../api/XmlPersistenceContextData.java | 48 --- .../xmlpers/api/XmlPersistenceDao.java | 47 --- .../xmlpers/api/XmlPersistenceDaoFactory.java | 40 -- .../api/XmlPersistenceFileHandler.java | 34 -- .../xmlpers/api/XmlPersistenceHandler.java | 31 -- .../api/XmlPersistenceMetadataDao.java | 44 -- .../api/XmlPersistenceSaxContextData.java | 65 --- .../xmlpers/api/XmlPersistenceSaxWriter.java | 35 -- .../XmlPersistenceStreamWriter.java | 2 +- .../api/XmlPersistenceTransaction.java | 76 ---- .../xmlpers/impl/DefaultPersistenceRealm.java | 59 +++ .../impl}/DefaultPersistenceTransaction.java | 108 +++-- .../impl/DefaultXmlPersistenceManager.java | 114 +++++ .../eitchnet/xmlpers/impl/MetadataXmlDao.java | 90 ---- .../ch/eitchnet/xmlpers/impl/PathBuilder.java | 127 ++++++ .../impl/TransactionDaoFactoryFacade.java | 107 ----- .../impl/XmlPersistenceDomHandler.java | 127 ------ .../xmlpers/impl/XmlPersistenceFileDao.java | 372 ---------------- .../impl/XmlPersistenceFileIoHandler.java | 37 -- .../impl/XmlPersistenceHandlerImpl.java | 90 ---- .../impl/XmlPersistencePathBuilder.java | 396 ------------------ .../impl/XmlPersistenceSaxHandler.java | 129 ------ .../impl/XmlPersistenceTransactionImpl.java | 227 ---------- .../xmlpers/objref/IdOfSubTypeRef.java | 79 ++++ .../eitchnet/xmlpers/objref/IdOfTypeRef.java | 73 ++++ .../xmlpers/objref/LockableObject.java | 29 ++ .../ch/eitchnet/xmlpers/objref/ObjectRef.java | 40 ++ .../xmlpers/objref/ObjectReferenceCache.java | 80 ++++ .../xmlpers/objref/RefNameCreator.java | 65 +++ .../ch/eitchnet/xmlpers/objref/RootRef.java | 53 +++ .../eitchnet/xmlpers/objref/SubTypeRef.java | 65 +++ .../ch/eitchnet/xmlpers/objref/TypeRef.java | 58 +++ .../eitchnet/xmlpers/util/AssertionUtil.java | 47 +++ .../xmlpers/{api => util}/DomUtil.java | 10 +- .../xmlpers/util}/FilenameUtility.java | 10 +- .../test/AbstractXmlPersistenceTest.java | 372 ---------------- .../ch/eitchnet/xmlpers/test/FileDaoTest.java | 154 +++++++ .../eitchnet/xmlpers/test/ObjectDaoTest.java | 221 ++++++++++ .../xmlpers/test/XmlPersistenceDomTest.java | 49 --- .../xmlpers/test/XmlPersistenceSaxTest.java | 49 --- .../ch/eitchnet/xmlpers/test/XmlTestMain.java | 73 ++-- .../ch/eitchnet/xmlpers/test/impl/Book.java | 2 +- .../eitchnet/xmlpers/test/impl/BookDao.java | 41 -- .../xmlpers/test/impl/BookDomDao.java | 33 +- .../impl/{rewrite => }/BookDomParser.java | 3 +- .../impl/{rewrite => }/BookParserFactory.java | 3 +- .../xmlpers/test/impl/BookSaxDao.java | 56 +-- .../impl/{rewrite => }/BookSaxParser.java | 5 +- .../test/impl/ResourceContextFactory.java | 25 ++ .../xmlpers/test/impl/ResourceDao.java | 53 --- .../xmlpers/test/impl/ResourceDomDao.java | 39 +- .../impl/{rewrite => }/ResourceDomParser.java | 6 +- .../{rewrite => }/ResourceParserFactory.java | 2 +- .../xmlpers/test/impl/ResourceSaxDao.java | 73 +--- .../impl/{rewrite => }/ResourceSaxParser.java | 6 +- .../test/impl/TestModelDaoFactory.java | 97 ----- .../test/impl/rewrite/AssertionUtil.java | 33 -- .../rewrite/DefaultXmlPersistenceManager.java | 69 --- .../xmlpers/test/impl/rewrite/FileDao.java | 135 ------ .../test/impl/rewrite/FileDaoTest.java | 145 ------- .../test/impl/rewrite/ObjectDaoTest.java | 228 ---------- .../rewrite/PersistenceContextFactory.java | 39 -- .../TestPersistenceContextFactory.java | 110 ----- .../xmlpers/test/model/ModelBuilder.java | 1 + .../xmlpers/test/model/Parameter.java | 1 + .../eitchnet/xmlpers/test/model/Resource.java | 1 + 88 files changed, 1988 insertions(+), 4357 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/AbstractXmlDao.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/DaoContext.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/FileDao.java rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/api}/FileIo.java (72%) delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/IoContext.java rename src/main/java/ch/eitchnet/xmlpers/api/{XmlPersistenceDomContextData.java => IoMode.java} (53%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/api}/MetadataDao.java (66%) rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/api}/ObjectDao.java (60%) rename src/main/java/ch/eitchnet/xmlpers/api/{XmlPersistenceConstants.java => PersistenceConstants.java} (95%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManager.java => main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java} (76%) rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManagerLoader.java => main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java} (83%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/api}/PersistenceTransaction.java (63%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceContextData.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceFileHandler.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxContextData.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxWriter.java rename src/main/java/ch/eitchnet/xmlpers/{impl => api}/XmlPersistenceStreamWriter.java (98%) delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/impl}/DefaultPersistenceTransaction.java (53%) create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/TransactionDaoFactoryFacade.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileIoHandler.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceHandlerImpl.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceSaxHandler.java delete mode 100644 src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java rename src/main/java/ch/eitchnet/xmlpers/{api => util}/DomUtil.java (81%) rename src/{test/java/ch/eitchnet/xmlpers/test/impl/rewrite => main/java/ch/eitchnet/xmlpers/util}/FilenameUtility.java (50%) delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceSaxTest.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java rename src/test/java/ch/eitchnet/xmlpers/test/impl/{rewrite => }/BookDomParser.java (93%) rename src/test/java/ch/eitchnet/xmlpers/test/impl/{rewrite => }/BookParserFactory.java (92%) rename src/test/java/ch/eitchnet/xmlpers/test/impl/{rewrite => }/BookSaxParser.java (90%) create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java rename src/test/java/ch/eitchnet/xmlpers/test/impl/{rewrite => }/ResourceDomParser.java (95%) rename src/test/java/ch/eitchnet/xmlpers/test/impl/{rewrite => }/ResourceParserFactory.java (96%) rename src/test/java/ch/eitchnet/xmlpers/test/impl/{rewrite => }/ResourceSaxParser.java (94%) delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/AssertionUtil.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java diff --git a/pom.xml b/pom.xml index 85a3a0442..bd8741828 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ch.eitchnet ch.eitchnet.xmlpers jar - 0.2.0-SNAPSHOT + 0.3.0-SNAPSHOT ch.eitchnet.xmlpers https://github.com/eitch/ch.eitchnet.xmlpers diff --git a/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java deleted file mode 100644 index 33ad1db95..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/AbstractDaoFactory.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -import java.util.Properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.PropertiesHelper; -import ch.eitchnet.xmlpers.impl.MetadataXmlDao; -import ch.eitchnet.xmlpers.impl.XmlPersistenceDomHandler; -import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; -import ch.eitchnet.xmlpers.impl.XmlPersistenceSaxHandler; - -/** - * @author Robert von Burg - */ -public abstract class AbstractDaoFactory implements XmlPersistenceDaoFactory { - - private static final Logger logger = LoggerFactory.getLogger(AbstractDaoFactory.class); - - private XmlIoMode xmlIoMode; - private XmlPersistenceFileDao fileDao; - - @Override - public void initialize(XmlPersistenceFileDao fileDao, Properties properties) { - this.fileDao = fileDao; - // TODO catch and throw proper exception - String xmlIoModeS = PropertiesHelper.getProperty(properties, AbstractDaoFactory.class.getName(), - XmlPersistenceConstants.PROP_XML_IO_MOD, XmlIoMode.SAX.name()); - this.xmlIoMode = XmlIoMode.valueOf(xmlIoModeS.toUpperCase()); - logger.info("Defaut Xml IO Mode is " + this.xmlIoMode.name()); - } - - /** - * @return - * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getMetadataDao() - */ - @Override - public XmlPersistenceMetadataDao getMetadataDao() { - MetadataXmlDao metadataDao = new MetadataXmlDao(); - metadataDao.initialize(this.fileDao); - return metadataDao; - } - - protected XmlIoMode getXmlIoMode() { - return this.xmlIoMode; - } - - protected XmlPersistenceDao initializeDao(XmlPersistenceDao dao) { - if (!(dao instanceof AbstractXmlDao)) { - throw new IllegalArgumentException("Your dao implementation does not extend from " - + AbstractXmlDao.class.getName() + "!"); - } - AbstractXmlDao abstractXmlDao = (AbstractXmlDao) dao; - abstractXmlDao.initialize(this.fileDao, getXmlFileHandler(this.xmlIoMode)); - return dao; - } - - protected XmlPersistenceFileHandler getXmlFileHandler(XmlIoMode ioMode) { - switch (ioMode) { - case DOM: - return new XmlPersistenceDomHandler(); - case SAX: - return new XmlPersistenceSaxHandler(); - default: - throw new IllegalArgumentException("The XmlIoMode " + ioMode + " is not yet supported!"); - } - } -} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/AbstractXmlDao.java b/src/main/java/ch/eitchnet/xmlpers/api/AbstractXmlDao.java deleted file mode 100644 index 321958b0c..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/AbstractXmlDao.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -import java.io.File; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import ch.eitchnet.utils.exceptions.XmlException; -import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; - -/** - * @author Robert von Burg - * - */ -public abstract class AbstractXmlDao implements XmlPersistenceDao { - - private XmlPersistenceFileDao fileDao; - private XmlPersistenceFileHandler fileHandler; - - // TODO think about setting some methods to final - // TODO if no sub type is given, then don't search recursively - it means subType does not exist - - void initialize(XmlPersistenceFileDao fileDao, XmlPersistenceFileHandler fileHandler) { - if (fileDao == null || fileHandler == null) - throw new IllegalArgumentException("Neither fileDao nor fileHandler may be null!"); - if (this.fileDao != null) - throw new IllegalStateException("DAO is already initialized!"); - this.fileDao = fileDao; - this.fileHandler = fileHandler; - } - - protected XmlPersistenceFileDao getXmlPersistenceFileDao() { - return this.fileDao; - } - - protected XmlPersistenceFileHandler getXmlPersistenceFileHandler() { - return this.fileHandler; - } - - /** - * @return the fileHandler - */ - protected XmlPersistenceFileHandler getFileHandler() { - return this.fileHandler; - } - - /** - * @return the fileDao - */ - protected XmlPersistenceFileDao getFileDao() { - return this.fileDao; - } - - @Override - public void remove(T object) { - if (getSubType() == null) - this.fileDao.remove(getType(), getId(object)); - else - this.fileDao.remove(getType(), getSubType(), getId(object)); - } - - @Override - public void removeById(String id) { - if (getSubType() == null) - this.fileDao.remove(getType(), id); - else - this.fileDao.remove(getType(), getSubType(), id); - } - - @Override - public void removeAll() { - if (getSubType() == null) - this.fileDao.removeAll(getType()); - else - this.fileDao.removeAll(getType(), getSubType()); - } - - @Override - public Set queryKeySet() { - if (getSubType() == null) - return this.fileDao.queryKeySet(getType()); - return this.fileDao.queryKeySet(getType(), getSubType()); - } - - @Override - public long querySize() { - if (getSubType() == null) - return this.fileDao.querySize(getType()); - return this.fileDao.querySize(getType(), getSubType()); - } - - @Override - public List queryAll() { - - if (getSubType() == null) { - - Set idsByType = this.fileDao.queryKeySet(getType()); - List result = new ArrayList<>(idsByType.size()); - - for (String id : idsByType) { - File objectPath = this.fileDao.getReadPath(getType(), id); - String msg = "Can not read persistence units for {0} / {1} at {2}"; - assertReadable(objectPath, msg, getType(), id, objectPath); - T object = read(objectPath); - result.add(object); - } - - return result; - } - - Set idsByType = this.fileDao.queryKeySet(getType(), getSubType()); - List result = new ArrayList<>(idsByType.size()); - - for (String id : idsByType) { - File objectPath = this.fileDao.getReadPath(getType(), getSubType(), id); - String msg = "Can not read persistence units for {0} / {1} / {2} at {3}"; - assertReadable(objectPath, msg, getType(), getSubType(), id, objectPath); - T object = read(objectPath); - result.add(object); - } - - return result; - } - - private void assertReadable(File persistenceUnit, String errorMsg, Object... msgArgs) { - if (!persistenceUnit.canRead()) { - String msg = MessageFormat.format(errorMsg, msgArgs); - throw new XmlException(msg); - } - } - - @Override - public T queryById(String id) { - if (getSubType() == null) { - File persistenceUnit = this.fileDao.getReadPath(getType(), id); - if (!persistenceUnit.exists()) - return null; - String msg = "Can not read persistence unit for {0} / {1} at {2}"; - assertReadable(persistenceUnit, msg, getType(), id, persistenceUnit); - T object = read(persistenceUnit); - return object; - } - - File persistenceUnit = this.fileDao.getReadPath(getType(), getSubType(), id); - if (!persistenceUnit.exists()) - return null; - String msg = "Can not read persistence unit for {0} / {1} / {2} at {3}"; - assertReadable(persistenceUnit, msg, getType(), getSubType(), id, persistenceUnit); - T object = read(persistenceUnit); - return object; - } - - @Override - public void add(T object) { - - File addPath; - if (getSubType() == null) - addPath = this.fileDao.getAddPath(getType(), getId(object)); - else - addPath = this.fileDao.getAddPath(getType(), getSubType(), getId(object)); - write(object, addPath); - } - - @Override - public void update(T object) { - File addPath; - if (getSubType() == null) - addPath = this.fileDao.getUpdatePath(getType(), getId(object)); - else - addPath = this.fileDao.getUpdatePath(getType(), getSubType(), getId(object)); - write(object, addPath); - } - - /** - * Returns the type of domain object being handled by this {@link XmlPersistenceDao}. This would in most cases be - * the simple name of the class being persisted - * - * @return the type of object being persisted - */ - protected abstract String getType(); - - /** - * Returns the sub type, enabling categorizing types by a sub type. Default implementation returns null, thus no - * categorization is performed for this {@link XmlPersistenceDao} implementation - * - * @return the sub type to further categorize the type of object being persisted - */ - protected String getSubType() { - return null; - } - - protected abstract String getId(T object); - - protected abstract T read(File filePath); - - protected abstract void write(T object, File filePath); -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/DaoContext.java b/src/main/java/ch/eitchnet/xmlpers/api/DaoContext.java deleted file mode 100644 index 0128f7bed..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/DaoContext.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -/** - * @author Robert von Burg - * - * @param - */ -public interface DaoContext { - - public String getType(); - - public String getSubType(); - - public String getId(); - - public boolean hasSubType(); - - public T getObject(); - - public U getIoContext(); -} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java new file mode 100644 index 000000000..2c0c3e0ca --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import java.io.File; +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.xmlpers.impl.PathBuilder; +import ch.eitchnet.xmlpers.objref.ObjectRef; + +public class FileDao { + + private static final Logger logger = LoggerFactory.getLogger(FileDao.class); + + private final PersistenceTransaction tx; + private final boolean verbose; + private final PathBuilder pathBuilder; + + public FileDao(PersistenceTransaction tx, PathBuilder pathBuilder, boolean verbose) { + this.tx = tx; + this.pathBuilder = pathBuilder; + this.verbose = verbose; + } + + private void assertIsIdRef(IoOperation ioOperation, ObjectRef objectRef) { + if (!objectRef.isLeaf()) { + String msg = "A {0} operation can only be performed with IdRefs!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, ioOperation); + throw new XmlPersistenceException(msg); + } + } + + public void performCreate(PersistenceContext ctx) { + ObjectRef objectRef = ctx.getObjectRef(); + assertIsIdRef(IoOperation.CREATE, objectRef); + File path = objectRef.getPath(this.pathBuilder); + logPath(IoOperation.CREATE, path, objectRef); + assertPathNotExists(path, objectRef); + createMissingParents(path, objectRef); + FileIo fileIo = new FileIo(path); + this.tx.getIoMode().write(ctx, fileIo); + } + + public void performRead(PersistenceContext ctx) { + ObjectRef objectRef = ctx.getObjectRef(); + assertIsIdRef(IoOperation.READ, objectRef); + File path = objectRef.getPath(this.pathBuilder); + if (!path.exists()) { + ctx.setObject(null); + return; + } + + logPath(IoOperation.READ, path, objectRef); + FileIo fileIo = new FileIo(path); + this.tx.getIoMode().read(ctx, fileIo); + } + + public void performUpdate(PersistenceContext ctx) { + ObjectRef objectRef = ctx.getObjectRef(); + assertIsIdRef(IoOperation.UPDATE, objectRef); + File path = objectRef.getPath(this.pathBuilder); + logPath(IoOperation.UPDATE, path, objectRef); + assertPathIsFileAndWritable(path, objectRef); + FileIo fileIo = new FileIo(path); + this.tx.getIoMode().write(ctx, fileIo); + } + + public void performDelete(PersistenceContext ctx) { + ObjectRef objectRef = ctx.getObjectRef(); + assertIsIdRef(IoOperation.DELETE, objectRef); + File path = objectRef.getPath(this.pathBuilder); + logPath(IoOperation.DELETE, path, objectRef); + assertPathIsFileAndWritable(path, objectRef); + if (!path.delete()) { + String msg = "Failed to delete file {0}"; //$NON-NLS-1$ + throw new RuntimeException(MessageFormat.format(msg, path.getAbsolutePath())); + } + + ObjectRef parentRef = objectRef.getParent(this.tx); + deleteEmptyDirectories(parentRef); + } + + private void deleteEmptyDirectories(ObjectRef objectRef) { + + // root can't be deleted + if (objectRef.isRoot()) + return; + + if (objectRef.isLeaf()) { + throw new IllegalArgumentException("IdRefs don't reference directories!"); //$NON-NLS-1$ + } + + File directoryPath = objectRef.getPath(this.pathBuilder); + if (!directoryPath.isDirectory()) { + String msg = "The path for {0} is not a directory: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath()); + throw new IllegalArgumentException(msg); + } + + // stop if empty + if (directoryPath.list().length != 0) + return; + + // delete + if (!directoryPath.delete()) { + String msg = "Deletion of empty directory for {0} at {1} failed! Check file permissions!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath()); + throw new XmlPersistenceException(msg); + } + + // log + if (this.verbose) { + String msg = "Deleted empty directory for {0} at {1}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, objectRef.getName(), directoryPath)); + } + + // recursively delete + ObjectRef parent = objectRef.getParent(this.tx); + deleteEmptyDirectories(parent); + } + + private void logPath(IoOperation operation, File path, ObjectRef objectRef) { + if (this.verbose) { + String msg = "Path for operation {0} for {1} is at {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, operation, objectRef.getName(), path.getAbsolutePath()); + } + } + + private void createMissingParents(File path, ObjectRef objectRef) { + File parentFile = path.getParentFile(); + if (!parentFile.exists() && !parentFile.mkdirs()) { + String msg = "Could not create parent path for {0} at {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath()); + throw new XmlPersistenceException(msg); + } + } + + private void assertPathIsFileAndWritable(File path, ObjectRef objectRef) { + if (!path.exists()) { + String msg = "Persistence unit does not exist for {0} at {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath()); + throw new XmlPersistenceException(msg); + } + + if (!path.isFile() || !path.canWrite()) { + String msg; + msg = "Persistence unit is not a file or is not readable for {0} at {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath()); + throw new XmlPersistenceException(msg); + } + } + + private void assertPathNotExists(File path, ObjectRef objectRef) { + if (path.exists()) { + String msg = "Persistence unit already exists for {0} at {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath()); + throw new XmlPersistenceException(msg); + } + } + +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java b/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java similarity index 72% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java rename to src/main/java/ch/eitchnet/xmlpers/api/FileIo.java index 7e64e45c0..0623b1ef5 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileIo.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; import java.io.File; import java.io.FileWriter; @@ -54,12 +54,7 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.helper.XmlHelper; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.api.DomUtil; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.util.DomUtil; public class FileIo { @@ -74,45 +69,7 @@ public class FileIo { this.path = path; } - public void write(PersistenceContext context) { - switch (context.getIoMode()) { - case DOM: - writeDom(context); - break; - case SAX: - writeSax(context); - break; - case DEFAULT: - logger.info("Using default XML IO Handler SAX"); //$NON-NLS-1$ - writeSax(context); - break; - default: - String msg = "The Xml IO Mode {0} is not supported!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getIoMode()); - throw new UnsupportedOperationException(msg); - } - } - - public void read(PersistenceContext context) { - switch (context.getIoMode()) { - case DOM: - readDom(context); - break; - case SAX: - readSax(context); - break; - case DEFAULT: - logger.info("Using default XML IO Handler SAX"); //$NON-NLS-1$ - readSax(context); - break; - default: - String msg = "The Xml IO Mode {0} is not supported!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getIoMode()); - throw new UnsupportedOperationException(msg); - } - } - - private void writeSax(PersistenceContext context) { + public void writeSax(PersistenceContext ctx) { XMLStreamWriter writer = null; try { @@ -127,8 +84,8 @@ public class FileIo { // then delegate object writing to caller XmlPersistenceStreamWriter xmlWriter = new XmlPersistenceStreamWriter(writer); - SaxParser saxParser = context.getParserFactor().getSaxParser(); - saxParser.setObject(context.getObject()); + SaxParser saxParser = ctx.getParserFactor().getSaxParser(); + saxParser.setObject(ctx.getObject()); saxParser.write(xmlWriter); // and now end @@ -148,33 +105,37 @@ public class FileIo { logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); } - private void readSax(PersistenceContext context) { + public void readSax(PersistenceContext ctx) { try { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); - SaxParser saxParser = context.getParserFactor().getSaxParser(); + SaxParser saxParser = ctx.getParserFactor().getSaxParser(); DefaultHandler defaultHandler = saxParser.getDefaultHandler(); sp.parse(this.path, defaultHandler); - context.setObject(saxParser.getObject()); + + String msg = "SAX parsed file {0}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); + + ctx.setObject(saxParser.getObject()); } catch (ParserConfigurationException | SAXException | IOException e) { String msg = "Parsing failed due to internal error: {0}"; //$NON-NLS-1$ throw new XmlPersistenceException(MessageFormat.format(msg, e.getMessage()), e); } - - String msg = "SAX parsed file {0}"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); } - private void writeDom(PersistenceContext context) { + public void writeDom(PersistenceContext ctx) { + String lineSep = System.getProperty(XmlHelper.PROP_LINE_SEPARATOR); + try { - DomParser domParser = context.getParserFactor().getDomParser(); - domParser.setObject(context.getObject()); + + DomParser domParser = ctx.getParserFactor().getDomParser(); + domParser.setObject(ctx.getObject()); Document document = domParser.toDom(); String encoding = document.getInputEncoding(); if (encoding == null || encoding.isEmpty()) { @@ -206,31 +167,38 @@ public class FileIo { logger.info(msg); } catch (TransformerFactoryConfigurationError | TransformerException e) { + if (this.path.exists()) this.path.delete(); + String msg = "Writing to file failed due to internal error: {0}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, e.getMessage()); throw new XmlException(msg, e); + } finally { System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, lineSep); } } - private void readDom(PersistenceContext context) { + public void readDom(PersistenceContext ctx) { + try { + DocumentBuilder docBuilder = DomUtil.createDocumentBuilder(); Document document = docBuilder.parse(this.path); - DomParser domParser = context.getParserFactor().getDomParser(); + DomParser domParser = ctx.getParserFactor().getDomParser(); domParser.fromDom(document); - context.setObject(domParser.getObject()); + + String msg = "DOM parsed file {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.path.getAbsolutePath()); + logger.info(msg); + + ctx.setObject(domParser.getObject()); + } catch (SAXException | IOException e) { String msg = "Parsing failed due to internal error: {0}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, e.getMessage()); throw new XmlPersistenceException(msg, e); } - - String msg = "DOM parsed file {0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, this.path.getAbsolutePath()); - logger.info(msg); } } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/IoContext.java b/src/main/java/ch/eitchnet/xmlpers/api/IoContext.java deleted file mode 100644 index 641eb3914..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/IoContext.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -/** - * @author Robert von Burg - * - */ -public interface IoContext { - - // marker interface -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDomContextData.java b/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java similarity index 53% rename from src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDomContextData.java rename to src/main/java/ch/eitchnet/xmlpers/api/IoMode.java index 081660b04..7464bff31 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDomContextData.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java @@ -21,29 +21,40 @@ */ package ch.eitchnet.xmlpers.api; -import org.w3c.dom.Document; - - /** * @author Robert von Burg * */ -public class XmlPersistenceDomContextData extends XmlPersistenceContextData { +public enum IoMode { - private Document document; + DOM { + @Override + public void write(PersistenceContext ctx, FileIo fileIo) { + fileIo.writeDom(ctx); + } - /** - * @return the document - */ - public Document getDocument() { - return this.document; + @Override + public void read(PersistenceContext ctx, FileIo fileIo) { + fileIo.readDom(ctx); + } + }, + SAX { + @Override + public void write(PersistenceContext ctx, FileIo fileIo) { + fileIo.writeSax(ctx); + } + + @Override + public void read(PersistenceContext ctx, FileIo fileIo) { + fileIo.readSax(ctx); + } + }; + + public void write(PersistenceContext ctx, FileIo fileIo) { + throw new UnsupportedOperationException("Override me!"); //$NON-NLS-1$ } - /** - * @param document - * the document to set - */ - public void setDocument(Document document) { - this.document = document; + public void read(PersistenceContext ctx, FileIo fileIo) { + throw new UnsupportedOperationException("Override me!"); //$NON-NLS-1$ } } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java b/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java new file mode 100644 index 000000000..f60211abe --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java @@ -0,0 +1,6 @@ +package ch.eitchnet.xmlpers.api; + +public enum IoOperation { + + CREATE, READ, UPDATE, DELETE; +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java b/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java similarity index 66% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java rename to src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java index 4cf2d287b..b2e57e7e6 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/MetadataDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java @@ -19,9 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; - -import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertHasType; +package ch.eitchnet.xmlpers.api; import java.io.File; import java.text.MessageFormat; @@ -32,7 +30,9 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.impl.PathBuilder; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.util.FilenameUtility; /** * @author Robert von Burg @@ -42,108 +42,75 @@ public class MetadataDao { private static final Logger logger = LoggerFactory.getLogger(MetadataDao.class); - private final DefaultPersistenceTransaction tx; - private final FileDao fileDao; + private final PersistenceTransaction tx; + private final PathBuilder pathBuilder; private final boolean verbose; - public MetadataDao(DefaultPersistenceTransaction tx, FileDao fileDao, boolean verbose) { + public MetadataDao(PathBuilder pathBuilder, PersistenceTransaction tx, boolean verbose) { this.tx = tx; - this.fileDao = fileDao; + this.pathBuilder = pathBuilder; this.verbose = verbose; } - public Set queryTypeSet(PersistenceContext ctx) { - assertNotClosed(); - assertHasNoSubType(ctx); + public Set queryTypeSet(ObjectRef parentRef) { + assertNotClosed(this.tx); + assertNotIdRef(parentRef); - File queryPath = this.fileDao.getPath(ctx); + File queryPath = parentRef.getPath(this.pathBuilder); Set keySet = queryTypeSet(queryPath); if (this.verbose) { - String msg; - if (!ctx.hasType()) { - msg = "Found {0} types"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, keySet.size()); - } else { - msg = "Found {0} subTypes of type {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, keySet.size(), ctx.getType()); - } - + String msg = "Found {0} types for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, keySet.size(), parentRef.getName()); logger.info(msg); } return keySet; } - private void assertHasNoSubType(PersistenceContext ctx) { - if (ctx.hasSubType()) { - throw new RuntimeException("Illegal query: sub type may not be set!"); //$NON-NLS-1$ - } - } + public Set queryKeySet(ObjectRef parentRef) { + assertNotClosed(this.tx); + assertNotRootRef(parentRef); + assertNotIdRef(parentRef); - public Set queryKeySet(PersistenceContext ctx) { - assertNotClosed(); - assertHasType(ctx); - - File queryPath = this.fileDao.getPath(ctx); + File queryPath = parentRef.getPath(this.pathBuilder); Set keySet = queryKeySet(queryPath); if (this.verbose) { - String msg; - if (!ctx.hasSubType()) { - msg = "Found {0} objects of type {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, keySet.size(), ctx.getType()); - } else { - msg = "Found {0} objects of type {1} and subtType {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, keySet.size(), ctx.getType(), ctx.getSubType()); - } - + String msg = "Found {0} objects for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, keySet.size(), parentRef.getName()); logger.info(msg); } return keySet; } - public long queryTypeSize(PersistenceContext ctx) { - assertNotClosed(); - assertHasNoSubType(ctx); + public long queryTypeSize(ObjectRef parentRef) { + assertNotClosed(this.tx); + assertNotRootRef(parentRef); + assertNotIdRef(parentRef); - File queryPath = this.fileDao.getPath(ctx); + File queryPath = parentRef.getPath(this.pathBuilder); long numberOfFiles = queryTypeSize(queryPath); if (this.verbose) { - String msg; - if (!ctx.hasType()) { - msg = "Found {0} types"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, numberOfFiles); - } else { - msg = "Found {0} subTypes of type {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, numberOfFiles, ctx.getType()); - } - + String msg = "Found {0} types for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName()); logger.info(msg); } return numberOfFiles; } - public long querySize(PersistenceContext ctx) { - assertNotClosed(); - assertHasType(ctx); + public long querySize(ObjectRef parentRef) { + assertNotClosed(this.tx); - File queryPath = this.fileDao.getPath(ctx); + File queryPath = parentRef.getPath(this.pathBuilder); long numberOfFiles = querySize(queryPath); if (this.verbose) { - String msg; - if (!ctx.hasSubType()) { - msg = "Found {0} objects of type {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, numberOfFiles, ctx.getType()); - } else { - msg = "Found {0} objects of type {1} and subtType {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, numberOfFiles, ctx.getType(), ctx.getSubType()); - } - + String msg = "Found {0} objects for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName()); logger.info(msg); } @@ -159,8 +126,16 @@ public class MetadataDao { * @return a set of types in the given query path */ private Set queryTypeSet(File queryPath) { - Set keySet = new HashSet(); + if (!queryPath.exists()) + return Collections.emptySet(); + if (!queryPath.isDirectory()) { + String msg = "The path is not a directory, thus can not query type set for it: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, queryPath.getAbsolutePath()); + throw new IllegalArgumentException(msg); + } + + Set keySet = new HashSet(); File[] subTypeFiles = queryPath.listFiles(); for (File subTypeFile : subTypeFiles) { if (subTypeFile.isFile()) { @@ -263,10 +238,26 @@ public class MetadataDao { return numberOfFiles; } - private void assertNotClosed() { - if (!this.tx.isOpen()) { + private void assertNotClosed(PersistenceTransaction tx) { + if (!tx.isOpen()) { String msg = "Transaction has been closed and thus no operation can be performed!"; //$NON-NLS-1$ throw new IllegalStateException(msg); } } + + private void assertNotIdRef(ObjectRef objectRef) { + if (objectRef.isLeaf()) { + String msg = "IdRef not allowed: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName()); + throw new IllegalArgumentException(msg); + } + } + + private void assertNotRootRef(ObjectRef objectRef) { + if (objectRef.isRoot()) { + String msg = "RootRef not allowed: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName()); + throw new IllegalArgumentException(msg); + } + } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java similarity index 60% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java rename to src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index 44957ff8a..ef5ead550 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -19,19 +19,20 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; -import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertHasId; -import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertHasType; -import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertNotNull; -import static ch.eitchnet.xmlpers.test.impl.rewrite.AssertionUtil.assertObjectRead; +import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsIdRef; +import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsNotIdRef; +import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsNotRootRef; +import static ch.eitchnet.xmlpers.util.AssertionUtil.assertNotNull; +import static ch.eitchnet.xmlpers.util.AssertionUtil.assertObjectRead; import java.util.ArrayList; import java.util.List; import java.util.Set; import ch.eitchnet.utils.objectfilter.ObjectFilter; -import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.objref.ObjectRef; /** * @author Robert von Burg @@ -39,10 +40,9 @@ import ch.eitchnet.xmlpers.api.PersistenceContext; */ public class ObjectDao { - private final PersistenceTransaction tx; private final ObjectFilter objectFilter; private final FileDao fileDao; - private boolean closed; + private final PersistenceTransaction tx; public ObjectDao(PersistenceTransaction tx, FileDao fileDao, ObjectFilter objectFilter) { this.tx = tx; @@ -92,83 +92,76 @@ public class ObjectDao { this.objectFilter.removeAll((List) objects); } - public void removeById(PersistenceContext context) { + public void removeById(ObjectRef objectRef) { assertNotClosed(); - assertHasType(context); - assertHasId(context); - this.objectFilter.remove(context.clone()); + assertIsIdRef(objectRef); + this.objectFilter.remove(objectRef); } - public void removeAll(PersistenceContext context) { + public void removeAll(ObjectRef parentRef) { assertNotClosed(); - assertHasType(context); + assertIsNotIdRef(parentRef); + assertIsNotRootRef(parentRef); - Set keySet = queryKeySet(context); + Set keySet = queryKeySet(parentRef); for (String id : keySet) { - PersistenceContext clone = context.clone(); - clone.setId(id); - this.objectFilter.remove(clone); + + ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); + PersistenceContext childCtx = childRef. createPersistenceContext(this.tx); + + this.objectFilter.remove(childCtx); } } - public T queryById(PersistenceContext context) { + public T queryById(ObjectRef objectRef) { assertNotClosed(); - assertHasType(context); - assertHasId(context); - this.fileDao.performRead(context); - return context.getObject(); + assertIsIdRef(objectRef); + PersistenceContext ctx = objectRef. createPersistenceContext(this.tx); + this.fileDao.performRead(ctx); + return ctx.getObject(); } - public List queryAll(PersistenceContext context) { + public List queryAll(ObjectRef parentRef) { assertNotClosed(); - assertHasType(context); + assertIsNotIdRef(parentRef); MetadataDao metadataDao = this.tx.getMetadataDao(); - Set keySet = metadataDao.queryKeySet(context); + Set keySet = metadataDao.queryKeySet(parentRef); List result = new ArrayList<>(); - PersistenceContext readContext = context.clone(); for (String id : keySet) { - readContext.setId(id); - this.fileDao.performRead(readContext); - assertObjectRead(readContext); - result.add(readContext.getObject()); + + ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); + PersistenceContext childCtx = childRef.createPersistenceContext(this.tx); + + this.fileDao.performRead(childCtx); + assertObjectRead(childCtx); + result.add(childCtx.getObject()); } return result; } - public Set queryKeySet(PersistenceContext context) { + public Set queryKeySet(ObjectRef parentRef) { assertNotClosed(); - assertHasType(context); - - assertNotClosed(); - assertHasType(context); + assertIsNotIdRef(parentRef); MetadataDao metadataDao = this.tx.getMetadataDao(); - Set keySet = metadataDao.queryKeySet(context); + Set keySet = metadataDao.queryKeySet(parentRef); return keySet; } - public long querySize(PersistenceContext context) { + public long querySize(ObjectRef parentRef) { assertNotClosed(); - assertHasType(context); - - assertNotClosed(); - assertHasType(context); + assertIsNotIdRef(parentRef); MetadataDao metadataDao = this.tx.getMetadataDao(); - long size = metadataDao.querySize(context); + long size = metadataDao.querySize(parentRef); return size; } - public void rollback() { - this.objectFilter.clearCache(); - this.closed = true; - } - private void assertNotClosed() { - if (this.closed || !this.tx.isOpen()) + if (!this.tx.isOpen()) throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); //$NON-NLS-1$ } } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java similarity index 95% rename from src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java rename to src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java index f1968952e..c06b54717 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceConstants.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java @@ -25,7 +25,8 @@ package ch.eitchnet.xmlpers.api; * @author Robert von Burg * */ -public class XmlPersistenceConstants { +@SuppressWarnings("nls") +public class PersistenceConstants { private static final String PROP_PREFIX = "ch.eitchnet.xmlpers."; public static final String PROP_VERBOSE = PROP_PREFIX + "verbose"; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java index 42d36b99c..5a074832f 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java @@ -21,44 +21,20 @@ */ package ch.eitchnet.xmlpers.api; -import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.xmlpers.objref.ObjectRef; public class PersistenceContext { + private final ObjectRef objectRef; private T object; - private String type; - private String subType; - private String id; - - private XmlIoMode ioMode; private ParserFactory parserFactory; - public PersistenceContext() { - this.ioMode = XmlIoMode.DEFAULT; + public PersistenceContext(ObjectRef objectRef) { + this.objectRef = objectRef; } - public String getType() { - return this.type; - } - - public void setType(String type) { - this.type = type; - } - - public String getSubType() { - return this.subType; - } - - public void setSubType(String subType) { - this.subType = subType; - } - - public String getId() { - return this.id; - } - - public void setId(String id) { - this.id = id; + public ObjectRef getObjectRef() { + return this.objectRef; } public T getObject() { @@ -69,14 +45,6 @@ public class PersistenceContext { this.object = object; } - public XmlIoMode getIoMode() { - return this.ioMode; - } - - public void setIoMode(XmlIoMode ioMode) { - this.ioMode = ioMode; - } - public ParserFactory getParserFactor() { return this.parserFactory; } @@ -84,28 +52,4 @@ public class PersistenceContext { public void setParserFactory(ParserFactory parserFactory) { this.parserFactory = parserFactory; } - - public boolean hasType() { - return !StringHelper.isEmpty(this.type); - } - - public boolean hasSubType() { - return !StringHelper.isEmpty(this.subType); - } - - public boolean hasId() { - return !StringHelper.isEmpty(this.id); - } - - @Override - public PersistenceContext clone() { - PersistenceContext clone = new PersistenceContext<>(); - clone.type = this.type; - clone.subType = this.subType; - clone.id = this.id; - clone.ioMode = this.ioMode; - clone.parserFactory = this.parserFactory; - clone.object = this.object; - return clone; - } } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java new file mode 100644 index 000000000..2735d4aeb --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java @@ -0,0 +1,12 @@ +package ch.eitchnet.xmlpers.api; + +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; + +public interface PersistenceContextFactory { + + public PersistenceContext createCtx(ObjectRef objectRef); + + public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, T t); + +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java new file mode 100644 index 000000000..92bff9299 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.api; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Robert von Burg + * + */ +public class PersistenceContextFactoryDelegator { + + private Map> contextFactoryCacheByType; + private Map, PersistenceContextFactory> contextFactoryCacheByClass; + + public PersistenceContextFactoryDelegator() { + this.contextFactoryCacheByType = new HashMap<>(); + this.contextFactoryCacheByClass = new HashMap<>(); + } + + public void registerPersistenceContextFactory(Class classType, String type, + PersistenceContextFactory ctxFactory) { + this.contextFactoryCacheByClass.put(classType, ctxFactory); + this.contextFactoryCacheByType.put(type, ctxFactory); + } + + public PersistenceContextFactory getCtxFactory(Class classType) { + + @SuppressWarnings("unchecked") + PersistenceContextFactory ctxFactory = (PersistenceContextFactory) this.contextFactoryCacheByClass + .get(classType); + if (ctxFactory != null) + return ctxFactory; + + String msg = "No context factory is registered for {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, classType); + throw new IllegalArgumentException(msg); + } + + public PersistenceContextFactory getCtxFactory(String type) { + + @SuppressWarnings("unchecked") + PersistenceContextFactory ctxFactory = (PersistenceContextFactory) this.contextFactoryCacheByType + .get(type); + if (ctxFactory != null) + return ctxFactory; + + String msg = "No context factory is registered for type {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, type); + throw new IllegalArgumentException(msg); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java similarity index 76% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManager.java rename to src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java index e57d02ca8..1d4d93fab 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManager.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java @@ -19,14 +19,20 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; /** * @author Robert von Burg * */ -public interface XmlPersistenceManager { +public interface PersistenceManager { + public static final String DEFAULT_REALM = "defaultRealm"; //$NON-NLS-1$ + + public PersistenceContextFactoryDelegator getCtxFactory(); + public PersistenceTransaction openTx(); + + public PersistenceTransaction openTx(String realm); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManagerLoader.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java similarity index 83% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManagerLoader.java rename to src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java index 4fbc34527..64e6aef82 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/XmlPersistenceManagerLoader.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java @@ -19,17 +19,19 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; import java.util.Properties; +import ch.eitchnet.xmlpers.impl.DefaultXmlPersistenceManager; + /** * @author Robert von Burg * */ -public class XmlPersistenceManagerLoader { +public class PersistenceManagerLoader { - public static XmlPersistenceManager load(Properties properties) { + public static PersistenceManager load(Properties properties) { DefaultXmlPersistenceManager persistenceManager = new DefaultXmlPersistenceManager(); persistenceManager.initialize(properties); diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java new file mode 100644 index 000000000..a83eeb014 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java @@ -0,0 +1,24 @@ +package ch.eitchnet.xmlpers.api; + +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; + +public interface PersistenceRealm { + + /** + * @return the realm + */ + public abstract String getRealmName(); + + public abstract PersistenceContextFactoryDelegator getCtxFactoryDelegator(); + + /** + * @return the objectRefCache + */ + public abstract ObjectReferenceCache getObjectRefCache(); + + /** + * @return the persistenceManager + */ + public abstract PersistenceManager getPersistenceManager(); + +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java similarity index 63% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java rename to src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java index 1109d65d1..0600aa2cf 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java @@ -19,19 +19,24 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.api; -import ch.eitchnet.xmlpers.api.XmlIoMode; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; /** * @author Robert von Burg * */ -public interface PersistenceTransaction { +public interface PersistenceTransaction extends AutoCloseable { - public void commit(PersistenceContextFactory ctxFactory); + public void setCloseStrategy(TransactionCloseStrategy closeStrategy); - public void rollback(); + public void autoCloseableCommit(); + + public void autoCloseableRollback(); + + @Override + public void close() throws XmlPersistenceException; public boolean isOpen(); @@ -39,7 +44,11 @@ public interface PersistenceTransaction { public MetadataDao getMetadataDao(); - public XmlIoMode getIoMode(); + public ObjectReferenceCache getObjectRefCache(); - public void setIoMode(XmlIoMode ioMode); + public PersistenceRealm getRealm(); + + public IoMode getIoMode(); + + public void setIoMode(IoMode ioMode); } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java b/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java index 9fc8d1dd9..f6e660175 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java @@ -25,8 +25,6 @@ import javax.xml.stream.XMLStreamException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; - public interface SaxParser { public T getObject(); diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java new file mode 100644 index 000000000..a535fe5c6 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java @@ -0,0 +1,21 @@ +package ch.eitchnet.xmlpers.api; + +public enum TransactionCloseStrategy { + COMMIT() { + @Override + public void close(PersistenceTransaction tx) { + tx.autoCloseableCommit(); + } + }, + + ROLLBACK() { + @Override + public void close(PersistenceTransaction tx) { + tx.autoCloseableRollback(); + } + }; + + public void close(PersistenceTransaction tx) { + throw new UnsupportedOperationException("Override in enum!"); //$NON-NLS-1$ + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java deleted file mode 100644 index 99592ff6c..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlIoMode.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -/** - * @author Robert von Burg - * - */ -public enum XmlIoMode { - - DEFAULT, DOM, SAX; -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceContextData.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceContextData.java deleted file mode 100644 index 821f26ea9..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceContextData.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -import java.io.File; - -/** - * @author Robert von Burg - * - */ -public class XmlPersistenceContextData { - - private File file; - - /** - * @return the file - */ - public File getFile() { - return this.file; - } - - /** - * @param file - * the file to set - */ - public void setFile(File file) { - this.file = file; - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java deleted file mode 100644 index 8923211fc..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDao.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.api; - -import java.util.List; -import java.util.Set; - -/** - * @author Robert von Burg - */ -public interface XmlPersistenceDao { - - public void add(T object); - - public void update(T object); - - public void remove(T object); - - public void removeById(String id); - - public void removeAll(); - - public T queryById(String id); - - public List queryAll(); - - public Set queryKeySet(); - - public long querySize(); -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java deleted file mode 100644 index 4b5ac1782..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceDaoFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.api; - -import java.util.Properties; - -import ch.eitchnet.xmlpers.impl.XmlPersistenceFileDao; - -/** - * @author Robert von Burg - */ -public interface XmlPersistenceDaoFactory { - - public void initialize(XmlPersistenceFileDao fileDao, Properties properties); - - public XmlPersistenceMetadataDao getMetadataDao(); - - public XmlPersistenceDao getDao(T object); - - public XmlPersistenceDao getDaoBy(String type); - - public XmlPersistenceDao getDaoBy(String type, String subType); -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceFileHandler.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceFileHandler.java deleted file mode 100644 index a3d71c00b..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceFileHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - - -/** - * @author Robert von Burg - * - */ -public interface XmlPersistenceFileHandler { - - public void read(XmlPersistenceContextData contextData); - - public void write(XmlPersistenceContextData contextData); -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java deleted file mode 100644 index 9f814a05a..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -/** - * @author Robert von Burg - * - */ -public interface XmlPersistenceHandler { - - public abstract XmlPersistenceTransaction openTx(); -} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java deleted file mode 100644 index 4f8e74b40..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceMetadataDao.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.api; - -import java.util.Set; - -/** - * @author Robert von Burg - */ -public interface XmlPersistenceMetadataDao { - - public Set queryTypeSet(); - - public Set queryTypeSet(String type); - - public Set queryKeySet(String type); - - public Set queryKeySet(String type, String subType); - - public long queryTypeSize(); - - public long querySubTypeSize(String type); - - public long querySize(String type); - - public long querySize(String type, String subType); -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxContextData.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxContextData.java deleted file mode 100644 index 8ab786c9a..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxContextData.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -import org.xml.sax.helpers.DefaultHandler; - - -/** - * @author Robert von Burg - * - */ -public class XmlPersistenceSaxContextData extends XmlPersistenceContextData { - - private DefaultHandler defaultHandler; - private XmlPersistenceSaxWriter xmlWriter; - - /** - * @return the defaultHandler - */ - public DefaultHandler getDefaultHandler() { - return this.defaultHandler; - } - - /** - * @param defaultHandler - * the defaultHandler to set - */ - public void setDefaultHandler(DefaultHandler defaultHandler) { - this.defaultHandler = defaultHandler; - } - - /** - * @return the xmlWriter - */ - public XmlPersistenceSaxWriter getXmlWriter() { - return this.xmlWriter; - } - - /** - * @param xmlWriter - * the xmlWriter to set - */ - public void setXmlWriter(XmlPersistenceSaxWriter xmlWriter) { - this.xmlWriter = xmlWriter; - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxWriter.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxWriter.java deleted file mode 100644 index df950f904..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceSaxWriter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -import javax.xml.stream.XMLStreamException; - -import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; - -/** - * @author Robert von Burg - * - */ -public interface XmlPersistenceSaxWriter { - - public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException; -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceStreamWriter.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java similarity index 98% rename from src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceStreamWriter.java rename to src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java index 7294e331d..7a758c901 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceStreamWriter.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.impl; +package ch.eitchnet.xmlpers.api; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java deleted file mode 100644 index ae6545d15..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceTransaction.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.api; - -import java.util.List; - -/** - * @author Robert von Burg - * - */ -public interface XmlPersistenceTransaction { - - /** - * @param object - */ - public abstract void add(T object); - - /** - * @param objects - */ - public abstract void addAll(List objects); - - /** - * @param object - */ - public abstract void update(T object); - - /** - * @param objects - */ - public abstract void updateAll(List objects); - - /** - * @param object - */ - public abstract void remove(T object); - - /** - * @param objects - */ - public abstract void removeAll(List objects); - - /** - * @return the daoFactory - */ - public abstract XmlPersistenceDaoFactory getDaoFactory(); - - /** - * - */ - public abstract void commit(); - - /** - * - */ - public abstract void clear(); -} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java new file mode 100644 index 000000000..b072576aa --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java @@ -0,0 +1,59 @@ +package ch.eitchnet.xmlpers.impl; + +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceManager; +import ch.eitchnet.xmlpers.api.PersistenceRealm; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; + +public class DefaultPersistenceRealm implements PersistenceRealm { + private final PersistenceManager persistenceManager; + private final PersistenceContextFactoryDelegator ctxFactory; + private final String realmName; + private final ObjectReferenceCache objectRefCache; + private final PathBuilder pathBuilder; + + public DefaultPersistenceRealm(String realm, PersistenceManager persistenceManager, + PersistenceContextFactoryDelegator ctxFactory, PathBuilder pathBuilder, ObjectReferenceCache objectRefCache) { + this.ctxFactory = ctxFactory; + this.pathBuilder = pathBuilder; + this.realmName = realm; + this.persistenceManager = persistenceManager; + this.objectRefCache = objectRefCache; + } + + /** + * @return the realm + */ + @Override + public String getRealmName() { + return this.realmName; + } + + @Override + public PersistenceContextFactoryDelegator getCtxFactoryDelegator() { + return this.ctxFactory; + } + + /** + * @return the objectRefCache + */ + @Override + public ObjectReferenceCache getObjectRefCache() { + return this.objectRefCache; + } + + /** + * @return the persistenceManager + */ + @Override + public PersistenceManager getPersistenceManager() { + return this.persistenceManager; + } + + /** + * @return the pathBuilder + */ + PathBuilder getPathBuilder() { + return this.pathBuilder; + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java similarity index 53% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java rename to src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index 598ab4dd1..ed1b3fa62 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -19,8 +19,9 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.impl; +import java.text.MessageFormat; import java.util.List; import java.util.Set; @@ -29,8 +30,17 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.objectfilter.ObjectFilter; +import ch.eitchnet.xmlpers.api.FileDao; +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.MetadataDao; +import ch.eitchnet.xmlpers.api.ObjectDao; import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.XmlIoMode; +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceRealm; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.api.TransactionCloseStrategy; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; /** * @author Robert von Burg @@ -40,23 +50,36 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { private static final Logger logger = LoggerFactory.getLogger(DefaultPersistenceTransaction.class); - private final FileDao fileDao; + private final DefaultPersistenceRealm realm; + private final boolean verbose; + + private final ObjectFilter objectFilter; private final ObjectDao objectDao; private final MetadataDao metadataDao; - private final ObjectFilter objectFilter; - private final boolean verbose; + + private FileDao fileDao; private boolean committed; private boolean closed; - private XmlIoMode ioMode; + private IoMode ioMode; - public DefaultPersistenceTransaction(FileDao fileDao, boolean verbose) { - this.fileDao = fileDao; + private TransactionCloseStrategy closeStrategy; + + public DefaultPersistenceTransaction(DefaultPersistenceRealm realm, boolean verbose) { + this.realm = realm; this.verbose = verbose; this.objectFilter = new ObjectFilter(); + this.fileDao = new FileDao(this, realm.getPathBuilder(), verbose); this.objectDao = new ObjectDao(this, this.fileDao, this.objectFilter); - this.metadataDao = new MetadataDao(this, this.fileDao, verbose); + this.metadataDao = new MetadataDao(realm.getPathBuilder(), this, verbose); + + this.closeStrategy = TransactionCloseStrategy.COMMIT; + } + + @Override + public PersistenceRealm getRealm() { + return this.realm; } @Override @@ -70,26 +93,45 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { } @Override - public void rollback() { + public ObjectReferenceCache getObjectRefCache() { + return this.realm.getObjectRefCache(); + } + + @Override + public void setCloseStrategy(TransactionCloseStrategy closeStrategy) { + this.closeStrategy = closeStrategy; + } + + @Override + public void close() throws XmlPersistenceException { + this.closeStrategy.close(this); + } + + @Override + public void autoCloseableRollback() { if (this.committed) throw new IllegalStateException("Transaction has already been committed!"); //$NON-NLS-1$ + if (!this.closed) { this.closed = true; - this.objectDao.rollback(); this.objectFilter.clearCache(); } } @Override - public void commit(PersistenceContextFactory ctxFactory) { + public void autoCloseableCommit() throws XmlPersistenceException { + + long start = System.nanoTime(); try { - long start = System.nanoTime(); - if (this.verbose) - logger.info("Committing TX..."); //$NON-NLS-1$ - Set keySet = this.objectFilter.keySet(); + + if (this.verbose) { + String msg = "Committing {0} operations in TX...";//$NON-NLS-1$ + logger.info(MessageFormat.format(msg, keySet.size())); + } + for (String key : keySet) { List removed = this.objectFilter.getRemoved(key); @@ -101,8 +143,11 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { logger.info(removed.size() + " objects removed in this tx."); //$NON-NLS-1$ for (Object object : removed) { - PersistenceContext context = ctxFactory.createCtx(this, object); - this.fileDao.performDelete(context); + PersistenceContextFactoryDelegator ctxFactoryDelegator = this.realm.getCtxFactoryDelegator(); + PersistenceContext ctx = ctxFactoryDelegator.getCtxFactory(object.getClass()) + .createCtx(this.realm.getObjectRefCache(), object); + ctx.setObject(object); + this.fileDao.performDelete(ctx); } } @@ -116,8 +161,11 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { for (Object object : updated) { - PersistenceContext context = ctxFactory.createCtx(this, object); - this.fileDao.performUpdate(context); + PersistenceContextFactoryDelegator ctxFactoryDelegator = this.realm.getCtxFactoryDelegator(); + PersistenceContext ctx = ctxFactoryDelegator.getCtxFactory(object.getClass()) + .createCtx(this.realm.getObjectRefCache(), object); + ctx.setObject(object); + this.fileDao.performUpdate(ctx); } } @@ -131,14 +179,24 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { for (Object object : added) { - PersistenceContext context = ctxFactory.createCtx(this, object); - this.fileDao.performCreate(context); + PersistenceContextFactoryDelegator ctxFactoryDelegator = this.realm.getCtxFactoryDelegator(); + PersistenceContext ctx = ctxFactoryDelegator.getCtxFactory(object.getClass()) + .createCtx(this.realm.getObjectRefCache(), object); + ctx.setObject(object); + this.fileDao.performCreate(ctx); } } } long end = System.nanoTime(); - logger.info("Completed TX in " + StringHelper.formatNanoDuration(end - start)); //$NON-NLS-1$ + logger.info("TX completed in " + StringHelper.formatNanoDuration(end - start)); //$NON-NLS-1$ + + } catch (Exception e) { + + long end = System.nanoTime(); + logger.info("TX failed after " + StringHelper.formatNanoDuration(end - start)); //$NON-NLS-1$ + + throw e; } finally { // clean up @@ -153,12 +211,12 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { } @Override - public void setIoMode(XmlIoMode ioMode) { + public void setIoMode(IoMode ioMode) { this.ioMode = ioMode; } @Override - public XmlIoMode getIoMode() { + public IoMode getIoMode() { return this.ioMode; } } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java new file mode 100644 index 000000000..cab1ddf90 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.impl; + +import java.io.File; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceManager; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; + +/** + * @author Robert von Burg + * + */ +public class DefaultXmlPersistenceManager implements PersistenceManager { + + protected static final Logger logger = LoggerFactory.getLogger(DefaultXmlPersistenceManager.class); + + protected boolean initialized; + protected boolean verbose; + protected Properties properties; + protected Map realmMap; + private PersistenceContextFactoryDelegator ctxFactory; + + public void initialize(Properties properties) { + if (this.initialized) + throw new IllegalStateException("Already initialized!"); //$NON-NLS-1$ + + String context = DefaultXmlPersistenceManager.class.getSimpleName(); + + // get verbose flag + boolean verbose = PropertiesHelper.getPropertyBool(properties, context, PersistenceConstants.PROP_VERBOSE, + Boolean.FALSE).booleanValue(); + + // validate base path + validateBasePath(properties); + + this.properties = properties; + this.verbose = verbose; + this.realmMap = new HashMap<>(); + this.ctxFactory = new PersistenceContextFactoryDelegator(); + } + + private void validateBasePath(Properties properties) { + String context = DefaultXmlPersistenceManager.class.getSimpleName(); + String basePath = PropertiesHelper.getProperty(properties, context, PersistenceConstants.PROP_BASEPATH, null); + + // validate base path exists and is writable + File basePathF = new File(basePath); + if (!basePathF.exists()) + throw new XmlPersistenceException(MessageFormat.format("The database store path does not exist at {0}", //$NON-NLS-1$ + basePathF.getAbsolutePath())); + if (!basePathF.canWrite()) + throw new XmlPersistenceException(MessageFormat.format("The database store path is not writeable at {0}", //$NON-NLS-1$ + basePathF.getAbsolutePath())); + } + + @Override + public PersistenceContextFactoryDelegator getCtxFactory() { + return this.ctxFactory; + } + + @Override + public PersistenceTransaction openTx() { + return openTx(DEFAULT_REALM); + } + + @Override + public synchronized PersistenceTransaction openTx(String realmName) { + + DefaultPersistenceRealm persistenceRealm = this.realmMap.get(realmName); + if (persistenceRealm == null) { + + PathBuilder pathBuilder = new PathBuilder(realmName, this.properties); + ObjectReferenceCache objectRefCache = new ObjectReferenceCache(realmName); + persistenceRealm = new DefaultPersistenceRealm(realmName, this, this.ctxFactory, pathBuilder, objectRefCache); + + this.realmMap.put(realmName, persistenceRealm); + } + + PersistenceTransaction tx = new DefaultPersistenceTransaction(persistenceRealm, this.verbose); + return tx; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java b/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java deleted file mode 100644 index 80dbd6317..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/MetadataXmlDao.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.impl; - -import java.util.Set; - -import ch.eitchnet.xmlpers.api.XmlPersistenceMetadataDao; - -/** - * @author Robert von Burg - * - */ -public class MetadataXmlDao implements XmlPersistenceMetadataDao { - - private XmlPersistenceFileDao fileDao; - - public void initialize(XmlPersistenceFileDao fileDao) { - if (fileDao == null) - throw new IllegalArgumentException("fileDao may not be null!"); - if (this.fileDao != null) - throw new IllegalStateException("DAO is already initialized!"); - this.fileDao = fileDao; - } - - /** - * @return the fileDao - */ - protected XmlPersistenceFileDao getFileDao() { - return this.fileDao; - } - - @Override - public Set queryTypeSet() { - return this.fileDao.queryTypeSet(); - } - - @Override - public Set queryTypeSet(String type) { - return this.fileDao.queryTypeSet(type); - } - - @Override - public Set queryKeySet(String type) { - return this.fileDao.queryKeySet(type); - } - - @Override - public Set queryKeySet(String type, String subType) { - return this.fileDao.queryKeySet(type, subType); - } - - @Override - public long queryTypeSize() { - return this.fileDao.queryTypeSize(); - } - - @Override - public long querySubTypeSize(String type) { - return this.fileDao.queryTypeSize(type); - } - - @Override - public long querySize(String type) { - return this.fileDao.querySize(type); - } - - @Override - public long querySize(String type, String subType) { - return this.fileDao.querySize(type, subType); - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java new file mode 100644 index 000000000..a0f593c07 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ch.eitchnet.java.xmlpers + * + * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ch.eitchnet.java.xmlpers. If not, see . + * + */ +package ch.eitchnet.xmlpers.impl; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; + +/** + * @author Robert von Burg + */ +public class PathBuilder { + + private static final Logger logger = LoggerFactory.getLogger(PathBuilder.class); + + public static final String FILE_EXT = ".xml"; //$NON-NLS-1$ + public static final int EXT_LENGTH = PathBuilder.FILE_EXT.length(); + + private final String basePath; + + public PathBuilder(String realm, Properties properties) { + + // get properties + String context = PathBuilder.class.getSimpleName(); + String basePath = PropertiesHelper.getProperty(properties, context, PersistenceConstants.PROP_BASEPATH, null); + + // validate base path exists and is writable + File basePathF = new File(basePath); + if (!basePathF.exists()) + throw new XmlPersistenceException(MessageFormat.format("The database store path does not exist at {0}", //$NON-NLS-1$ + basePathF.getAbsolutePath())); + if (!basePathF.canWrite()) + throw new XmlPersistenceException(MessageFormat.format("The database store path is not writeable at {0}", //$NON-NLS-1$ + basePathF.getAbsolutePath())); + + File realmPathF = new File(basePathF, realm); + if (!realmPathF.exists() && !realmPathF.mkdir()) + throw new XmlPersistenceException(MessageFormat.format("Could not create path for realm {0} at {1}", //$NON-NLS-1$ + realm, basePathF.getAbsolutePath())); + + // we want a clean base path + String canonicalBasePath; + try { + canonicalBasePath = realmPathF.getCanonicalPath(); + } catch (IOException e) { + throw new XmlPersistenceException(MessageFormat.format( + "Failed to build canonical path from {0}", realmPathF.getAbsolutePath()), e); //$NON-NLS-1$ + } + + // this.basePathF = basePathF; + this.basePath = canonicalBasePath; + logger.info(MessageFormat.format("Using base path {0}", this.basePath)); //$NON-NLS-1$ + } + + String getFilename(String id) { + return id.concat(PathBuilder.FILE_EXT); + } + + String getPathAsString(String type, String subType, String id) { + StringBuilder sb = new StringBuilder(this.basePath); + if (!StringHelper.isEmpty(type)) { + sb.append(File.separatorChar); + sb.append(type); + } + if (!StringHelper.isEmpty(subType)) { + sb.append(File.separatorChar); + sb.append(subType); + } + if (!StringHelper.isEmpty(id)) { + sb.append(File.separatorChar); + sb.append(getFilename(id)); + } + + return sb.toString(); + } + + public File getRootPath() { + File path = new File(getPathAsString(null, null, null)); + return path; + } + + public File getTypePath(String type) { + File path = new File(getPathAsString(type, null, null)); + return path; + } + + public File getSubTypePath(String type, String subType) { + File path = new File(getPathAsString(type, subType, null)); + return path; + } + + public File getIdOfTypePath(String type, String id) { + File path = new File(getPathAsString(type, null, id)); + return path; + } + + public File getIdOfSubTypePath(String type, String subType, String id) { + File path = new File(getPathAsString(type, subType, id)); + return path; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/TransactionDaoFactoryFacade.java b/src/main/java/ch/eitchnet/xmlpers/impl/TransactionDaoFactoryFacade.java deleted file mode 100644 index 3aa95a8ec..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/TransactionDaoFactoryFacade.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.impl; - -import java.util.Properties; - -import ch.eitchnet.xmlpers.api.XmlPersistenceDao; -import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.api.XmlPersistenceMetadataDao; - -/** - * @author Robert von Burg - * - */ -public class TransactionDaoFactoryFacade implements XmlPersistenceDaoFactory { - - private XmlPersistenceDaoFactory daoFactory; - private XmlPersistenceTransactionImpl tx; - - TransactionDaoFactoryFacade(XmlPersistenceDaoFactory daoFactory) { - this.daoFactory = daoFactory; - } - - void setTx(XmlPersistenceTransactionImpl tx) { - this.tx = tx; - } - - /** - * @throws UnsupportedOperationException - * as this method may not be called on the facade - */ - @Override - public void initialize(XmlPersistenceFileDao fileDao, Properties properties) throws UnsupportedOperationException { - throw new UnsupportedOperationException(); - } - - /** - * @return - * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getMetadataDao() - */ - @Override - public XmlPersistenceMetadataDao getMetadataDao() { - assertTxOpen(); - return this.daoFactory.getMetadataDao(); - } - - /** - * @param object - * @return - * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getDao(java.lang.Object) - */ - @Override - public XmlPersistenceDao getDao(T object) { - assertTxOpen(); - return this.daoFactory.getDao(object); - } - - /** - * @param type - * @return - * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getDaoBy(java.lang.String) - */ - @Override - public XmlPersistenceDao getDaoBy(String type) { - assertTxOpen(); - return this.daoFactory.getDaoBy(type); - } - - /** - * @param type - * @param subType - * @return - * @see ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory#getDaoBy(java.lang.String, java.lang.String) - */ - @Override - public XmlPersistenceDao getDaoBy(String type, String subType) { - assertTxOpen(); - return this.daoFactory.getDaoBy(type, subType); - } - - private void assertTxOpen() { - if (this.tx.isCleared()) { - throw new XmlPersistenceException( - "The transaction has already been closed, thus no operation may be performed anymore with this dao factory instance"); - } - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java deleted file mode 100644 index 30484e387..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceDomHandler.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.impl; - -import java.io.File; -import java.io.IOException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Source; -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.xml.sax.SAXException; - -import ch.eitchnet.utils.exceptions.XmlException; -import ch.eitchnet.utils.helper.XmlHelper; -import ch.eitchnet.xmlpers.api.DomUtil; -import ch.eitchnet.xmlpers.api.XmlPersistenceContextData; -import ch.eitchnet.xmlpers.api.XmlPersistenceDomContextData; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.api.XmlPersistenceFileHandler; - -/** - * @author Robert von Burg - * - */ -public class XmlPersistenceDomHandler implements XmlPersistenceFileHandler { - - private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceDomHandler.class); - - @Override - public void read(XmlPersistenceContextData contextData) { - - XmlPersistenceDomContextData cd = (XmlPersistenceDomContextData) contextData; - - // check assertions - if (cd.getFile() == null) - throw new IllegalStateException("No file has been set on the context data!"); - - try { - DocumentBuilder docBuilder = DomUtil.createDocumentBuilder(); - File file = cd.getFile(); - Document document = docBuilder.parse(file); - cd.setDocument(document); - } catch (SAXException | IOException e) { - throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); - } - - logger.info("DOM parsed file " + cd.getFile().getAbsolutePath()); - } - - @Override - public void write(XmlPersistenceContextData contextData) { - - XmlPersistenceDomContextData cd = (XmlPersistenceDomContextData) contextData; - - // check assertions - if (cd.getFile() == null) - throw new IllegalStateException("No file has been set on the context data!"); - if (cd.getDocument() == null) - throw new IllegalStateException("No document has been set on the context data!"); - - String lineSep = System.getProperty(XmlHelper.PROP_LINE_SEPARATOR); - try { - Document document = cd.getDocument(); - String encoding = document.getInputEncoding(); - if (encoding == null || encoding.isEmpty()) { - logger.info("No encoding passed. Using default encoding " + XmlHelper.DEFAULT_ENCODING); - encoding = XmlHelper.DEFAULT_ENCODING; - } - - if (!lineSep.equals("\n")) { - logger.info("Overriding line separator to \\n"); - System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, "\n"); - } - - // Set up a transformer - TransformerFactory transfac = TransformerFactory.newInstance(); - Transformer transformer = transfac.newTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.ENCODING, encoding); - transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); - // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); - - // Transform to file - StreamResult result = new StreamResult(cd.getFile()); - Source xmlSource = new DOMSource(document); - transformer.transform(xmlSource, result); - - logger.info("Wrote DOM to " + cd.getFile().getAbsolutePath()); - - } catch (TransformerFactoryConfigurationError | TransformerException e) { - throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); - } finally { - System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, lineSep); - } - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java deleted file mode 100644 index ae905546a..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileDao.java +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.impl; - -import java.io.File; -import java.text.MessageFormat; -import java.util.Collections; -import java.util.HashSet; -import java.util.Properties; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.PropertiesHelper; -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.test.impl.rewrite.FilenameUtility; - -/** - * @author Robert von Burg - */ -public class XmlPersistenceFileDao { - - private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceFileDao.class); - - private boolean verbose; - private XmlPersistencePathBuilder pathBuilder; - - public XmlPersistenceFileDao(XmlPersistencePathBuilder pathBuilder, Properties properties) { - - // get properties - String context = XmlPersistencePathBuilder.class.getSimpleName(); - boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, - Boolean.FALSE).booleanValue(); - - // initialize - this.verbose = verbose; - this.pathBuilder = pathBuilder; - } - - /** - * Does a recursive and complete deletion of all objects, types and sub-types - */ - public void removeAll() { - - Set types = queryTypeSet(); - for (String type : types) { - - Set idsByType = queryKeySet(type); - for (String id : idsByType) { - remove(type, id); - } - - Set subTypes = queryTypeSet(type); - for (String subType : subTypes) { - - Set idsBySubType = queryKeySet(type, subType); - for (String id : idsBySubType) { - remove(type, subType, id); - } - } - } - } - - public void removeAll(String type) { - - Set idsByType = queryKeySet(type); - for (String id : idsByType) { - remove(type, id); - } - - Set subTypes = queryTypeSet(type); - for (String subType : subTypes) { - - Set idsBySubType = queryKeySet(type, subType); - for (String id : idsBySubType) { - remove(type, subType, id); - } - } - } - - public void removeAll(String type, String subType) { - - Set idsBySubType = queryKeySet(type, subType); - for (String id : idsBySubType) { - remove(type, subType, id); - } - } - - public void remove(String type, String id) { - File removePath = this.pathBuilder.getRemovePath(type, id); - String failMsg = "Deletion of persistence units for {0} / {1} at {2} failed! Check file permissions!"; - remove(removePath, failMsg, type, id, removePath); - - // if no more objects with this type exist, then delete the path - failMsg = "Deletion of empty directory for {0} at {2} failed! Check file permissions!"; - File parentFile = removePath.getParentFile(); - deleteEmptyDirectory(parentFile, failMsg, type, parentFile); - } - - public void remove(String type, String subType, String id) { - File removePath = this.pathBuilder.getRemovePath(type, subType, id); - String failMsg = "Deletion of persistence units for {0} / {1} / {2} at {3} failed! Check file permissions!"; - remove(removePath, failMsg, type, subType, id, removePath); - - // if no more objects with this subType exist, then delete the path - failMsg = "Deletion of empty directory for {0} / {1} at {2} failed! Check file permissions!"; - File parentFile = removePath.getParentFile(); - deleteEmptyDirectory(parentFile, failMsg, type, subType, parentFile); - } - - public File getAddPath(String type, String id) { - return this.pathBuilder.getAddPath(type, id); - } - - public File getAddPath(String type, String subType, String id) { - return this.pathBuilder.getAddPath(type, subType, id); - } - - public File getReadPath(String type, String id) { - return this.pathBuilder.getReadPath(type, id); - } - - public File getReadPath(String type, String subType, String id) { - return this.pathBuilder.getReadPath(type, subType, id); - } - - public File getUpdatePath(String type, String id) { - return this.pathBuilder.getUpdatePath(type, id); - } - - public File getUpdatePath(String type, String subType, String id) { - return this.pathBuilder.getUpdatePath(type, subType, id); - } - - /** - * Returns the set of types - * - * @return - */ - public Set queryTypeSet() { - File queryPath = this.pathBuilder.getQueryPath(); - Set keySet = queryTypeSet(queryPath); - if (this.verbose) - XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " types"); - return keySet; - } - - /** - * Returns the set of sub types for the given type - * - * @return - */ - public Set queryTypeSet(String type) { - File queryPath = this.pathBuilder.getQueryPath(type); - Set keySet = queryTypeSet(queryPath); - if (this.verbose) - XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " subTypes of type " + type); - return keySet; - } - - /** - * Returns the object ids for the given type - * - * @param type - * - * @return - */ - public Set queryKeySet(String type) { - File queryPath = this.pathBuilder.getQueryPath(type); - Set keySet = queryKeySet(queryPath); - if (this.verbose) - XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " elements for " + type); - return keySet; - } - - /** - * Returns the object ids for the give type and sub type - * - * @param type - * @param subType - * - * @return - */ - public Set queryKeySet(String type, String subType) { - File queryPath = this.pathBuilder.getQueryPath(type, subType); - Set keySet = queryKeySet(queryPath); - if (this.verbose) - XmlPersistenceFileDao.logger.info("Found " + keySet.size() + " elements for " + type); - return keySet; - } - - public long queryTypeSize() { - File queryPath = this.pathBuilder.getQueryPath(); - long numberOfFiles = queryTypeSize(queryPath); - if (this.verbose) - XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " types"); - return numberOfFiles; - } - - public long queryTypeSize(String type) { - File queryPath = this.pathBuilder.getQueryPath(type); - long numberOfFiles = queryTypeSize(queryPath); - if (this.verbose) - XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " elements for " + type); - return numberOfFiles; - } - - public long querySize(String type) { - File queryPath = this.pathBuilder.getQueryPath(type); - long numberOfFiles = querySize(queryPath); - if (this.verbose) - XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " elements for " + type); - return numberOfFiles; - } - - public long querySize(String type, String subType) { - File queryPath = this.pathBuilder.getQueryPath(type, subType); - long numberOfFiles = querySize(queryPath); - if (this.verbose) - XmlPersistenceFileDao.logger.info("Found " + numberOfFiles + " elements for " + type); - return numberOfFiles; - } - - /** - * Returns the types, i.e. directories in the given query path - * - * @param queryPath - * the path for which the types should be gathered - * - * @return a set of types in the given query path - */ - private Set queryTypeSet(File queryPath) { - Set keySet = new HashSet(); - - File[] subTypeFiles = queryPath.listFiles(); - for (File subTypeFile : subTypeFiles) { - if (subTypeFile.isFile()) { - String filename = subTypeFile.getName(); - String id = FilenameUtility.getId(filename); - keySet.add(id); - } - } - - return keySet; - } - - /** - * Returns the ids of all objects in the given query path, i.e. the id part of all the files in the given query path - * - * @param queryPath - * the path for which the ids should be gathered - * - * @return a set of ids for the objects in the given query path - */ - private Set queryKeySet(File queryPath) { - if (!queryPath.exists()) - return Collections.emptySet(); - if (!queryPath.isDirectory()) - throw new IllegalArgumentException("The path is not a directory, thus can not query key set for it: " - + queryPath.getAbsolutePath()); - - Set keySet = new HashSet(); - - File[] subTypeFiles = queryPath.listFiles(); - for (File subTypeFile : subTypeFiles) { - if (subTypeFile.isFile()) { - String filename = subTypeFile.getName(); - String id = FilenameUtility.getId(filename); - keySet.add(id); - } - } - - return keySet; - } - - /** - * Returns the number of all types, i.e. directories in the given query path - * - * @param queryPath - * the path in which to count the types - * - * @return the number of types in the given query path - */ - private long queryTypeSize(File queryPath) { - if (!queryPath.exists()) - return 0L; - if (!queryPath.isDirectory()) - throw new IllegalArgumentException("The path is not a directory, thus can not query type size for it: " - + queryPath.getAbsolutePath()); - - long numberOfFiles = 0l; - - File[] subTypeFiles = queryPath.listFiles(); - for (File subTypeFile : subTypeFiles) { - - if (subTypeFile.isDirectory()) - numberOfFiles++; - } - return numberOfFiles; - } - - /** - * Returns the number of all objects in the given query path - * - * @param queryPath - * the path in which to count the objects - * - * @return the number of objects in the given query path - */ - private long querySize(File queryPath) { - if (!queryPath.exists()) - return 0L; - if (!queryPath.isDirectory()) - throw new IllegalArgumentException("The path is not a directory, thus can not query key size for it: " - + queryPath.getAbsolutePath()); - - long numberOfFiles = 0l; - - File[] subTypeFiles = queryPath.listFiles(); - for (File subTypeFile : subTypeFiles) { - - if (subTypeFile.isFile()) - numberOfFiles++; - } - return numberOfFiles; - } - - private void remove(File removePath, String failMsg, Object... msgParts) { - if (!removePath.isFile()) - throw new IllegalArgumentException("The given path for deletion is not a file:" - + removePath.getAbsolutePath()); - if (!removePath.delete()) { - String msg = MessageFormat.format(failMsg, msgParts); - throw new XmlPersistenceException(msg); - } - } - - private void deleteEmptyDirectory(File directoryPath, String failMsg, Object... msgArgs) { - if (!directoryPath.isDirectory()) - throw new IllegalArgumentException("The given path for deletion when empty is not a directory:" - + directoryPath.getAbsolutePath()); - if (directoryPath.list().length == 0) { - if (this.verbose) { - String msg = "Deleting empty directory for type {0} at {1}"; - logger.info(MessageFormat.format(msg, directoryPath.getName(), directoryPath)); - } - if (!directoryPath.delete()) { - String msg = MessageFormat.format(failMsg, msgArgs); - throw new XmlPersistenceException(msg); - } - } - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileIoHandler.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileIoHandler.java deleted file mode 100644 index 35f377dd9..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceFileIoHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.impl; - -import java.io.File; - -import ch.eitchnet.xmlpers.api.DaoContext; - -/** - * @author Robert von Burg - * - */ -public interface XmlPersistenceFileIoHandler { - - public void read(DaoContext context, File file); - - public void write(DaoContext context, File file); -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceHandlerImpl.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceHandlerImpl.java deleted file mode 100644 index 98790ba01..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceHandlerImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.impl; - -import java.util.Properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.PropertiesHelper; -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.api.XmlPersistenceHandler; -import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; - -/** - * @author Robert von Burg - * - */ -public class XmlPersistenceHandlerImpl implements XmlPersistenceHandler { - - protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandlerImpl.class); - - protected boolean initialized; - protected boolean verbose; - protected XmlPersistenceDaoFactory daoFactory; - - public void initialize(Properties properties) { - if (this.initialized) - throw new IllegalStateException("Already initialized!"); - - // get properties - String context = XmlPersistenceHandlerImpl.class.getSimpleName(); - boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, - Boolean.FALSE).booleanValue(); - String daoFactoryClassName = PropertiesHelper.getProperty(properties, context, - XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, null); - - // load dao factory - XmlPersistenceDaoFactory daoFactory; - try { - @SuppressWarnings("unchecked") - Class xmlDaoFactoryClass = (Class) Class - .forName(daoFactoryClassName); - - daoFactory = xmlDaoFactoryClass.newInstance(); - - } catch (ClassNotFoundException e) { - throw new XmlPersistenceException("XmlDaoFactory class does not exist " + daoFactoryClassName, e); - } catch (Exception e) { - throw new XmlPersistenceException("Failed to load class " + daoFactoryClassName, e); - } - - // initialize the dao factory - XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(properties); - XmlPersistenceFileDao fileDao = new XmlPersistenceFileDao(pathBuilder, properties); - daoFactory.initialize(fileDao, properties); - - this.daoFactory = daoFactory; - this.verbose = verbose; - } - - @Override - public XmlPersistenceTransaction openTx() { - - TransactionDaoFactoryFacade daoFactoryFacade = new TransactionDaoFactoryFacade(this.daoFactory); - XmlPersistenceTransactionImpl tx = new XmlPersistenceTransactionImpl(daoFactoryFacade, this.verbose); - daoFactoryFacade.setTx(tx); - XmlPersistenceTransactionImpl.setTx(tx); - return tx; - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java deleted file mode 100644 index a70d09444..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistencePathBuilder.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.impl; - -import java.io.File; -import java.io.IOException; -import java.text.MessageFormat; -import java.util.Properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.PropertiesHelper; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; - -/** - * @author Robert von Burg - */ -public class XmlPersistencePathBuilder { - private static final String SLASH = "/"; //$NON-NLS-1$ - - private static final Logger logger = LoggerFactory.getLogger(XmlPersistencePathBuilder.class); - - public static final String FILE_EXT = ".xml"; //$NON-NLS-1$ - public static final int EXT_LENGTH = XmlPersistencePathBuilder.FILE_EXT.length(); - - private final boolean verbose; - private final String basePath; - - public XmlPersistencePathBuilder(Properties properties) { - - // get properties - String context = XmlPersistencePathBuilder.class.getSimpleName(); - boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, - Boolean.FALSE).booleanValue(); - String basePath = PropertiesHelper - .getProperty(properties, context, XmlPersistenceConstants.PROP_BASEPATH, null); - - // validate base path exists and is writable - File basePathF = new File(basePath); - if (!basePathF.exists()) - throw new XmlPersistenceException(MessageFormat.format("The database store path does not exist at {0}", //$NON-NLS-1$ - basePathF.getAbsolutePath())); - if (!basePathF.canWrite()) - throw new XmlPersistenceException(MessageFormat.format("The database store path is not writeable at {0}", //$NON-NLS-1$ - basePathF.getAbsolutePath())); - - // we want a clean base path - String canonicalBasePath; - try { - canonicalBasePath = basePathF.getCanonicalPath(); - } catch (IOException e) { - throw new XmlPersistenceException( - MessageFormat.format("Failed to build canonical path from {0}", basePath), e); //$NON-NLS-1$ - } - - // this.basePathF = basePathF; - this.basePath = canonicalBasePath; - this.verbose = verbose; - - logger.info(MessageFormat.format("Using base path {0}", this.basePath)); //$NON-NLS-1$ - } - - String getFilename(String id) { - return id.concat(XmlPersistencePathBuilder.FILE_EXT); - } - - String getPathAsString(String type, String subType, String id) { - StringBuilder sb = new StringBuilder(this.basePath); - if (!StringHelper.isEmpty(type)) { - sb.append(SLASH); - sb.append(type); - } - if (!StringHelper.isEmpty(subType)) { - sb.append(SLASH); - sb.append(subType); - } - if (!StringHelper.isEmpty(id)) { - sb.append(SLASH); - sb.append(getFilename(id)); - } - - return sb.toString(); - } - - File getAddPath(String type, String id) { - assertType(type); - assertId(id); - - File path = new File(getPathAsString(type, null, id)); - - // assert path exists - String msg = "Persistence unit already exists for {0} / {1} at {2}"; //$NON-NLS-1$ - assertPathNotExists(path, msg, type, id, path.getAbsolutePath()); - - // check if parent path exists - msg = "Could not create parent path for {0} / {1} at {2}"; //$NON-NLS-1$ - createMissingParents(path, msg, type, id, path.getAbsolutePath()); - - return path; - } - - File getAddPath(String type, String subType, String id) { - assertType(type); - assertSubType(subType); - assertId(id); - - File path = new File(getPathAsString(type, subType, id)); - - // assert path exists - String msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ - assertPathNotExists(path, msg, type, subType, id, path.getAbsolutePath()); - - // check if parent path exists - msg = "Could not create parent path for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ - createMissingParents(path, msg, type, subType, id, path.getAbsolutePath()); - - return path; - } - - File getReadPath(String type, String id) { - assertType(type); - assertId(id); - File path = new File(getPathAsString(type, null, id)); - if (this.verbose) { - String msg = "Query path for {0} / {1} is {2}..."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, type, id, path.getAbsolutePath())); - } - return path; - } - - File getReadPath(String type, String subType, String id) { - assertType(type); - assertSubType(subType); - assertId(id); - File path = new File(getPathAsString(type, subType, id)); - if (this.verbose) { - String msg = "Query path for {0} / {1} / {2} is {3}..."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); - } - return path; - } - - File getUpdatePath(String type, String id) { - assertType(type); - assertId(id); - - File path = new File(getPathAsString(type, null, id)); - - if (!path.exists()) { - String msg = "Persistence unit does not exist for {0} / {1} at {2}"; //$NON-NLS-1$ - throw new XmlPersistenceException(MessageFormat.format(msg, type, id, path.getAbsolutePath())); - } - - return path; - } - - File getUpdatePath(String type, String subType, String id) { - assertType(type); - assertSubType(subType); - assertId(id); - - File path = new File(getPathAsString(type, subType, id)); - - if (!path.exists()) { - String msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ - throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); - } - - return path; - } - - File getRemovePath(String type, String id) { - assertType(type); - assertId(id); - - File path = new File(getPathAsString(type, null, id)); - if (!path.exists()) { - String msg = "No Persistence units exist for {0} / {1} at {2}"; //$NON-NLS-1$ - throw new XmlPersistenceException(MessageFormat.format(msg, type, id, path.getAbsolutePath())); - } - - if (this.verbose) { - String msg = "Remove path for {0} / {1} is {2}..."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, type, id, path.getAbsolutePath())); - } - - return path; - } - - File getRemovePath(String type, String subType, String id) { - assertType(type); - assertSubType(subType); - assertId(id); - - File path = new File(getPathAsString(type, subType, id)); - if (!path.exists()) { - String msg = "Persistence unit for {0} / {1} / {2} does not exist at {3}"; //$NON-NLS-1$ - throw new XmlPersistenceException(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); - } - - if (this.verbose) { - String msg = "Remove path for {0} / {1} / {2} is {3}..."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, type, subType, id, path.getAbsolutePath())); - } - - return path; - } - - File getQueryPath() { - return new File(getPathAsString(null, null, null)); - } - - File getQueryPath(String type) { - assertType(type); - File path = new File(getPathAsString(type, null, null)); - if (this.verbose) { - String msg = "Query path for {0} is {1}..."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, type, path.getAbsolutePath())); - } - return path; - } - - File getQueryPath(String type, String subType) { - assertType(type); - assertSubType(subType); - File path = new File(getPathAsString(type, subType, null)); - if (this.verbose) { - String msg = "Query path for {0} / {1} is {2}..."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, type, subType, path.getAbsolutePath())); - } - return path; - } - - private void assertId(String id) { - if (StringHelper.isEmpty(id)) - throw new XmlPersistenceException( - "The id can not be empty! An object must always be handled with at least the type and id!"); //$NON-NLS-1$ - } - - private void assertType(String type) { - if (StringHelper.isEmpty(type)) - throw new XmlPersistenceException( - "The type can not be empty! An object must always be handled with at least the type and id!"); //$NON-NLS-1$ - } - - private void assertSubType(String subType) { - if (StringHelper.isEmpty(subType)) - throw new XmlPersistenceException("The subType can not be empty!"); //$NON-NLS-1$ - } - - private void assertPathNotExists(File path, String msg, Object... args) { - if (path.exists()) { - throw new XmlPersistenceException(MessageFormat.format(msg, args)); - } - } - - private void createMissingParents(File path, String msg, Object... args) { - File parentFile = path.getParentFile(); - if (!parentFile.exists() && !parentFile.mkdirs()) { - throw new XmlPersistenceException(MessageFormat.format(msg, args)); - } - } - - private void logPath(String operation, File path, PersistenceContext context) { - if (this.verbose) { - String msg; - if (StringHelper.isEmpty(context.getSubType())) { - msg = "Path for operation {0} for {1} / {2} / is at {3}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, operation, context.getType(), context.getId(), path.getAbsolutePath()); - } else { - msg = "Path for operation {0} for {1} / {2} / {3} / is at {4}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, operation, context.getType(), context.getSubType(), context.getId(), - path.getAbsolutePath()); - } - } - } - - private void createMissingParents(File path, PersistenceContext context) { - File parentFile = path.getParentFile(); - if (!parentFile.exists() && !parentFile.mkdirs()) { - String msg; - if (StringHelper.isEmpty(context.getSubType())) { - msg = "Could not create parent path for {0} / {1} / at {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); - } else { - msg = "Could not create parent path for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), - path.getAbsolutePath()); - } - - throw new XmlPersistenceException(msg); - } - } - - private void assertPathIsFileAndWritable(File path, PersistenceContext context) { - if (!path.exists()) { - String msg; - if (StringHelper.isEmpty(context.getSubType())) { - msg = "Persistence unit does not exist for {0} / {1} at {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); - } else { - msg = "Persistence unit does not exist for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), - path.getAbsolutePath()); - } - - throw new XmlPersistenceException(msg); - } - - if (!path.isFile() || !path.canWrite()) { - String msg; - if (StringHelper.isEmpty(context.getSubType())) { - msg = "Persistence unit is not a file or is not readable for {0} / {1} at {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); - } else { - msg = "Persistence unit is not a file or is not readable for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), - path.getAbsolutePath()); - } - - throw new XmlPersistenceException(msg); - } - } - - private void assertPathNotExists(File path, PersistenceContext context) { - if (path.exists()) { - String msg; - if (StringHelper.isEmpty(context.getSubType())) { - msg = "Persistence unit already exists for {0} / {1} at {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getId(), path.getAbsolutePath()); - } else { - msg = "Persistence unit already exists for {0} / {1} / {2} at {3}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId(), - path.getAbsolutePath()); - } - - throw new XmlPersistenceException(msg); - } - } - - public File getCreatePath(PersistenceContext context) { - File path = getPath(context); - logPath("CREATE", path, context); //$NON-NLS-1$ - assertPathNotExists(path, context); - createMissingParents(path, context); - return path; - } - - public File getDeletePath(PersistenceContext context) { - File path = getPath(context); - logPath("DELETE", path, context); //$NON-NLS-1$ - assertPathIsFileAndWritable(path, context); - return path; - } - - public File getUpdatePath(PersistenceContext context) { - File path = getPath(context); - logPath("UPDATE", path, context); //$NON-NLS-1$ - assertPathIsFileAndWritable(path, context); - return path; - } - - public File getReadPath(PersistenceContext context) { - File path = getPath(context); - logPath("READ", path, context); //$NON-NLS-1$ - if (!path.exists()) - return null; - return path; - } - - public File getPath(PersistenceContext context) { - File path = new File(getPathAsString(context.getType(), context.getSubType(), context.getId())); - return path; - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceSaxHandler.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceSaxHandler.java deleted file mode 100644 index 52c5ef0cf..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceSaxHandler.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.impl; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -import javanet.staxutils.IndentingXMLStreamWriter; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import javax.xml.stream.FactoryConfigurationError; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import ch.eitchnet.utils.exceptions.XmlException; -import ch.eitchnet.xmlpers.api.XmlPersistenceContextData; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.api.XmlPersistenceFileHandler; -import ch.eitchnet.xmlpers.api.XmlPersistenceSaxContextData; - -/** - * @author Robert von Burg - * - */ -public class XmlPersistenceSaxHandler implements XmlPersistenceFileHandler { - - private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceSaxHandler.class); - - @Override - public void read(XmlPersistenceContextData contextData) { - - XmlPersistenceSaxContextData cd = (XmlPersistenceSaxContextData) contextData; - - // check assertions - if (cd.getFile() == null) - throw new IllegalStateException("No file has been set on the context data!"); - if (cd.getDefaultHandler() == null) - throw new IllegalStateException("No DefaultHandler has been set on the context data!"); - - File file; - try { - - SAXParserFactory spf = SAXParserFactory.newInstance(); - SAXParser sp = spf.newSAXParser(); - - file = cd.getFile(); - DefaultHandler defaultHandler = cd.getDefaultHandler(); - sp.parse(file, defaultHandler); - - } catch (ParserConfigurationException | SAXException | IOException e) { - - throw new XmlPersistenceException("Parsing failed due to internal error: " + e.getMessage(), e); - } - - logger.info("SAX parsed file " + file.getAbsolutePath()); - } - - @Override - public void write(XmlPersistenceContextData contextData) { - - XmlPersistenceSaxContextData cd = (XmlPersistenceSaxContextData) contextData; - - // check assertions - if (cd.getFile() == null) - throw new IllegalStateException("No file has been set on the context data!"); - if (cd.getXmlWriter() == null) - throw new IllegalStateException("No Xml writer has been set on the context data!"); - - XMLStreamWriter writer = null; - try { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - File file = cd.getFile(); - writer = factory.createXMLStreamWriter(new FileWriter(file)); - writer = new IndentingXMLStreamWriter(writer); - - // start document - writer.writeStartDocument("utf-8", "1.0"); - - // then delegate object writing to caller - XmlPersistenceStreamWriter xmlWriter = new XmlPersistenceStreamWriter(writer); - cd.getXmlWriter().write(xmlWriter); - - // and now end - writer.writeEndDocument(); - writer.flush(); - - } catch (FactoryConfigurationError | XMLStreamException | IOException e) { - throw new XmlException("Writing to file failed due to internal error: " + e.getMessage(), e); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (Exception e) { - logger.error("Failed to close stream: " + e.getMessage()); - } - } - } - - logger.info("Wrote SAX to " + cd.getFile().getAbsolutePath()); - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java b/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java deleted file mode 100644 index 6b27aa0bd..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/impl/XmlPersistenceTransactionImpl.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.impl; - -import java.util.List; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.objectfilter.ObjectFilter; -import ch.eitchnet.xmlpers.api.XmlPersistenceDao; -import ch.eitchnet.xmlpers.api.XmlPersistenceDaoFactory; -import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; - -/** - * @author Robert von Burg - */ -public class XmlPersistenceTransactionImpl implements XmlPersistenceTransaction { - - private static final Logger logger = LoggerFactory.getLogger(XmlPersistenceTransactionImpl.class); - - private static final ThreadLocal TX_THREADLOCAL_THREAD_LOCAL; - static { - TX_THREADLOCAL_THREAD_LOCAL = new ThreadLocal<>(); - } - - private XmlPersistenceDaoFactory daoFactory; - private ObjectFilter objectFilter; - private boolean verbose; - private boolean cleared; - - /** - * @param verbose - */ - public XmlPersistenceTransactionImpl(XmlPersistenceDaoFactory daoFactory, boolean verbose) { - this.daoFactory = daoFactory; - this.verbose = verbose; - this.objectFilter = new ObjectFilter(); - } - - /* - * modifying methods - */ - - /** - * @param object - */ - @Override - public void add(T object) { - this.objectFilter.add(object); - } - - /** - * @param objects - */ - @Override - @SuppressWarnings("unchecked") - public void addAll(List objects) { - this.objectFilter.addAll((List) objects); - } - - /** - * @param object - */ - @Override - public void update(T object) { - this.objectFilter.update(object); - } - - /** - * @param objects - */ - @Override - @SuppressWarnings("unchecked") - public void updateAll(List objects) { - this.objectFilter.updateAll((List) objects); - } - - /** - * @param object - */ - @Override - public void remove(T object) { - this.objectFilter.remove(object); - } - - /** - * @param objects - */ - @Override - @SuppressWarnings("unchecked") - public void removeAll(List objects) { - this.objectFilter.removeAll((List) objects); - } - - /** - * @return the daoFactory - */ - @Override - public XmlPersistenceDaoFactory getDaoFactory() { - return this.daoFactory; - } - - /** - * - */ - @Override - public void commit() { - - try { - long start = System.nanoTime(); - if (this.verbose) - XmlPersistenceTransactionImpl.logger.info("Committing TX..."); - Set keySet = this.objectFilter.keySet(); - if (keySet.isEmpty()) - return; - for (String key : keySet) { - - List removed = this.objectFilter.getRemoved(key); - if (removed.isEmpty()) { - if (this.verbose) - XmlPersistenceTransactionImpl.logger.info("No objects removed in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransactionImpl.logger.info(removed.size() + " objects removed in this tx."); - - for (Object object : removed) { - XmlPersistenceDao dao = this.daoFactory.getDao(object); - dao.remove(object); - } - } - - List updated = this.objectFilter.getUpdated(key); - if (updated.isEmpty()) { - if (this.verbose) - XmlPersistenceTransactionImpl.logger.info("No objects updated in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransactionImpl.logger.info(updated.size() + " objects updated in this tx."); - - for (Object object : updated) { - - XmlPersistenceDao dao = this.daoFactory.getDao(object); - dao.update(object); - } - } - - List added = this.objectFilter.getAdded(key); - if (added.isEmpty()) { - if (this.verbose) - XmlPersistenceTransactionImpl.logger.info("No objects added in this tx."); - } else { - if (this.verbose) - XmlPersistenceTransactionImpl.logger.info(added.size() + " objects added in this tx."); - - for (Object object : added) { - - XmlPersistenceDao dao = this.daoFactory.getDao(object); - dao.add(object); - } - } - } - - long end = System.nanoTime(); - logger.info("Completed TX in " + StringHelper.formatNanoDuration(end - start)); //$NON-NLS-1$ - - } finally { - // clean up - clear(); - } - } - - /** - * Clears the object filter and releases the transaction. After calling this method, this transaction instance can - * not be used anymore - */ - @Override - public void clear() { - if (!this.cleared) { - this.objectFilter.clearCache(); - this.objectFilter = null; - - this.daoFactory = null; - TX_THREADLOCAL_THREAD_LOCAL.set(null); - this.cleared = true; - } - } - - /** - * @return - */ - public boolean isCleared() { - return this.cleared; - } - - public static XmlPersistenceTransaction getTx() { - XmlPersistenceTransaction tx = TX_THREADLOCAL_THREAD_LOCAL.get(); - if (tx == null) - throw new IllegalStateException("No transaction is currently open!"); - return tx; - } - - public static void setTx(XmlPersistenceTransactionImpl tx) { - if (TX_THREADLOCAL_THREAD_LOCAL.get() != null) - throw new IllegalStateException("A transaction is already open!"); - TX_THREADLOCAL_THREAD_LOCAL.set(tx); - } -} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java new file mode 100644 index 000000000..d2c4b37a6 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java @@ -0,0 +1,79 @@ +package ch.eitchnet.xmlpers.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class IdOfSubTypeRef extends ObjectRef { + + private final String type; + private final String subType; + private final String id; + + public IdOfSubTypeRef(String realmName, String type, String subType, String id) { + super(realmName, RefNameCreator.createIdOfSubTypeName(realmName, type, subType, id)); + this.type = type; + this.subType = subType; + this.id = id; + } + + public String getType() { + return this.type; + } + + public String getSubType() { + return this.subType; + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + return tx.getRealm().getObjectRefCache().getSubTypeRef(this.type, this.subType); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + String msg = MessageFormat.format("Already a leaf: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + String msg = MessageFormat.format("Already a leaf: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getIdOfSubTypePath(this.type, this.subType, this.id); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + PersistenceContextFactoryDelegator ctxFactoryDelegator = tx.getRealm().getCtxFactoryDelegator(); + PersistenceContextFactory persistenceContextFactory = ctxFactoryDelegator + . getCtxFactory(this.type); + return persistenceContextFactory.createCtx(this); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java new file mode 100644 index 000000000..40db517c3 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java @@ -0,0 +1,73 @@ +package ch.eitchnet.xmlpers.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class IdOfTypeRef extends ObjectRef { + + private final String type; + private final String id; + + public IdOfTypeRef(String realmName, String type, String id) { + super(realmName, RefNameCreator.createIdOfTypeName(realmName, type, id)); + this.type = type; + this.id = id; + } + + public String getType() { + return this.type; + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + return tx.getRealm().getObjectRefCache().getTypeRef(this.type); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + String msg = MessageFormat.format("Already a leaf: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + String msg = MessageFormat.format("Already a leaf: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getIdOfTypePath(this.type, this.id); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + PersistenceContextFactoryDelegator ctxFactoryDelegator = tx.getRealm().getCtxFactoryDelegator(); + PersistenceContextFactory persistenceContextFactory = ctxFactoryDelegator + . getCtxFactory(this.type); + return persistenceContextFactory.createCtx(this); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java new file mode 100644 index 000000000..250f3e488 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java @@ -0,0 +1,29 @@ +package ch.eitchnet.xmlpers.objref; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class LockableObject { + + private final Lock lock; + + public LockableObject() { + this.lock = new ReentrantLock(); + } + + /** + * @return + * @see java.util.concurrent.locks.Lock#tryLock() + */ + public boolean tryLock() { + return this.lock.tryLock(); + } + + /** + * + * @see java.util.concurrent.locks.Lock#unlock() + */ + public void unlock() { + this.lock.unlock(); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java new file mode 100644 index 000000000..9feb80282 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java @@ -0,0 +1,40 @@ +package ch.eitchnet.xmlpers.objref; + +import java.io.File; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public abstract class ObjectRef extends LockableObject { + + private String realmName; + private String name; + + protected ObjectRef(String realmName, String name) { + this.realmName = realmName; + this.name = name; + } + + public String getRealmName() { + return this.realmName; + } + + public String getName() { + return this.name; + } + + public abstract boolean isRoot(); + + public abstract boolean isLeaf(); + + public abstract ObjectRef getParent(PersistenceTransaction tx); + + public abstract ObjectRef getChildIdRef(PersistenceTransaction tx, String id); + + public abstract ObjectRef getChildTypeRef(PersistenceTransaction tx, String type); + + public abstract File getPath(PathBuilder pathBuilder); + + public abstract PersistenceContext createPersistenceContext(PersistenceTransaction tx); +} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java new file mode 100644 index 000000000..7a2a64412 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java @@ -0,0 +1,80 @@ +package ch.eitchnet.xmlpers.objref; + +import java.util.HashMap; +import java.util.Map; + +import ch.eitchnet.utils.helper.StringHelper; + +public class ObjectReferenceCache { + + private final String realmName; + private final RootRef rootRef; + private final Map typeRefMap; + private final Map subTypeRefMap; + private final Map idOfTypeRefMap; + private final Map idOfSubTypeRefMap; + + public ObjectReferenceCache(String realmName) { + if (StringHelper.isEmpty(realmName)) + throw new IllegalArgumentException("Realm name may not be empty!"); //$NON-NLS-1$ + + this.realmName = realmName; + this.rootRef = new RootRef(this.realmName); + this.typeRefMap = new HashMap<>(); + this.subTypeRefMap = new HashMap<>(); + this.idOfTypeRefMap = new HashMap<>(); + this.idOfSubTypeRefMap = new HashMap<>(); + } + + public String getRealmName() { + return this.realmName; + } + + public synchronized RootRef getRootRef() { + return this.rootRef; + } + + public synchronized TypeRef getTypeRef(String type) { + String key = RefNameCreator.createTypeName(this.realmName, type); + TypeRef ref = this.typeRefMap.get(key); + if (ref == null) { + ref = new TypeRef(this.realmName, type); + this.typeRefMap.put(key, ref); + } + + return ref; + } + + public synchronized SubTypeRef getSubTypeRef(String type, String subType) { + String key = RefNameCreator.createSubTypeName(this.realmName, type, subType); + SubTypeRef ref = this.subTypeRefMap.get(key); + if (ref == null) { + ref = new SubTypeRef(this.realmName, type, subType); + this.subTypeRefMap.put(key, ref); + } + + return ref; + } + + public synchronized IdOfTypeRef getIdOfTypeRef(String type, String id) { + String key = RefNameCreator.createIdOfTypeName(this.realmName, type, id); + IdOfTypeRef idRef = this.idOfTypeRefMap.get(key); + if (idRef == null) { + idRef = new IdOfTypeRef(this.realmName, type, id); + this.idOfTypeRefMap.put(key, idRef); + } + + return idRef; + } + + public synchronized IdOfSubTypeRef getIdOfSubTypeRef(String type, String subType, String id) { + String key = RefNameCreator.createIdOfSubTypeName(this.realmName, type, subType, id); + IdOfSubTypeRef idRef = this.idOfSubTypeRefMap.get(key); + if (idRef == null) { + idRef = new IdOfSubTypeRef(this.realmName, type, subType, id); + this.idOfSubTypeRefMap.put(key, idRef); + } + + return idRef; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java b/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java new file mode 100644 index 000000000..d899a410f --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java @@ -0,0 +1,65 @@ +package ch.eitchnet.xmlpers.objref; + +import ch.eitchnet.utils.helper.StringHelper; + +public class RefNameCreator { + + protected static final String SLASH = "/"; //$NON-NLS-1$ + + public static String createRootName(String realmName) { + assertRealmName(realmName); + return SLASH + realmName + SLASH; + } + + public static String createTypeName(String realmName, String type) { + assertRealmName(realmName); + assertType(type); + return SLASH + realmName + SLASH + type + SLASH; + } + + public static String createIdOfTypeName(String realmName, String type, String id) { + assertRealmName(realmName); + assertType(type); + assertId(id); + return SLASH + realmName + SLASH + type + SLASH + id + SLASH; + } + + public static String createSubTypeName(String realmName, String type, String subType) { + assertRealmName(realmName); + assertType(type); + assertSubType(subType); + return SLASH + realmName + SLASH + type + SLASH + subType + SLASH; + } + + public static String createIdOfSubTypeName(String realmName, String type, String subType, String id) { + assertRealmName(realmName); + assertType(type); + assertSubType(subType); + assertId(id); + return SLASH + realmName + SLASH + type + SLASH + subType + SLASH + id + SLASH; + } + + private static void assertRealmName(String realmName) { + if (StringHelper.isEmpty(realmName)) { + throw new IllegalArgumentException("Realm name may not be empty!"); //$NON-NLS-1$ + } + } + + private static void assertType(String type) { + if (StringHelper.isEmpty(type)) { + throw new IllegalArgumentException("type may not be empty!"); //$NON-NLS-1$ + } + } + + private static void assertSubType(String subType) { + if (StringHelper.isEmpty(subType)) { + throw new IllegalArgumentException("subType may not be empty!"); //$NON-NLS-1$ + } + } + + private static void assertId(String id) { + if (StringHelper.isEmpty(id)) { + throw new IllegalArgumentException("id may not be empty!"); //$NON-NLS-1$ + } + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java new file mode 100644 index 000000000..204ceec8c --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java @@ -0,0 +1,53 @@ +package ch.eitchnet.xmlpers.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class RootRef extends ObjectRef { + + public RootRef(String realmName) { + super(realmName, RefNameCreator.createRootName(realmName)); + } + + @Override + public boolean isRoot() { + return true; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + String msg = MessageFormat.format("RootRef has no parent: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + String msg = MessageFormat.format("RootRef has no child id: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + return tx.getRealm().getObjectRefCache().getTypeRef(type); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getRootPath(); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java new file mode 100644 index 000000000..29064486c --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java @@ -0,0 +1,65 @@ +package ch.eitchnet.xmlpers.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class SubTypeRef extends ObjectRef { + + private final String type; + private final String subType; + + public SubTypeRef(String realmName, String type, String subType) { + super(realmName, RefNameCreator.createSubTypeName(realmName, type, subType)); + this.type = type; + this.subType = subType; + } + + public String getType() { + return this.type; + } + + public String getSubType() { + return this.subType; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + return tx.getRealm().getObjectRefCache().getTypeRef(this.type); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + return tx.getRealm().getObjectRefCache().getIdOfSubTypeRef(this.type, this.subType, id); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + String msg = MessageFormat.format("A SubType has no child type: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getSubTypePath(this.type, this.subType); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java new file mode 100644 index 000000000..6a588527f --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java @@ -0,0 +1,58 @@ +package ch.eitchnet.xmlpers.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class TypeRef extends ObjectRef { + + private final String type; + + public TypeRef(String realmName, String type) { + super(realmName, RefNameCreator.createTypeName(realmName, type)); + this.type = type; + } + + public String getType() { + return this.type; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + return tx.getRealm().getObjectRefCache().getRootRef(); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + return tx.getRealm().getObjectRefCache().getIdOfTypeRef(this.type, id); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + return tx.getRealm().getObjectRefCache().getSubTypeRef(this.type, type); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getTypePath(this.type); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java b/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java new file mode 100644 index 000000000..cdd7d24be --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java @@ -0,0 +1,47 @@ +package ch.eitchnet.xmlpers.util; + +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.objref.ObjectRef; + +public class AssertionUtil { + + public static void assertNotNull(Object object) { + if (object == null) + throw new RuntimeException("Object may not be null!"); //$NON-NLS-1$ + } + + public static void assertObjectRead(PersistenceContext context) { + if (context.getObject() == null) { + String msg = "Failed to read object with for {0}"; //$NON-NLS-1$ + ObjectRef objectRef = context.getObjectRef(); + msg = MessageFormat.format(msg, objectRef.getName()); + throw new RuntimeException(msg); + } + } + + public static void assertIsIdRef(ObjectRef objectRef) { + if (!objectRef.isLeaf()) { + String msg = "Expected IdRef not {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName()); + throw new IllegalArgumentException(msg); + } + } + + public static void assertIsNotIdRef(ObjectRef objectRef) { + if (objectRef.isLeaf()) { + String msg = "IdRef not expected {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName()); + throw new IllegalArgumentException(msg); + } + } + + public static void assertIsNotRootRef(ObjectRef objectRef) { + if (!objectRef.isRoot()) { + String msg = "RootRef not expected {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName()); + throw new IllegalArgumentException(msg); + } + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/DomUtil.java b/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java similarity index 81% rename from src/main/java/ch/eitchnet/xmlpers/api/DomUtil.java rename to src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java index 54959a342..f3e06cf60 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/DomUtil.java +++ b/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java @@ -19,12 +19,16 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.api; +package ch.eitchnet.xmlpers.util; + +import java.text.MessageFormat; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; + /** * @author Robert von Burg * @@ -37,7 +41,9 @@ public class DomUtil { DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); return docBuilder; } catch (ParserConfigurationException e) { - throw new XmlPersistenceException("No Xml Parser could be loaded: " + e.getMessage(), e); + String msg = "No Xml Parser could be loaded: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, e.getMessage()); + throw new XmlPersistenceException(msg, e); } } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FilenameUtility.java b/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java similarity index 50% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FilenameUtility.java rename to src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java index 30cd558fc..2632531c9 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FilenameUtility.java +++ b/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java @@ -1,21 +1,21 @@ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.util; import java.text.MessageFormat; import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; +import ch.eitchnet.xmlpers.impl.PathBuilder; public class FilenameUtility { public static String getId(String filename) { assertFilename(filename); - return filename.substring(0, filename.length() - XmlPersistencePathBuilder.EXT_LENGTH); + return filename.substring(0, filename.length() - PathBuilder.EXT_LENGTH); } public static void assertFilename(String filename) { - if (filename.charAt(filename.length() - XmlPersistencePathBuilder.EXT_LENGTH) != '.') { + if (filename.charAt(filename.length() - PathBuilder.EXT_LENGTH) != '.') { String msg = "The filename does not have a . (dot) at index {0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, (filename.length() - XmlPersistencePathBuilder.EXT_LENGTH)); + msg = MessageFormat.format(msg, (filename.length() - PathBuilder.EXT_LENGTH)); throw new XmlPersistenceException(msg); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java deleted file mode 100644 index a5224b98a..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/AbstractXmlPersistenceTest.java +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.test; - -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.BOOK_ID; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE_INEXISTANT; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBook; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBookUpdated; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createBook; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateBook; - -import java.io.File; -import java.util.List; -import java.util.Properties; -import java.util.Set; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.xmlpers.api.XmlPersistenceDao; -import ch.eitchnet.xmlpers.api.XmlPersistenceHandler; -import ch.eitchnet.xmlpers.api.XmlPersistenceMetadataDao; -import ch.eitchnet.xmlpers.api.XmlPersistenceTransaction; -import ch.eitchnet.xmlpers.impl.XmlPersistenceHandlerImpl; -import ch.eitchnet.xmlpers.test.impl.Book; -import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.ModelBuilder; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - */ -public abstract class AbstractXmlPersistenceTest { - - protected static final Logger logger = LoggerFactory.getLogger(AbstractXmlPersistenceTest.class.getName()); - protected static XmlPersistenceHandler persistenceHandler; - protected XmlPersistenceTransaction tx; - - /** - * @throws Exception - * if something goes wrong - */ - public static void init(Properties props) throws Exception { - - try { - - cleanUpDb(); - - String userDir = System.getProperty("user.dir"); - String basePath = userDir + "/target/testdb"; - File basePathF = new File(basePath); - if (!basePathF.exists() && !basePathF.mkdirs()) - Assert.fail("Could not create temporaray database store in " + basePathF.getAbsolutePath()); - - AbstractXmlPersistenceTest.persistenceHandler = new XmlPersistenceHandlerImpl(); - ((XmlPersistenceHandlerImpl) AbstractXmlPersistenceTest.persistenceHandler).initialize(props); - AbstractXmlPersistenceTest.logger.info("Initialized persistence handler."); - - } catch (Exception e) { - - AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); - throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); - } - } - - @AfterClass - public static void cleanUpDb() { - String userDir = System.getProperty("user.dir"); - String basePath = userDir + "/target/testdb"; - File basePathF = new File(basePath); - if (basePathF.exists() && !FileHelper.deleteFile(basePathF, true)) - Assert.fail("Could not delete temporaray database store at " + basePathF.getAbsolutePath()); - } - - @After - public void tearDown() { - if (this.tx != null) - this.tx.clear(); - } - - /** - * Tests the following story: - *
      - *
    • create book
    • - *
    • read book
    • - *
    • update book
    • - *
    • remove book
    • - *
    - */ - @Test - public void testBookPersistence() { - - // create - Book book = createBook(); - assertBook(book); - this.tx = persistenceHandler.openTx(); - this.tx.add(book); - this.tx.commit(); - - // read - this.tx = persistenceHandler.openTx(); - XmlPersistenceDao dao = this.tx.getDaoFactory().getDao(book); - Book persistedBook = dao.queryById(String.valueOf(BOOK_ID)); - assertBook(persistedBook); - - // update - updateBook(persistedBook); - this.tx.update(persistedBook); - this.tx.commit(); - - // read - this.tx = persistenceHandler.openTx(); - dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_BOOK); - persistedBook = dao.queryById(String.valueOf(BOOK_ID)); - assertBookUpdated(persistedBook); - - // delete - this.tx.remove(book); - this.tx.commit(); - - // fail to read - this.tx = persistenceHandler.openTx(); - dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_BOOK); - persistedBook = dao.queryById(String.valueOf(BOOK_ID)); - Assert.assertNull(persistedBook); - } - - /** - * Tests the following story: - *
      - *
    • create resource
    • - *
    • read resource
    • - *
    • update resource
    • - *
    • remove resource
    • - *
    - */ - @Test - public void testResourcePersistence() { - - persistResource(); - readResource(); - updateResource(); - removeResource(); - } - - private void persistResource() { - - try { - AbstractXmlPersistenceTest.logger.info("Trying to create..."); - - // new instance - Resource resource = createResource(); - assertResource(resource); - - // persist instance - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - this.tx.add(resource); - this.tx.commit(); - - AbstractXmlPersistenceTest.logger.info("Done creating."); - - } catch (Exception e) { - AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); - Assert.fail("Failed by exception: " + e.getLocalizedMessage()); - } - } - - private void readResource() { - - try { - AbstractXmlPersistenceTest.logger.info("Trying to read..."); - - // query Resource - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); - Resource resource = dao.queryById(RES_ID); - AbstractXmlPersistenceTest.logger.info("Found Resource: " + resource); - assertResource(resource); - this.tx.commit(); - - AbstractXmlPersistenceTest.logger.info("Done reading."); - - } catch (Exception e) { - AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); - Assert.fail("Failed by exception: " + e.getLocalizedMessage()); - } - } - - private void updateResource() { - - try { - AbstractXmlPersistenceTest.logger.info("Trying to update an object..."); - - // query the instance - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); - Resource resource = dao.queryById(RES_ID); - AbstractXmlPersistenceTest.logger.info("Found Resource: " + resource); - assertResource(resource); - - // modify the instance - ModelBuilder.updateResource(resource); - - // update the instance - this.tx.update(resource); - this.tx.commit(); - - // re-read and validate - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); - resource = dao.queryById(RES_ID); - this.tx.commit(); - AbstractXmlPersistenceTest.logger.info("Found Resource: " + resource); - assertResourceUpdated(resource); - - AbstractXmlPersistenceTest.logger.info("Done updating."); - - } catch (Exception e) { - AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); - Assert.fail("Failed by exception: " + e.getLocalizedMessage()); - } - } - - private void removeResource() { - - AbstractXmlPersistenceTest.logger.info("Trying to remove..."); - - // query the instance - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); - Resource resource = dao.queryById(RES_ID); - AbstractXmlPersistenceTest.logger.info("Found Resource: " + resource); - assertResourceUpdated(resource); - - this.tx.remove(resource); - this.tx.commit(); - - AbstractXmlPersistenceTest.logger.info("Done removing."); - } - - @Test - public void testQueryFail() { - - AbstractXmlPersistenceTest.logger.info("Trying to query removed object..."); - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); - Resource resource = dao.queryById(RES_ID); - this.tx.commit(); - Assert.assertNull("Expected resource not to be found!", resource); - } - - @Test - public void testReCreate() { - - try { - AbstractXmlPersistenceTest.logger.info("Trying to recreate..."); - - Resource resource = createResource(); - assertResource(resource); - - // persist instance - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - this.tx.add(resource); - this.tx.commit(); - - AbstractXmlPersistenceTest.logger.info("Done creating."); - - } catch (Exception e) { - AbstractXmlPersistenceTest.logger.error(e.getMessage(), e); - Assert.fail("Failed by exception: " + e.getLocalizedMessage()); - } - } - - @Test - @Ignore - public void testQueryFromTo() { - Assert.fail("Not yet implemented"); - } - - @Test - public void testQueryAll() { - - AbstractXmlPersistenceTest.logger.info("Trying to query all..."); - - // query all - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); - List list = dao.queryAll(); - Assert.assertEquals("Expected only one object, found " + list, 1, list.size()); - - // and now something useless - dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE_INEXISTANT); - list = dao.queryAll(); - this.tx.commit(); - Assert.assertEquals("Expected no objects, found " + list, 0, list.size()); - - AbstractXmlPersistenceTest.logger.info("Done querying."); - } - - @Test - public void testKeySet() { - - // first prepare by creating a resource - createResource(); - - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - XmlPersistenceMetadataDao metadataDao = this.tx.getDaoFactory().getMetadataDao(); - - // query by type only, which should return nothing on this level - Set keySet = metadataDao.queryKeySet(TestConstants.TYPE_RES); - Assert.assertEquals("A resource can only be queried by type/subtype, but dao returned values!", 0, - keySet.size()); - - // now we shoud find our resource with the given type - keySet = metadataDao.queryKeySet(TestConstants.TYPE_RES, RES_TYPE); - Assert.assertEquals("Expected one key, found " + keySet, 1, keySet.size()); - - // and now something useless - keySet = metadataDao.queryKeySet(TestConstants.TYPE_RES, RES_TYPE_INEXISTANT); - Assert.assertEquals("Expected no keys, found " + keySet, 0, keySet.size()); - - this.tx.commit(); - } - - @Test - public void testRemoveAll() { - - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - XmlPersistenceDao dao = this.tx.getDaoFactory().getDaoBy(TestConstants.TYPE_RES, RES_TYPE); - List objects = dao.queryAll(); - this.tx.removeAll(objects); - this.tx.commit(); - } - - @Test - public void testSize() { - - this.tx = AbstractXmlPersistenceTest.persistenceHandler.openTx(); - XmlPersistenceMetadataDao metadataDao = this.tx.getDaoFactory().getMetadataDao(); - long size = metadataDao.querySize(TestConstants.TYPE_RES, RES_TYPE); - this.tx.commit(); - Assert.assertEquals("Expected size = 0, found: " + size, 0, size); - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java new file mode 100644 index 000000000..adbb13677 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.util.Properties; + +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.xmlpers.api.FileDao; +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceManager; +import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.DefaultPersistenceRealm; +import ch.eitchnet.xmlpers.impl.DefaultPersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import ch.eitchnet.xmlpers.test.impl.ResourceContextFactory; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +@SuppressWarnings("nls") +public class FileDaoTest { + + private static final String TEST_PATH = "target/dbTest"; + private static final boolean VERBOSE = true; + private static Properties properties; + private PersistenceManager persistenceManager; + private DefaultPersistenceRealm realm; + private PathBuilder pathBuilder; + + @BeforeClass + public static void beforeClass() { + File file = new File(TEST_PATH).getAbsoluteFile(); + if (file.exists() && file.isDirectory()) + if (!FileHelper.deleteFiles(file.listFiles(), true)) + throw new RuntimeException("Could not clean up path " + file.getAbsolutePath()); + + if (!file.exists() && !file.mkdir()) + throw new RuntimeException("Failed to create path " + file); + + File domFile = new File(file, "dom"); + if (!domFile.mkdir()) + throw new RuntimeException("Failed to create path " + domFile); + + File saxFile = new File(file, "sax"); + if (!saxFile.mkdir()) + throw new RuntimeException("Failed to create path " + saxFile); + + properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ + } + + private void setup(String subPath) { + properties.setProperty(PersistenceConstants.PROP_BASEPATH, TEST_PATH + subPath); + this.persistenceManager = PersistenceManagerLoader.load(properties); + this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Resource.class, + TestConstants.TYPE_RES, new ResourceContextFactory()); + + ObjectReferenceCache objectRefCache = new ObjectReferenceCache(PersistenceManager.DEFAULT_REALM); + this.pathBuilder = new PathBuilder(PersistenceManager.DEFAULT_REALM, FileDaoTest.properties); + this.realm = new DefaultPersistenceRealm(PersistenceManager.DEFAULT_REALM, this.persistenceManager, + this.persistenceManager.getCtxFactory(), this.pathBuilder, objectRefCache); + } + + @Test + public void testCrudSax() { + setup("/sax/"); + try (PersistenceTransaction tx = new DefaultPersistenceTransaction(this.realm, VERBOSE)) { + FileDao fileDao = new FileDao(tx, this.pathBuilder, VERBOSE); + tx.setIoMode(IoMode.SAX); + testCrud(tx, this.realm.getCtxFactoryDelegator(), fileDao); + } + } + + @Test + public void testCrudDom() { + setup("/dom/"); + try (PersistenceTransaction tx = new DefaultPersistenceTransaction(this.realm, VERBOSE)) { + FileDao fileDao = new FileDao(tx, this.pathBuilder, VERBOSE); + tx.setIoMode(IoMode.DOM); + testCrud(tx, this.realm.getCtxFactoryDelegator(), fileDao); + } + } + + private void testCrud(PersistenceTransaction tx, PersistenceContextFactoryDelegator ctxFactoryDelegator, + FileDao fileDao) { + + Resource resource = createResource(); + assertResource(resource); + Class classType = resource.getClass(); + PersistenceContextFactory ctxFactory = ctxFactoryDelegator.getCtxFactory(classType); + ObjectReferenceCache objectRefCache = this.realm.getObjectRefCache(); + PersistenceContext context = ctxFactory.createCtx(objectRefCache, resource); + context.setObject(resource); + fileDao.performCreate(context); + + context.setObject(null); + fileDao.performRead(context); + assertResource(context.getObject()); + + updateResource(context.getObject()); + fileDao.performUpdate(context); + + context.setObject(null); + fileDao.performRead(context); + assertResourceUpdated(context.getObject()); + + fileDao.performDelete(context); + + context.setObject(null); + fileDao.performRead(context); + assertNull(context.getObject()); + + context.setObject(createResource()); + fileDao.performCreate(context); + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java new file mode 100644 index 000000000..36503a683 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.ObjectDao; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceManager; +import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; +import ch.eitchnet.xmlpers.objref.SubTypeRef; +import ch.eitchnet.xmlpers.test.impl.ResourceContextFactory; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class ObjectDaoTest { + + private static final String BASEPATH = "target/dbTest/rewrite"; //$NON-NLS-1$ + + private PersistenceManager persistenceManager; + + private static Properties properties; + + @BeforeClass + public static void beforeClass() { + + File basePath = new File(BASEPATH); + if (basePath.exists()) { + if (!FileHelper.deleteFile(basePath, true)) { + throw new RuntimeException("Faile to delete base path " + BASEPATH); //$NON-NLS-1$ + } + } + + if (!basePath.mkdirs()) { + throw new RuntimeException("Failed to create base path " + BASEPATH); //$NON-NLS-1$ + } + + new File(BASEPATH + "/sax").mkdir(); //$NON-NLS-1$ + new File(BASEPATH + "/dom").mkdir(); //$NON-NLS-1$ + + properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ + } + + private void setup(String subPath) { + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASEPATH + subPath); + this.persistenceManager = PersistenceManagerLoader.load(properties); + + this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Resource.class, + TestConstants.TYPE_RES, new ResourceContextFactory()); + } + + @Test + public void testCrudSax() { + setup("/sax"); //$NON-NLS-1$ + testCrud(IoMode.SAX); + } + + @Test + public void testCrudDom() { + setup("/dom"); //$NON-NLS-1$ + testCrud(IoMode.DOM); + } + + private PersistenceTransaction freshTx(IoMode ioMode) { + PersistenceTransaction tx = this.persistenceManager.openTx(); + tx.setIoMode(ioMode); + return tx; + } + + private void testCrud(IoMode ioMode) { + + ObjectDao objectDao; + + // create new resource + Resource resource = createResource(); + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.add(resource); + } + + // read resource + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfSubTypeRef resRef = tx.getObjectRefCache().getIdOfSubTypeRef(TestConstants.TYPE_RES, RES_TYPE, RES_ID); + objectDao = tx.getObjectDao(); + resource = objectDao.queryById(resRef); + assertResource(resource); + + // modify resource + updateResource(resource); + objectDao.update(resource); + } + + // read modified resource + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfSubTypeRef resRef = tx.getObjectRefCache().getIdOfSubTypeRef(TestConstants.TYPE_RES, RES_TYPE, RES_ID); + objectDao = tx.getObjectDao(); + resource = objectDao.queryById(resRef); + assertResourceUpdated(resource); + } + + // delete resource + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.remove(resource); + } + + // fail to read + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfSubTypeRef resRef = tx.getObjectRefCache().getIdOfSubTypeRef(TestConstants.TYPE_RES, RES_TYPE, RES_ID); + objectDao = tx.getObjectDao(); + resource = objectDao.queryById(resRef); + assertNull(resource); + + // and create again + resource = createResource(); + assertResource(resource); + objectDao.add(resource); + } + } + + @Test + public void testBulkSax() { + setup("/sax"); //$NON-NLS-1$ + IoMode ioMode = IoMode.SAX; + testBulk(ioMode); + } + + @Test + public void testBulkDom() { + setup("/dom"); //$NON-NLS-1$ + IoMode ioMode = IoMode.DOM; + testBulk(ioMode); + } + + private void testBulk(IoMode ioMode) { + + // context + String type = "testBulk" + ioMode.name(); //$NON-NLS-1$ + + // create a list of resources + List resources = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + String id = RES_ID + "_" + i; //$NON-NLS-1$ + String name = "Bulk Test Object. " + i; //$NON-NLS-1$ + + Resource resource = createResource(id, name, type); + resources.add(resource); + } + + ObjectDao objectDao; + + // save all + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.addAll(resources); + resources.clear(); + } + + // query all + try (PersistenceTransaction tx = freshTx(ioMode);) { + SubTypeRef subTypeRef = tx.getObjectRefCache().getSubTypeRef(TestConstants.TYPE_RES, type); + objectDao = tx.getObjectDao(); + resources = objectDao.queryAll(subTypeRef); + assertEquals("Expected to find 10 entries!", 10, resources.size()); //$NON-NLS-1$ + + // delete them all + objectDao.removeAll(resources); + } + + // now query them again + try (PersistenceTransaction tx = freshTx(ioMode);) { + SubTypeRef subTypeRef = tx.getObjectRefCache().getSubTypeRef(TestConstants.TYPE_RES, type); + objectDao = tx.getObjectDao(); + resources = objectDao.queryAll(subTypeRef); + assertEquals("Expected to find 0 entries!", 0, resources.size()); //$NON-NLS-1$ + } + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java deleted file mode 100644 index 2143e62b4..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceDomTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.test; - -import java.util.Properties; - -import org.junit.BeforeClass; - -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; - -/** - * @author Robert von Burg - */ -public class XmlPersistenceDomTest extends AbstractXmlPersistenceTest { - - /** - * @throws Exception - * if something goes wrong - */ - @BeforeClass - public static void init() throws Exception { - - Properties props = new Properties(); - props.setProperty(XmlPersistenceConstants.PROP_BASEPATH, "target/testdb"); - props.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); - props.setProperty(XmlPersistenceConstants.PROP_XML_IO_MOD, "dom"); - props.setProperty(XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, TestModelDaoFactory.class.getName()); - - init(props); - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceSaxTest.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceSaxTest.java deleted file mode 100644 index fba8c2eb2..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlPersistenceSaxTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.test; - -import java.util.Properties; - -import org.junit.BeforeClass; - -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.test.impl.TestModelDaoFactory; - -/** - * @author Robert von Burg - */ -public class XmlPersistenceSaxTest extends AbstractXmlPersistenceTest { - - /** - * @throws Exception - * if something goes wrong - */ - @BeforeClass - public static void init() throws Exception { - - Properties props = new Properties(); - props.setProperty(XmlPersistenceConstants.PROP_BASEPATH, "target/testdb"); - props.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); - props.setProperty(XmlPersistenceConstants.PROP_XML_IO_MOD, "sax"); - props.setProperty(XmlPersistenceConstants.PROP_DAO_FACTORY_CLASS, TestModelDaoFactory.class.getName()); - - init(props); - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java index 2575aa3c1..decef2036 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java @@ -60,6 +60,7 @@ import ch.eitchnet.xmlpers.test.model.Resource; * @author Robert von Burg * */ +@SuppressWarnings("nls") public class XmlTestMain { private static final Logger logger = LoggerFactory.getLogger(XmlTestMain.class); @@ -119,10 +120,6 @@ public class XmlTestMain { return resources; } - /** - * @param res2 - * @param paramElements - */ private static void parseParameters(Resource res, NodeList paramElements) { for (int i = 0; i < paramElements.getLength(); i++) { Element paramElement = (Element) paramElements.item(i); @@ -263,43 +260,45 @@ public class XmlTestMain { XMLOutputFactory factory = XMLOutputFactory.newInstance(); File file = new File("target/res_sax.xml"); - XMLStreamWriter writer = factory.createXMLStreamWriter(new FileWriter(file)); + try (FileWriter fileWriter = new FileWriter(file)) { + XMLStreamWriter writer = factory.createXMLStreamWriter(fileWriter); - writer = new IndentingXMLStreamWriter(writer); + writer = new IndentingXMLStreamWriter(writer); - writer.writeStartDocument("utf-8", "1.0"); - writer.writeStartElement("model"); + writer.writeStartDocument("utf-8", "1.0"); + writer.writeStartElement("model"); - writer.writeStartElement("Resource"); - writer.writeAttribute("id", res.getId()); - writer.writeAttribute("name", res.getName()); - writer.writeAttribute("type", res.getType()); + writer.writeStartElement("Resource"); + writer.writeAttribute("id", res.getId()); + writer.writeAttribute("name", res.getName()); + writer.writeAttribute("type", res.getType()); - for (String paramId : res.getParameterKeySet()) { - Parameter param = res.getParameterBy(paramId); - writer.writeEmptyElement("Parameter"); - writer.writeAttribute("id", param.getId()); - writer.writeAttribute("name", param.getName()); - writer.writeAttribute("type", param.getType()); - writer.writeAttribute("value", param.getValue()); + for (String paramId : res.getParameterKeySet()) { + Parameter param = res.getParameterBy(paramId); + writer.writeEmptyElement("Parameter"); + writer.writeAttribute("id", param.getId()); + writer.writeAttribute("name", param.getName()); + writer.writeAttribute("type", param.getType()); + writer.writeAttribute("value", param.getValue()); + } + + //writer.writeEmptyElement("data"); + //writer.writeAttribute("name", "value"); + ////writer.writeEndElement(); + //writer.writeEmptyElement("stuff"); + //writer.writeAttribute("attr", "attrVal"); + + writer.writeEndElement(); + writer.writeEndDocument(); + + writer.flush(); + writer.close(); + logger.info("Wrote SAX to " + file.getAbsolutePath()); + + //Transformer transformer = TransformerFactory.newInstance().newTransformer(); + //Result outputTarget = new StaxR; + //Source xmlSource; + //transformer.transform(xmlSource, outputTarget); } - - //writer.writeEmptyElement("data"); - //writer.writeAttribute("name", "value"); - ////writer.writeEndElement(); - //writer.writeEmptyElement("stuff"); - //writer.writeAttribute("attr", "attrVal"); - - writer.writeEndElement(); - writer.writeEndDocument(); - - writer.flush(); - writer.close(); - logger.info("Wrote SAX to " + file.getAbsolutePath()); - - //Transformer transformer = TransformerFactory.newInstance().newTransformer(); - //Result outputTarget = new StaxR; - //Source xmlSource; - //transformer.transform(xmlSource, outputTarget); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java index 7df3b66b9..533715026 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java @@ -54,7 +54,7 @@ public class Book { */ public Book(Long id) { if (id == null) - throw new IllegalArgumentException("Id may not be null!"); + throw new IllegalArgumentException("Id may not be null!"); //$NON-NLS-1$ this.id = id; } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java deleted file mode 100644 index a6b1f05bd..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDao.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl; - -import ch.eitchnet.xmlpers.api.AbstractXmlDao; - -/** - * @author Robert von Burg - * - */ -public abstract class BookDao extends AbstractXmlDao { - - @Override - protected String getType() { - return Book.class.getSimpleName(); - } - - @Override - protected String getId(Book object) { - return object.getId().toString(); - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java index 36a0f098a..2f5702ce8 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java @@ -21,44 +21,15 @@ */ package ch.eitchnet.xmlpers.test.impl; -import java.io.File; - -import javax.xml.parsers.DocumentBuilder; - import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.xmlpers.api.DomUtil; -import ch.eitchnet.xmlpers.api.XmlPersistenceDomContextData; - /** * @author Robert von Burg * */ -public class BookDomDao extends BookDao { - - @Override - protected Book read(File filePath) { - - XmlPersistenceDomContextData cd = new XmlPersistenceDomContextData(); - cd.setFile(filePath); - getFileHandler().read(cd); - Document document = cd.getDocument(); - Book book = parseFromDom(document.getDocumentElement()); - return book; - } - - @Override - protected void write(Book book, File filePath) { - - XmlPersistenceDomContextData cd = new XmlPersistenceDomContextData(); - cd.setFile(filePath); - DocumentBuilder documentBuilder = DomUtil.createDocumentBuilder(); - Document document = documentBuilder.getDOMImplementation().createDocument(null, null, null); - serializeToDom(book, document); - cd.setDocument(document); - getFileHandler().write(cd); - } +@SuppressWarnings("nls") +public class BookDomDao { public Element serializeToDom(Book book, Document document) { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java similarity index 93% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookDomParser.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java index 35c584904..d363769c4 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java @@ -19,12 +19,11 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.test.impl; import org.w3c.dom.Document; import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.test.impl.Book; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java similarity index 92% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookParserFactory.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java index 01ab00143..26442a97a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookParserFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java @@ -19,12 +19,11 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.test.impl; import ch.eitchnet.xmlpers.api.DomParser; import ch.eitchnet.xmlpers.api.ParserFactory; import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.test.impl.Book; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java index ad151c351..889dc49f4 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java @@ -21,64 +21,30 @@ */ package ch.eitchnet.xmlpers.test.impl; -import java.io.File; - import javax.xml.stream.XMLStreamException; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.xmlpers.api.XmlPersistenceSaxContextData; -import ch.eitchnet.xmlpers.api.XmlPersistenceSaxWriter; -import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; /** * @author Robert von Burg * */ -public class BookSaxDao extends BookDao { +@SuppressWarnings("nls") +public class BookSaxDao { - @Override - protected Book read(File filePath) { + private Book book; - XmlPersistenceSaxContextData cd = new XmlPersistenceSaxContextData(); - cd.setFile(filePath); - BookDefaultHandler bookDefaultHandler = new BookDefaultHandler(); - cd.setDefaultHandler(bookDefaultHandler); - - getFileHandler().read(cd); - - return bookDefaultHandler.getBook(); - } - - @Override - protected void write(Book book, File filePath) { - - XmlPersistenceSaxContextData cd = new XmlPersistenceSaxContextData(); - cd.setFile(filePath); - cd.setXmlWriter(new BookSaxWriter(book)); - - getFileHandler().write(cd); - } - - private class BookSaxWriter implements XmlPersistenceSaxWriter { - - private final Book book; - - public BookSaxWriter(Book book) { - this.book = book; - } - - @Override - public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { - writer.writeEmptyElement("Book"); - writer.writeAttribute("id", Long.toString(this.book.getId())); - writer.writeAttribute("title", this.book.getTitle()); - writer.writeAttribute("author", this.book.getAuthor()); - writer.writeAttribute("press", this.book.getPress()); - writer.writeAttribute("price", Double.toString(this.book.getPrice())); - } + public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { + writer.writeEmptyElement("Book"); + writer.writeAttribute("id", Long.toString(this.book.getId())); + writer.writeAttribute("title", this.book.getTitle()); + writer.writeAttribute("author", this.book.getAuthor()); + writer.writeAttribute("press", this.book.getPress()); + writer.writeAttribute("price", Double.toString(this.book.getPrice())); } private class BookDefaultHandler extends DefaultHandler { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java similarity index 90% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookSaxParser.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java index ab4d012f5..b2cb36186 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/BookSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java @@ -19,15 +19,14 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.test.impl; import javax.xml.stream.XMLStreamException; import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; -import ch.eitchnet.xmlpers.test.impl.Book; +import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java new file mode 100644 index 000000000..977ca09f3 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java @@ -0,0 +1,25 @@ +package ch.eitchnet.xmlpers.test.impl; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import ch.eitchnet.xmlpers.test.model.Resource; + +public class ResourceContextFactory implements PersistenceContextFactory { + + @Override + public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, Resource t) { + IdOfSubTypeRef objectRef = objectRefCache.getIdOfSubTypeRef(TestConstants.TYPE_RES, t.getType(), t.getId()); + return createCtx(objectRef); + } + + @Override + public PersistenceContext createCtx(ObjectRef objectRef) { + PersistenceContext ctx = new PersistenceContext<>(objectRef); + ctx.setParserFactory(new ResourceParserFactory()); + return ctx; + } + +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java deleted file mode 100644 index ce61da6a3..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDao.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl; - -import ch.eitchnet.xmlpers.api.AbstractXmlDao; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - * - */ -public abstract class ResourceDao extends AbstractXmlDao { - - private final String subType; - - public ResourceDao(String subType) { - this.subType = subType; - } - - @Override - public String getType() { - return Resource.class.getSimpleName(); - } - - @Override - public String getSubType() { - return this.subType; - } - - @Override - public String getId(Resource object) { - return object.getId(); - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java index 0267b70eb..d555eb2dd 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java @@ -19,17 +19,11 @@ */ package ch.eitchnet.xmlpers.test.impl; -import java.io.File; - -import javax.xml.parsers.DocumentBuilder; - import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import ch.eitchnet.xmlpers.api.DomUtil; -import ch.eitchnet.xmlpers.api.XmlPersistenceDomContextData; import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; @@ -37,37 +31,8 @@ import ch.eitchnet.xmlpers.test.model.Resource; * @author Robert von Burg * */ -public class ResourceDomDao extends ResourceDao { - - /** - * @param subType - */ - public ResourceDomDao(String subType) { - super(subType); - } - - @Override - protected Resource read(File filePath) { - - XmlPersistenceDomContextData cd = new XmlPersistenceDomContextData(); - cd.setFile(filePath); - getFileHandler().read(cd); - Document document = cd.getDocument(); - Resource resource = parseFromDom(document.getDocumentElement()); - return resource; - } - - @Override - protected void write(Resource resource, File filePath) { - - XmlPersistenceDomContextData cd = new XmlPersistenceDomContextData(); - cd.setFile(filePath); - DocumentBuilder documentBuilder = DomUtil.createDocumentBuilder(); - Document document = documentBuilder.getDOMImplementation().createDocument(null, null, null); - serializeToDom(resource, document); - cd.setDocument(document); - getFileHandler().write(cd); - } +@SuppressWarnings("nls") +public class ResourceDomDao { public Element serializeToDom(Resource resource, Document document) { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java similarity index 95% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java index 869664596..9b86472e8 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.test.impl; import javax.xml.parsers.DocumentBuilder; @@ -29,9 +29,9 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.api.DomUtil; import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.util.DomUtil; public class ResourceDomParser implements DomParser { @@ -47,6 +47,7 @@ public class ResourceDomParser implements DomParser { this.resource = resource; } + @SuppressWarnings("nls") @Override public Document toDom() { @@ -74,6 +75,7 @@ public class ResourceDomParser implements DomParser { return document; } + @SuppressWarnings("nls") @Override public void fromDom(Document document) { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java similarity index 96% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java index 0e062b1ae..002b83ad8 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceParserFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.test.impl; import ch.eitchnet.xmlpers.api.DomParser; import ch.eitchnet.xmlpers.api.ParserFactory; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java index 5db3afe5b..742b9cc63 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java @@ -19,17 +19,13 @@ */ package ch.eitchnet.xmlpers.test.impl; -import java.io.File; - import javax.xml.stream.XMLStreamException; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.xmlpers.api.XmlPersistenceSaxContextData; -import ch.eitchnet.xmlpers.api.XmlPersistenceSaxWriter; -import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; @@ -37,60 +33,23 @@ import ch.eitchnet.xmlpers.test.model.Resource; * @author Robert von Burg * */ -public class ResourceSaxDao extends ResourceDao { +@SuppressWarnings("nls") +public class ResourceSaxDao { - /** - * @param subType - */ - public ResourceSaxDao(String subType) { - super(subType); - } + private Resource resource; - @Override - protected Resource read(File filePath) { - - XmlPersistenceSaxContextData cd = new XmlPersistenceSaxContextData(); - cd.setFile(filePath); - ResourceDefaultHandler bookDefaultHandler = new ResourceDefaultHandler(); - cd.setDefaultHandler(bookDefaultHandler); - - getFileHandler().read(cd); - - return bookDefaultHandler.getResource(); - } - - @Override - protected void write(Resource resource, File filePath) { - - XmlPersistenceSaxContextData cd = new XmlPersistenceSaxContextData(); - cd.setFile(filePath); - cd.setXmlWriter(new ResourceSaxWriter(resource)); - - getFileHandler().write(cd); - } - - private class ResourceSaxWriter implements XmlPersistenceSaxWriter { - - private final Resource resource; - - public ResourceSaxWriter(Resource resource) { - this.resource = resource; - } - - @Override - public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { - writer.writeElement("Resource"); - writer.writeAttribute("id", this.resource.getId()); - writer.writeAttribute("name", this.resource.getName()); - writer.writeAttribute("type", this.resource.getType()); - for (String paramId : this.resource.getParameterKeySet()) { - Parameter param = this.resource.getParameterBy(paramId); - writer.writeElement("Parameter"); - writer.writeAttribute("id", param.getId()); - writer.writeAttribute("name", param.getName()); - writer.writeAttribute("type", param.getType()); - writer.writeAttribute("value", param.getValue()); - } + public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { + writer.writeElement("Resource"); + writer.writeAttribute("id", this.resource.getId()); + writer.writeAttribute("name", this.resource.getName()); + writer.writeAttribute("type", this.resource.getType()); + for (String paramId : this.resource.getParameterKeySet()) { + Parameter param = this.resource.getParameterBy(paramId); + writer.writeElement("Parameter"); + writer.writeAttribute("id", param.getId()); + writer.writeAttribute("name", param.getName()); + writer.writeAttribute("type", param.getType()); + writer.writeAttribute("value", param.getValue()); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java similarity index 94% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java index 1bc512827..f3cdf82d4 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ResourceSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl.rewrite; +package ch.eitchnet.xmlpers.test.impl; import javax.xml.stream.XMLStreamException; @@ -28,7 +28,7 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.impl.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; import ch.eitchnet.xmlpers.test.model.Parameter; import ch.eitchnet.xmlpers.test.model.Resource; @@ -51,6 +51,7 @@ class ResourceSaxParser extends DefaultHandler implements SaxParser { return this; } + @SuppressWarnings("nls") @Override public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { @@ -68,6 +69,7 @@ class ResourceSaxParser extends DefaultHandler implements SaxParser { } } + @SuppressWarnings("nls") @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java deleted file mode 100644 index 43474a8d4..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestModelDaoFactory.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.test.impl; - -import java.text.MessageFormat; - -import ch.eitchnet.xmlpers.api.AbstractDaoFactory; -import ch.eitchnet.xmlpers.api.XmlIoMode; -import ch.eitchnet.xmlpers.api.XmlPersistenceDao; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - * - */ -public class TestModelDaoFactory extends AbstractDaoFactory { - - @Override - public XmlPersistenceDao getDao(T object) { - - XmlPersistenceDao dao; - if (object instanceof Resource) { - dao = getDaoBy(object.getClass().getSimpleName(), ((Resource) object).getType()); - } else if (object instanceof Book) { - dao = getDaoBy(Book.class.getSimpleName()); - } else { - String msg = "The object with class {0} is not handled!"; - msg = MessageFormat.format(msg, object.getClass()); - throw new IllegalArgumentException(msg); - } - - return dao; - } - - @SuppressWarnings("unchecked") - @Override - public XmlPersistenceDao getDaoBy(String type) { - - XmlPersistenceDao dao; - if (TestConstants.TYPE_BOOK.equals(type)) { - XmlIoMode ioMode = getXmlIoMode(); - if (ioMode == XmlIoMode.DOM) { - dao = (XmlPersistenceDao) new BookDomDao(); - } else if (ioMode == XmlIoMode.SAX) { - dao = (XmlPersistenceDao) new BookSaxDao(); - } else { - throw new IllegalArgumentException("The XmlIoMode " + ioMode + " is not yet supported!"); - } - } else { - String msg = "The object with type {0} is not handled!"; - msg = MessageFormat.format(msg, type); - throw new IllegalArgumentException(msg); - } - - return initializeDao(dao); - } - - @SuppressWarnings("unchecked") - @Override - public XmlPersistenceDao getDaoBy(String type, String subType) { - - XmlPersistenceDao dao; - if (TestConstants.TYPE_RES.equals(type)) { - XmlIoMode ioMode = getXmlIoMode(); - if (ioMode == XmlIoMode.DOM) { - dao = (XmlPersistenceDao) new ResourceDomDao(subType); - } else if (ioMode == XmlIoMode.SAX) { - dao = (XmlPersistenceDao) new ResourceSaxDao(subType); - } else { - throw new IllegalArgumentException("The XmlIoMode " + ioMode + " is not yet supported!"); - } - } else { - String msg = "The object with type {0} and subType {1} is not handled!"; - msg = MessageFormat.format(msg, type, subType); - throw new IllegalArgumentException(msg); - } - - return initializeDao(dao); - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/AssertionUtil.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/AssertionUtil.java deleted file mode 100644 index d83f5edeb..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/AssertionUtil.java +++ /dev/null @@ -1,33 +0,0 @@ -package ch.eitchnet.xmlpers.test.impl.rewrite; - -import java.text.MessageFormat; - -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.xmlpers.api.PersistenceContext; - -public class AssertionUtil { - - public static void assertNotNull(Object object) { - if (object == null) - throw new RuntimeException("Object may not be null!"); //$NON-NLS-1$ - } - - public static void assertObjectRead(PersistenceContext context) { - if (context.getObject() == null) { - String msg = "Failed to read object with for {0} / {1} / {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, context.getType(), context.getSubType(), context.getId()); - throw new RuntimeException(msg); - } - } - - public static void assertHasType(PersistenceContext context) { - if (StringHelper.isEmpty(context.getType())) - throw new RuntimeException("Type is always needed on a persistence context!"); //$NON-NLS-1$ - } - - public static void assertHasId(PersistenceContext context) { - if (StringHelper.isEmpty(context.getId())) - throw new RuntimeException("Id is not set on persistence context!"); //$NON-NLS-1$ - } - -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java deleted file mode 100644 index 30ee6cac0..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/DefaultXmlPersistenceManager.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl.rewrite; - -import java.util.Properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.PropertiesHelper; -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.impl.XmlPersistenceHandlerImpl; -import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; - -/** - * @author Robert von Burg - * - */ -public class DefaultXmlPersistenceManager implements XmlPersistenceManager { - - protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandlerImpl.class); - - protected boolean initialized; - protected boolean verbose; - - private XmlPersistencePathBuilder pathBuilder; - - public void initialize(Properties properties) { - if (this.initialized) - throw new IllegalStateException("Already initialized!"); //$NON-NLS-1$ - - // get properties - String context = XmlPersistenceHandlerImpl.class.getSimpleName(); - boolean verbose = PropertiesHelper.getPropertyBool(properties, context, XmlPersistenceConstants.PROP_VERBOSE, - Boolean.FALSE).booleanValue(); - - this.verbose = verbose; - - this.pathBuilder = new XmlPersistencePathBuilder(properties); - } - - @Override - public PersistenceTransaction openTx() { - - FileDao fileDao = new FileDao(this.pathBuilder, this.verbose); - PersistenceTransaction tx = new DefaultPersistenceTransaction(fileDao, this.verbose); - - return tx; - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java deleted file mode 100644 index 1e8898bfa..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDao.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl.rewrite; - -import java.io.File; -import java.text.MessageFormat; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; - -public class FileDao { - - private static final Logger logger = LoggerFactory.getLogger(FileDao.class); - - private final boolean verbose; - private final XmlPersistencePathBuilder pathBuilder; - - public FileDao(XmlPersistencePathBuilder pathBuilder, boolean verbose) { - this.pathBuilder = pathBuilder; - this.verbose = verbose; - } - - File getPath(PersistenceContext context) { - return this.pathBuilder.getPath(context); - } - - void performCreate(PersistenceContext context) { - File path = this.pathBuilder.getCreatePath(context); - FileIo fileIo = new FileIo(path); - fileIo.write(context); - } - - void performRead(PersistenceContext context) { - File path = this.pathBuilder.getReadPath(context); - if (path == null) { - context.setObject(null); - } else { - FileIo fileIo = new FileIo(path); - fileIo.read(context); - } - } - - void performUpdate(PersistenceContext context) { - File path = this.pathBuilder.getUpdatePath(context); - FileIo fileIo = new FileIo(path); - fileIo.write(context); - } - - void performDelete(PersistenceContext context) { - File path = this.pathBuilder.getDeletePath(context); - if (!path.delete()) { - String msg = "Failed to delete file {0}"; //$NON-NLS-1$ - throw new RuntimeException(MessageFormat.format(msg, path.getAbsolutePath())); - } - - deleteEmptyDirectory(path.getParentFile(), context); - } - - private void deleteEmptyDirectory(File directoryPath, PersistenceContext ctx) { - if (!directoryPath.isDirectory()) { - String msg = "The given path for deletion when empty is not a directory:{0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, directoryPath.getAbsolutePath()); - throw new IllegalArgumentException(msg); - } - if (directoryPath.list().length == 0) { - if (!ctx.hasSubType()) { - if (!directoryPath.delete()) { - throw failedToDelete(directoryPath, ctx); - } - if (this.verbose) { - String msg = "Deleted empty directory for type {0} at {1}"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, ctx.getType(), directoryPath)); - } - } else { - - if (!directoryPath.delete()) { - throw failedToDelete(directoryPath, ctx); - } - if (this.verbose) { - String msg = "Deleted empty directory for subType {0} of type {1} at {2}"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, ctx.getSubType(), ctx.getType(), directoryPath)); - } - - File typePath = directoryPath.getParentFile(); - if (typePath.list().length == 0) { - - if (!typePath.delete()) { - throw failedToDelete(typePath, ctx); - } - if (this.verbose) { - String msg = "Deleted empty directory for type {0} at {1}"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, ctx.getType(), typePath)); - } - } - } - } - } - - private XmlPersistenceException failedToDelete(File directoryPath, PersistenceContext ctx) { - String msg; - if (ctx.hasSubType()) { - msg = "Deletion of empty directory for {0} / {1} / {2} at {3} failed! Check file permissions!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, ctx.getType(), ctx.getSubType(), ctx.getId(), - directoryPath.getAbsolutePath()); - } else { - - msg = "Deletion of empty directory for {0} / {1} / at {2} failed! Check file permissions!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, ctx.getType(), ctx.getId(), directoryPath.getAbsolutePath()); - } - return new XmlPersistenceException(msg); - } -} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java deleted file mode 100644 index b5d980543..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/FileDaoTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl.rewrite; - -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; -import static org.junit.Assert.assertNull; - -import java.io.File; -import java.util.Properties; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.XmlIoMode; -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.impl.XmlPersistencePathBuilder; -import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - * - */ -@SuppressWarnings("nls") -public class FileDaoTest { - - private static final String TEST_PATH = "target/dbTest"; - private static final boolean VERBOSE = true; - - private FileDao fileDao; - private Properties properties; - - @BeforeClass - public static void beforeClass() { - File file = new File(TEST_PATH).getAbsoluteFile(); - if (file.exists() && file.isDirectory()) - if (!FileHelper.deleteFiles(file.listFiles(), true)) - throw new RuntimeException("Could not clean up path " + file.getAbsolutePath()); - - if (!file.exists() && !file.mkdir()) - throw new RuntimeException("Failed to create path " + file); - - File domFile = new File(file, "dom"); - if (!domFile.mkdir()) - throw new RuntimeException("Failed to create path " + domFile); - - File saxFile = new File(file, "sax"); - if (!saxFile.mkdir()) - throw new RuntimeException("Failed to create path " + saxFile); - } - - @Before - public void setUp() { - this.properties = new Properties(); - this.properties.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); - } - - @Test - public void testCrudSax() { - this.properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, TEST_PATH + "/sax/"); - XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(this.properties); - this.fileDao = new FileDao(pathBuilder, VERBOSE); - - Resource resource = createResource(); - assertResource(resource); - XmlIoMode ioMode = XmlIoMode.SAX; - PersistenceContext context = createPersistenceContext(resource, ioMode); - testCrud(context); - } - - @Test - public void testCrudDom() { - this.properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, TEST_PATH + "/dom/"); - XmlPersistencePathBuilder pathBuilder = new XmlPersistencePathBuilder(this.properties); - this.fileDao = new FileDao(pathBuilder, VERBOSE); - - Resource resource = createResource(); - assertResource(resource); - XmlIoMode ioMode = XmlIoMode.DOM; - PersistenceContext context = createPersistenceContext(resource, ioMode); - testCrud(context); - } - - private void testCrud(PersistenceContext context) { - - this.fileDao.performCreate(context); - - context.setObject(null); - this.fileDao.performRead(context); - assertResource(context.getObject()); - - updateResource(context.getObject()); - this.fileDao.performUpdate(context); - - context.setObject(null); - this.fileDao.performRead(context); - assertResourceUpdated(context.getObject()); - - this.fileDao.performDelete(context); - - context.setObject(null); - this.fileDao.performRead(context); - assertNull(context.getObject()); - - context.setObject(createResource()); - this.fileDao.performCreate(context); - } - - private PersistenceContext createPersistenceContext(Resource resource, XmlIoMode ioMode) { - PersistenceContext context = new PersistenceContext(); - context.setId(resource.getId()); - context.setType(TestConstants.TYPE_RES); - context.setSubType(resource.getType()); - context.setObject(resource); - context.setIoMode(ioMode); - context.setParserFactory(new ResourceParserFactory()); - - return context; - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java deleted file mode 100644 index d5e6e9c59..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/ObjectDaoTest.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl.rewrite; - -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -import org.junit.After; -import org.junit.BeforeClass; -import org.junit.Test; - -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.XmlIoMode; -import ch.eitchnet.xmlpers.api.XmlPersistenceConstants; -import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - * - */ -public class ObjectDaoTest { - - private static final String BASEPATH = "target/dbTest/rewrite"; //$NON-NLS-1$ - - private PersistenceContextFactory ctxFactory; - private XmlPersistenceManager persistenceManager; - private PersistenceTransaction tx; - - @BeforeClass - public static void beforeClass() { - - File basePath = new File(BASEPATH); - if (basePath.exists()) { - if (!FileHelper.deleteFile(basePath, true)) { - throw new RuntimeException("Faile to delete base path " + BASEPATH); //$NON-NLS-1$ - } - } - - if (!basePath.mkdirs()) { - throw new RuntimeException("Failed to create base path " + BASEPATH); //$NON-NLS-1$ - } - - new File(BASEPATH + "/sax").mkdir(); //$NON-NLS-1$ - new File(BASEPATH + "/dom").mkdir(); //$NON-NLS-1$ - } - - @After - public void tearDown() { - if (this.tx != null && this.tx.isOpen()) { - this.tx.rollback(); - } - } - - @Test - public void testCrudSax() { - this.ctxFactory = new TestPersistenceContextFactory(); - Properties properties = new Properties(); - properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/sax"); //$NON-NLS-1$ - this.persistenceManager = XmlPersistenceManagerLoader.load(properties); - - testCrud(XmlIoMode.SAX); - } - - @Test - public void testCrudDom() { - this.ctxFactory = new TestPersistenceContextFactory(); - Properties properties = new Properties(); - properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/dom"); //$NON-NLS-1$ - this.persistenceManager = XmlPersistenceManagerLoader.load(properties); - - testCrud(XmlIoMode.DOM); - } - - private PersistenceTransaction freshTx(XmlIoMode ioMode) { - if (this.tx != null && this.tx.isOpen()) - this.tx.rollback(); - - this.tx = this.persistenceManager.openTx(); - this.tx.setIoMode(ioMode); - - return this.tx; - } - - private void testCrud(XmlIoMode ioMode) { - - ObjectDao objectDao; - - // create new resource - Resource resource = createResource(); - objectDao = freshTx(ioMode).getObjectDao(); - objectDao.add(resource); - this.tx.commit(this.ctxFactory); - - // read resource - PersistenceContext ctx = this.ctxFactory.createCtx(this.tx, TestConstants.TYPE_RES, RES_TYPE, RES_ID); - objectDao = freshTx(ioMode).getObjectDao(); - resource = objectDao.queryById(ctx); - assertResource(resource); - - // modify resource - updateResource(resource); - objectDao = freshTx(ioMode).getObjectDao(); - objectDao.update(resource); - this.tx.commit(this.ctxFactory); - - // read modified resource - objectDao = freshTx(ioMode).getObjectDao(); - resource = objectDao.queryById(ctx); - assertResourceUpdated(resource); - this.tx.commit(this.ctxFactory); - - // delete resource - objectDao = freshTx(ioMode).getObjectDao(); - objectDao.remove(resource); - this.tx.commit(this.ctxFactory); - - // fail to read - objectDao = freshTx(ioMode).getObjectDao(); - resource = objectDao.queryById(ctx); - assertNull(resource); - - // and create again - resource = createResource(); - assertResource(resource); - objectDao = freshTx(ioMode).getObjectDao(); - objectDao.add(resource); - this.tx.commit(this.ctxFactory); - } - - @Test - public void testBulkSax() { - - this.ctxFactory = new TestPersistenceContextFactory(); - Properties properties = new Properties(); - properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/sax"); //$NON-NLS-1$ - properties.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ - this.persistenceManager = XmlPersistenceManagerLoader.load(properties); - - XmlIoMode ioMode = XmlIoMode.SAX; - testBulk(ioMode); - } - - @Test - public void testBulkDom() { - - this.ctxFactory = new TestPersistenceContextFactory(); - Properties properties = new Properties(); - properties.setProperty(XmlPersistenceConstants.PROP_BASEPATH, BASEPATH + "/sax"); //$NON-NLS-1$ - properties.setProperty(XmlPersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ - this.persistenceManager = XmlPersistenceManagerLoader.load(properties); - - XmlIoMode ioMode = XmlIoMode.DOM; - testBulk(ioMode); - } - - private void testBulk(XmlIoMode ioMode) { - - // context - String type = "testBulk" + ioMode.name(); //$NON-NLS-1$ - - // create a list of resources - List resources = new ArrayList<>(10); - for (int i = 0; i < 10; i++) { - String id = RES_ID + "_" + i; //$NON-NLS-1$ - String name = "Bulk Test Object. " + i; //$NON-NLS-1$ - - Resource resource = createResource(id, name, type); - resources.add(resource); - } - - ObjectDao objectDao; - - // save all - objectDao = freshTx(ioMode).getObjectDao(); - objectDao.addAll(resources); - resources.clear(); - this.tx.commit(this.ctxFactory); - - // query all - objectDao = freshTx(ioMode).getObjectDao(); - PersistenceContext ctx = this.ctxFactory.createCtx(this.tx, TestConstants.TYPE_RES, type); - resources = objectDao.queryAll(ctx); - assertEquals("Expected to find 10 entries!", 10, resources.size()); //$NON-NLS-1$ - - // delete them all - objectDao.removeAll(resources); - this.tx.commit(this.ctxFactory); - - // now query them again - objectDao = freshTx(ioMode).getObjectDao(); - ctx = this.ctxFactory.createCtx(this.tx, TestConstants.TYPE_RES, type); - resources = objectDao.queryAll(ctx); - assertEquals("Expected to find 0 entries!", 0, resources.size()); //$NON-NLS-1$ - this.tx.commit(this.ctxFactory); - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java deleted file mode 100644 index 5f1a20900..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/PersistenceContextFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl.rewrite; - -import ch.eitchnet.xmlpers.api.PersistenceContext; - -/** - * @author Robert von Burg - * - */ -public interface PersistenceContextFactory { - - public PersistenceContext createCtx(PersistenceTransaction tx, String type, String subType, String id); - - public PersistenceContext createCtx(PersistenceTransaction tx, String type, String subType); - - public PersistenceContext createCtx(PersistenceTransaction tx, String type); - - public PersistenceContext createCtx(PersistenceTransaction tx, T t); -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java deleted file mode 100644 index bdb02f306..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/rewrite/TestPersistenceContextFactory.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl.rewrite; - -import java.text.MessageFormat; - -import ch.eitchnet.xmlpers.api.ParserFactory; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.test.impl.Book; -import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - * - */ -public class TestPersistenceContextFactory implements PersistenceContextFactory { - - @Override - public PersistenceContext createCtx(PersistenceTransaction tx, String type, String subType, String id) { - return buildPersistenceContext(tx, type, subType, id); - } - - @Override - public PersistenceContext createCtx(PersistenceTransaction tx, String type, String subType) { - return buildPersistenceContext(tx, type, subType, null); - } - - @Override - public PersistenceContext createCtx(PersistenceTransaction tx, String type) { - return buildPersistenceContext(tx, type, null, null); - } - - @Override - public PersistenceContext createCtx(PersistenceTransaction tx, T t) { - if (t == null) - throw new RuntimeException("T may not be null!"); //$NON-NLS-1$ - - PersistenceContext context; - - if (t instanceof Resource) { - Resource resource = (Resource) t; - context = buildPersistenceContext(tx, TestConstants.TYPE_RES, resource.getType(), resource.getId()); - } else if (t instanceof Book) { - context = buildPersistenceContext(tx, TestConstants.TYPE_BOOK, null, ((Book) t).getId().toString()); - } else { - String msg = "Handling of {0} is not implemented!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, t.getClass().getName()); - throw new UnsupportedOperationException(msg); - } - - context.setObject(t); - return context; - } - - private PersistenceContext buildPersistenceContext(PersistenceTransaction tx, String type, String subType, - String id) { - - PersistenceContext context = new PersistenceContext<>(); - - context.setType(type); - context.setSubType(subType); - context.setId(id); - - context.setIoMode(tx.getIoMode()); - context.setParserFactory(this. getParserFactoryInstance(type)); - - return context; - } - - /** - * @param type - * @return - */ - @SuppressWarnings("unchecked") - private ParserFactory getParserFactoryInstance(String type) { - - ParserFactory parserFactory; - if (type.equals(TestConstants.TYPE_RES)) - parserFactory = (ParserFactory) new ResourceParserFactory(); - else if (type.equals(TestConstants.TYPE_BOOK)) - parserFactory = (ParserFactory) new BookParserFactory(); - else { - String msg = "No ParserFactory can be returned for type {0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, type); - throw new UnsupportedOperationException(msg); - } - - return parserFactory; - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java index f44cc6590..08dc5d582 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -29,6 +29,7 @@ import ch.eitchnet.xmlpers.test.impl.Book; * @author Robert von Burg * */ +@SuppressWarnings("nls") public class ModelBuilder { public static final String RES_TYPE = "@subType"; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java index 99fa52403..1f6ee3b25 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java @@ -49,6 +49,7 @@ public class Parameter { this.value = value; } + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java index 283b758d5..b647b9d7c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java @@ -52,6 +52,7 @@ public class Resource { this.type = type; } + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); From 0d106c071495ea08ec8267e7efa24a6524b2796a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 16 Oct 2013 07:59:03 +0200 Subject: [PATCH 185/457] [Minor] refactored tests to have a base class for cleaning up and preparing the db store --- .../xmlpers/test/AbstractPersistenceTest.java | 55 +++++++++++++++++++ .../ch/eitchnet/xmlpers/test/FileDaoTest.java | 45 ++++----------- .../eitchnet/xmlpers/test/ObjectDaoTest.java | 49 ++++------------- .../ch/eitchnet/xmlpers/test/RealmTest.java | 11 ++++ .../xmlpers/test/impl/BookContextFactory.java | 24 ++++++++ .../xmlpers/test/impl/BookDomDao.java | 2 + .../xmlpers/test/impl/BookDomParser.java | 1 + .../xmlpers/test/impl/BookParserFactory.java | 2 +- .../xmlpers/test/impl/BookSaxDao.java | 1 + .../xmlpers/test/impl/BookSaxParser.java | 1 + .../xmlpers/test/impl/TestConstants.java | 1 + .../xmlpers/test/{impl => model}/Book.java | 2 +- .../xmlpers/test/model/ModelBuilder.java | 2 - 13 files changed, 119 insertions(+), 77 deletions(-) create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java rename src/test/java/ch/eitchnet/xmlpers/test/{impl => model}/Book.java (98%) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java new file mode 100644 index 000000000..5a47834d8 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java @@ -0,0 +1,55 @@ +package ch.eitchnet.xmlpers.test; + +import java.io.File; +import java.util.Properties; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceManager; +import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; +import ch.eitchnet.xmlpers.test.impl.BookContextFactory; +import ch.eitchnet.xmlpers.test.impl.ResourceContextFactory; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Book; +import ch.eitchnet.xmlpers.test.model.Resource; + +public abstract class AbstractPersistenceTest { + + protected PersistenceManager persistenceManager; + + @SuppressWarnings("nls") + protected static void cleanPath(String path) { + + File file = new File(path).getAbsoluteFile(); + File parent = file.getParentFile(); + if (!parent.getAbsolutePath().endsWith("/target/db")) + throw new RuntimeException("Bad parent! Must be /target/db/: " + parent); + if (!parent.exists() && !parent.mkdirs()) + throw new RuntimeException("Failed to create path " + parent); + + if (file.exists() && file.isDirectory()) + if (!FileHelper.deleteFiles(file.listFiles(), true)) + throw new RuntimeException("Could not clean up path " + file.getAbsolutePath()); + + if (!file.exists() && !file.mkdir()) + throw new RuntimeException("Failed to create path " + file); + + File domFile = new File(file, IoMode.DOM.name()); + if (!domFile.mkdir()) + throw new RuntimeException("Failed to create path " + domFile); + + File saxFile = new File(file, IoMode.SAX.name()); + if (!saxFile.mkdir()) + throw new RuntimeException("Failed to create path " + saxFile); + } + + protected void setup(Properties properties) { + properties.setProperty(PersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ + this.persistenceManager = PersistenceManagerLoader.load(properties); + this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Resource.class, + TestConstants.TYPE_RES, new ResourceContextFactory()); + this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Book.class, TestConstants.TYPE_BOOK, + new BookContextFactory()); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java index adbb13677..2ba28bcd7 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java @@ -27,13 +27,11 @@ import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; import static org.junit.Assert.assertNull; -import java.io.File; import java.util.Properties; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.xmlpers.api.FileDao; import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.PersistenceConstants; @@ -41,14 +39,11 @@ import ch.eitchnet.xmlpers.api.PersistenceContext; import ch.eitchnet.xmlpers.api.PersistenceContextFactory; import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; import ch.eitchnet.xmlpers.api.PersistenceManager; -import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.impl.DefaultPersistenceRealm; import ch.eitchnet.xmlpers.impl.DefaultPersistenceTransaction; import ch.eitchnet.xmlpers.impl.PathBuilder; import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; -import ch.eitchnet.xmlpers.test.impl.ResourceContextFactory; -import ch.eitchnet.xmlpers.test.impl.TestConstants; import ch.eitchnet.xmlpers.test.model.Resource; /** @@ -56,52 +51,32 @@ import ch.eitchnet.xmlpers.test.model.Resource; * */ @SuppressWarnings("nls") -public class FileDaoTest { +public class FileDaoTest extends AbstractPersistenceTest { - private static final String TEST_PATH = "target/dbTest"; + private static final String TEST_PATH = "target/db/FileDaoTest/"; private static final boolean VERBOSE = true; - private static Properties properties; - private PersistenceManager persistenceManager; private DefaultPersistenceRealm realm; private PathBuilder pathBuilder; @BeforeClass public static void beforeClass() { - File file = new File(TEST_PATH).getAbsoluteFile(); - if (file.exists() && file.isDirectory()) - if (!FileHelper.deleteFiles(file.listFiles(), true)) - throw new RuntimeException("Could not clean up path " + file.getAbsolutePath()); - - if (!file.exists() && !file.mkdir()) - throw new RuntimeException("Failed to create path " + file); - - File domFile = new File(file, "dom"); - if (!domFile.mkdir()) - throw new RuntimeException("Failed to create path " + domFile); - - File saxFile = new File(file, "sax"); - if (!saxFile.mkdir()) - throw new RuntimeException("Failed to create path " + saxFile); - - properties = new Properties(); - properties.setProperty(PersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ + cleanPath(TEST_PATH); } - private void setup(String subPath) { - properties.setProperty(PersistenceConstants.PROP_BASEPATH, TEST_PATH + subPath); - this.persistenceManager = PersistenceManagerLoader.load(properties); - this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Resource.class, - TestConstants.TYPE_RES, new ResourceContextFactory()); + private void setup(IoMode ioMode) { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, TEST_PATH + ioMode.name()); + setup(properties); ObjectReferenceCache objectRefCache = new ObjectReferenceCache(PersistenceManager.DEFAULT_REALM); - this.pathBuilder = new PathBuilder(PersistenceManager.DEFAULT_REALM, FileDaoTest.properties); + this.pathBuilder = new PathBuilder(PersistenceManager.DEFAULT_REALM, properties); this.realm = new DefaultPersistenceRealm(PersistenceManager.DEFAULT_REALM, this.persistenceManager, this.persistenceManager.getCtxFactory(), this.pathBuilder, objectRefCache); } @Test public void testCrudSax() { - setup("/sax/"); + setup(IoMode.SAX); try (PersistenceTransaction tx = new DefaultPersistenceTransaction(this.realm, VERBOSE)) { FileDao fileDao = new FileDao(tx, this.pathBuilder, VERBOSE); tx.setIoMode(IoMode.SAX); @@ -111,7 +86,7 @@ public class FileDaoTest { @Test public void testCrudDom() { - setup("/dom/"); + setup(IoMode.DOM); try (PersistenceTransaction tx = new DefaultPersistenceTransaction(this.realm, VERBOSE)) { FileDao fileDao = new FileDao(tx, this.pathBuilder, VERBOSE); tx.setIoMode(IoMode.DOM); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java index 36503a683..c13712a8a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java @@ -30,7 +30,6 @@ import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -38,16 +37,12 @@ import java.util.Properties; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.ObjectDao; import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceManager; -import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; import ch.eitchnet.xmlpers.objref.SubTypeRef; -import ch.eitchnet.xmlpers.test.impl.ResourceContextFactory; import ch.eitchnet.xmlpers.test.impl.TestConstants; import ch.eitchnet.xmlpers.test.model.Resource; @@ -55,52 +50,30 @@ import ch.eitchnet.xmlpers.test.model.Resource; * @author Robert von Burg * */ -public class ObjectDaoTest { +public class ObjectDaoTest extends AbstractPersistenceTest { - private static final String BASEPATH = "target/dbTest/rewrite"; //$NON-NLS-1$ - - private PersistenceManager persistenceManager; - - private static Properties properties; + private static final String BASEPATH = "target/db/ObjectDaoTest/"; //$NON-NLS-1$ @BeforeClass public static void beforeClass() { - - File basePath = new File(BASEPATH); - if (basePath.exists()) { - if (!FileHelper.deleteFile(basePath, true)) { - throw new RuntimeException("Faile to delete base path " + BASEPATH); //$NON-NLS-1$ - } - } - - if (!basePath.mkdirs()) { - throw new RuntimeException("Failed to create base path " + BASEPATH); //$NON-NLS-1$ - } - - new File(BASEPATH + "/sax").mkdir(); //$NON-NLS-1$ - new File(BASEPATH + "/dom").mkdir(); //$NON-NLS-1$ - - properties = new Properties(); - properties.setProperty(PersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ + cleanPath(BASEPATH); } - private void setup(String subPath) { - properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASEPATH + subPath); - this.persistenceManager = PersistenceManagerLoader.load(properties); - - this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Resource.class, - TestConstants.TYPE_RES, new ResourceContextFactory()); + private void setup(IoMode ioMode) { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASEPATH + ioMode.name()); + setup(properties); } @Test public void testCrudSax() { - setup("/sax"); //$NON-NLS-1$ + setup(IoMode.SAX); testCrud(IoMode.SAX); } @Test public void testCrudDom() { - setup("/dom"); //$NON-NLS-1$ + setup(IoMode.DOM); testCrud(IoMode.DOM); } @@ -163,14 +136,14 @@ public class ObjectDaoTest { @Test public void testBulkSax() { - setup("/sax"); //$NON-NLS-1$ + setup(IoMode.SAX); IoMode ioMode = IoMode.SAX; testBulk(ioMode); } @Test public void testBulkDom() { - setup("/dom"); //$NON-NLS-1$ + setup(IoMode.DOM); IoMode ioMode = IoMode.DOM; testBulk(ioMode); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java b/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java new file mode 100644 index 000000000..755a09c56 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java @@ -0,0 +1,11 @@ +package ch.eitchnet.xmlpers.test; + +@SuppressWarnings("nls") +public class RealmTest extends AbstractPersistenceTest { + + protected static final String BASE_PATH = "target/db/RealmTest"; + + public static void beforeClass() { + cleanPath(BASE_PATH); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java new file mode 100644 index 000000000..c092b735d --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java @@ -0,0 +1,24 @@ +package ch.eitchnet.xmlpers.test.impl; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.objref.IdOfTypeRef; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import ch.eitchnet.xmlpers.test.model.Book; + +public class BookContextFactory implements PersistenceContextFactory { + + @Override + public PersistenceContext createCtx(ObjectRef objectRef) { + PersistenceContext ctx = new PersistenceContext<>(objectRef); + ctx.setParserFactory(new BookParserFactory()); + return ctx; + } + + @Override + public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, Book t) { + IdOfTypeRef objectRef = objectRefCache.getIdOfTypeRef(TestConstants.TYPE_BOOK, t.getId().toString()); + return createCtx(objectRef); + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java index 2f5702ce8..d5756af25 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java @@ -24,6 +24,8 @@ package ch.eitchnet.xmlpers.test.impl; import org.w3c.dom.Document; import org.w3c.dom.Element; +import ch.eitchnet.xmlpers.test.model.Book; + /** * @author Robert von Burg * diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java index d363769c4..f98f57576 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java @@ -24,6 +24,7 @@ package ch.eitchnet.xmlpers.test.impl; import org.w3c.dom.Document; import ch.eitchnet.xmlpers.api.DomParser; +import ch.eitchnet.xmlpers.test.model.Book; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java index 26442a97a..8ca161052 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java @@ -24,6 +24,7 @@ package ch.eitchnet.xmlpers.test.impl; import ch.eitchnet.xmlpers.api.DomParser; import ch.eitchnet.xmlpers.api.ParserFactory; import ch.eitchnet.xmlpers.api.SaxParser; +import ch.eitchnet.xmlpers.test.model.Book; /** * @author Robert von Burg @@ -40,5 +41,4 @@ public class BookParserFactory implements ParserFactory { public SaxParser getSaxParser() { return new BookSaxParser(); } - } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java index 889dc49f4..3ef898df8 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java @@ -28,6 +28,7 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.test.model.Book; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java index b2cb36186..950dc0896 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java @@ -27,6 +27,7 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.SaxParser; import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; +import ch.eitchnet.xmlpers.test.model.Book; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java index 5ec56aaa5..1ac98b08c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java @@ -21,6 +21,7 @@ */ package ch.eitchnet.xmlpers.test.impl; +import ch.eitchnet.xmlpers.test.model.Book; import ch.eitchnet.xmlpers.test.model.Resource; /** diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java similarity index 98% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java rename to src/test/java/ch/eitchnet/xmlpers/test/model/Book.java index 533715026..21318298c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/Book.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.xmlpers.test.impl; +package ch.eitchnet.xmlpers.test.model; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java index 08dc5d582..7aa72e6c0 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -23,8 +23,6 @@ package ch.eitchnet.xmlpers.test.model; import org.junit.Assert; -import ch.eitchnet.xmlpers.test.impl.Book; - /** * @author Robert von Burg * From a0d5904db9768f739d01513e8d778c9ecabbe860 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 16 Oct 2013 18:53:32 +0200 Subject: [PATCH 186/457] [Minor] added default IoMode, so that it must no be set on each TX --- .../xmlpers/impl/DefaultXmlPersistenceManager.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java index cab1ddf90..76c24c5bd 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java @@ -31,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.PersistenceConstants; import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; import ch.eitchnet.xmlpers.api.PersistenceManager; @@ -48,6 +49,7 @@ public class DefaultXmlPersistenceManager implements PersistenceManager { protected boolean initialized; protected boolean verbose; + protected IoMode defaultIoMode; protected Properties properties; protected Map realmMap; private PersistenceContextFactoryDelegator ctxFactory; @@ -61,12 +63,16 @@ public class DefaultXmlPersistenceManager implements PersistenceManager { // get verbose flag boolean verbose = PropertiesHelper.getPropertyBool(properties, context, PersistenceConstants.PROP_VERBOSE, Boolean.FALSE).booleanValue(); + String ioModeS = PropertiesHelper.getProperty(properties, context, PersistenceConstants.PROP_XML_IO_MOD, + IoMode.DOM.name()); + IoMode ioMode = IoMode.valueOf(ioModeS); // validate base path validateBasePath(properties); this.properties = properties; this.verbose = verbose; + this.defaultIoMode = ioMode; this.realmMap = new HashMap<>(); this.ctxFactory = new PersistenceContextFactoryDelegator(); } @@ -103,12 +109,14 @@ public class DefaultXmlPersistenceManager implements PersistenceManager { PathBuilder pathBuilder = new PathBuilder(realmName, this.properties); ObjectReferenceCache objectRefCache = new ObjectReferenceCache(realmName); - persistenceRealm = new DefaultPersistenceRealm(realmName, this, this.ctxFactory, pathBuilder, objectRefCache); + persistenceRealm = new DefaultPersistenceRealm(realmName, this, this.ctxFactory, pathBuilder, + objectRefCache); this.realmMap.put(realmName, persistenceRealm); } PersistenceTransaction tx = new DefaultPersistenceTransaction(persistenceRealm, this.verbose); + tx.setIoMode(this.defaultIoMode); return tx; } } From bce78769c04d8547f239234d91fdb7792c090653 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 19 Oct 2013 17:43:36 +0200 Subject: [PATCH 187/457] [New] added testing of the *byId() methods Here i found a conceptual problem where the *byId() methods didn't properly resolve ObjectRefs and add them to the TX because, we added the ObjectRefs to the filter, which made the TX resolve PersistenContexts by the wrong type. Now we always add the same type of object to the ObjectFilter; the PersistentContext. This means that when we call ObjectDao.add(T), etc. we must already create such the context. This helps resolve another issue at the back of my mind: We don't want to partially commit a TX if such a basic information as the object's CTX is not available. --- .../java/ch/eitchnet/xmlpers/api/FileDao.java | 1 + .../ch/eitchnet/xmlpers/api/ObjectDao.java | 63 +++++++++---- .../PersistenceContextFactoryDelegator.java | 3 + .../xmlpers/api/PersistenceManagerLoader.java | 4 +- ...er.java => DefaultPersistenceManager.java} | 8 +- .../impl/DefaultPersistenceTransaction.java | 21 ++--- .../ch/eitchnet/xmlpers/objref/ObjectRef.java | 7 ++ .../xmlpers/objref/RefNameCreator.java | 6 +- .../ch/eitchnet/xmlpers/objref/RootRef.java | 6 ++ .../xmlpers/test/impl/BookDomDao.java | 62 ------------- .../xmlpers/test/impl/BookDomParser.java | 46 ++++++++-- .../xmlpers/test/impl/BookSaxDao.java | 84 ----------------- .../xmlpers/test/impl/BookSaxParser.java | 47 ++++++++-- .../xmlpers/test/impl/ResourceDomDao.java | 86 ----------------- .../xmlpers/test/impl/ResourceSaxDao.java | 92 ------------------- .../xmlpers/test/model/ModelBuilder.java | 5 + 16 files changed, 160 insertions(+), 381 deletions(-) rename src/main/java/ch/eitchnet/xmlpers/impl/{DefaultXmlPersistenceManager.java => DefaultPersistenceManager.java} (94%) delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java delete mode 100644 src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java index 2c0c3e0ca..c646d19f1 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java @@ -145,6 +145,7 @@ public class FileDao { if (this.verbose) { String msg = "Path for operation {0} for {1} is at {2}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, operation, objectRef.getName(), path.getAbsolutePath()); + logger.info(msg); } } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index ef5ead550..872c37a3b 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -43,59 +43,80 @@ public class ObjectDao { private final ObjectFilter objectFilter; private final FileDao fileDao; private final PersistenceTransaction tx; + private PersistenceContextFactoryDelegator ctxFactoryDelegator; public ObjectDao(PersistenceTransaction tx, FileDao fileDao, ObjectFilter objectFilter) { this.tx = tx; this.fileDao = fileDao; this.objectFilter = objectFilter; + this.ctxFactoryDelegator = this.tx.getRealm().getCtxFactoryDelegator(); } public void add(T object) { assertNotClosed(); assertNotNull(object); - this.objectFilter.add(object); + PersistenceContext ctx = createCtx(object); + ctx.setObject(object); + this.objectFilter.add(object.getClass().getName(), ctx); } - @SuppressWarnings("unchecked") public void addAll(List objects) { assertNotClosed(); assertNotNull(objects); - if (!objects.isEmpty()) - this.objectFilter.addAll((List) objects); + if (!objects.isEmpty()) { + for (T object : objects) { + PersistenceContext ctx = createCtx(object); + ctx.setObject(object); + this.objectFilter.add(object.getClass().getName(), ctx); + } + } } public void update(T object) { assertNotClosed(); assertNotNull(object); - this.objectFilter.update(object); + PersistenceContext ctx = createCtx(object); + ctx.setObject(object); + this.objectFilter.update(object.getClass().getName(), ctx); } - @SuppressWarnings("unchecked") public void updateAll(List objects) { assertNotClosed(); assertNotNull(objects); - if (!objects.isEmpty()) - this.objectFilter.updateAll((List) objects); + if (!objects.isEmpty()) { + for (T object : objects) { + PersistenceContext ctx = createCtx(object); + ctx.setObject(object); + this.objectFilter.update(object.getClass().getName(), ctx); + } + } } public void remove(T object) { assertNotClosed(); assertNotNull(object); - this.objectFilter.remove(object); + PersistenceContext ctx = createCtx(object); + ctx.setObject(object); + this.objectFilter.remove(object.getClass().getName(), ctx); } - @SuppressWarnings("unchecked") public void removeAll(List objects) { assertNotClosed(); assertNotNull(objects); - if (!objects.isEmpty()) - this.objectFilter.removeAll((List) objects); + if (!objects.isEmpty()) { + for (T object : objects) { + PersistenceContext ctx = createCtx(object); + ctx.setObject(object); + this.objectFilter.remove(object.getClass().getName(), ctx); + } + } } public void removeById(ObjectRef objectRef) { assertNotClosed(); assertIsIdRef(objectRef); - this.objectFilter.remove(objectRef); + PersistenceContext ctx = createCtx(objectRef); + this.objectFilter.remove(objectRef.getType(), ctx); } public void removeAll(ObjectRef parentRef) { @@ -107,9 +128,8 @@ public class ObjectDao { for (String id : keySet) { ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); - PersistenceContext childCtx = childRef. createPersistenceContext(this.tx); - - this.objectFilter.remove(childCtx); + PersistenceContext ctx = createCtx(childRef); + this.objectFilter.remove(childRef.getType(), ctx); } } @@ -160,6 +180,17 @@ public class ObjectDao { return size; } + private PersistenceContext createCtx(T object) { + return this.ctxFactoryDelegator. getCtxFactory(object.getClass()).createCtx(this.tx.getObjectRefCache(), + object); + } + + private PersistenceContext createCtx(ObjectRef objectRef) { + String type = objectRef.getType(); + PersistenceContextFactory ctxFactory = this.ctxFactoryDelegator. getCtxFactory(type); + return ctxFactory.createCtx(objectRef); + } + private void assertNotClosed() { if (!this.tx.isOpen()) throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); //$NON-NLS-1$ diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java index 92bff9299..bddabd33c 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java @@ -41,8 +41,11 @@ public class PersistenceContextFactoryDelegator { public void registerPersistenceContextFactory(Class classType, String type, PersistenceContextFactory ctxFactory) { + this.contextFactoryCacheByClass.put(classType, ctxFactory); this.contextFactoryCacheByType.put(type, ctxFactory); + if (!classType.getName().equals(type)) + this.contextFactoryCacheByType.put(classType.getName(), ctxFactory); } public PersistenceContextFactory getCtxFactory(Class classType) { diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java index 64e6aef82..16fb0d667 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java @@ -23,7 +23,7 @@ package ch.eitchnet.xmlpers.api; import java.util.Properties; -import ch.eitchnet.xmlpers.impl.DefaultXmlPersistenceManager; +import ch.eitchnet.xmlpers.impl.DefaultPersistenceManager; /** * @author Robert von Burg @@ -33,7 +33,7 @@ public class PersistenceManagerLoader { public static PersistenceManager load(Properties properties) { - DefaultXmlPersistenceManager persistenceManager = new DefaultXmlPersistenceManager(); + DefaultPersistenceManager persistenceManager = new DefaultPersistenceManager(); persistenceManager.initialize(properties); return persistenceManager; } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java similarity index 94% rename from src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java rename to src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java index 76c24c5bd..950f1dfeb 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultXmlPersistenceManager.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java @@ -43,9 +43,9 @@ import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; * @author Robert von Burg * */ -public class DefaultXmlPersistenceManager implements PersistenceManager { +public class DefaultPersistenceManager implements PersistenceManager { - protected static final Logger logger = LoggerFactory.getLogger(DefaultXmlPersistenceManager.class); + protected static final Logger logger = LoggerFactory.getLogger(DefaultPersistenceManager.class); protected boolean initialized; protected boolean verbose; @@ -58,7 +58,7 @@ public class DefaultXmlPersistenceManager implements PersistenceManager { if (this.initialized) throw new IllegalStateException("Already initialized!"); //$NON-NLS-1$ - String context = DefaultXmlPersistenceManager.class.getSimpleName(); + String context = DefaultPersistenceManager.class.getSimpleName(); // get verbose flag boolean verbose = PropertiesHelper.getPropertyBool(properties, context, PersistenceConstants.PROP_VERBOSE, @@ -78,7 +78,7 @@ public class DefaultXmlPersistenceManager implements PersistenceManager { } private void validateBasePath(Properties properties) { - String context = DefaultXmlPersistenceManager.class.getSimpleName(); + String context = DefaultPersistenceManager.class.getSimpleName(); String basePath = PropertiesHelper.getProperty(properties, context, PersistenceConstants.PROP_BASEPATH, null); // validate base path exists and is writable diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index ed1b3fa62..d9c524212 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -35,7 +35,6 @@ import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.MetadataDao; import ch.eitchnet.xmlpers.api.ObjectDao; import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; import ch.eitchnet.xmlpers.api.PersistenceRealm; import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.api.TransactionCloseStrategy; @@ -143,10 +142,8 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { logger.info(removed.size() + " objects removed in this tx."); //$NON-NLS-1$ for (Object object : removed) { - PersistenceContextFactoryDelegator ctxFactoryDelegator = this.realm.getCtxFactoryDelegator(); - PersistenceContext ctx = ctxFactoryDelegator.getCtxFactory(object.getClass()) - .createCtx(this.realm.getObjectRefCache(), object); - ctx.setObject(object); + @SuppressWarnings("unchecked") + PersistenceContext ctx = (PersistenceContext) object; this.fileDao.performDelete(ctx); } } @@ -160,11 +157,8 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { logger.info(updated.size() + " objects updated in this tx."); //$NON-NLS-1$ for (Object object : updated) { - - PersistenceContextFactoryDelegator ctxFactoryDelegator = this.realm.getCtxFactoryDelegator(); - PersistenceContext ctx = ctxFactoryDelegator.getCtxFactory(object.getClass()) - .createCtx(this.realm.getObjectRefCache(), object); - ctx.setObject(object); + @SuppressWarnings("unchecked") + PersistenceContext ctx = (PersistenceContext) object; this.fileDao.performUpdate(ctx); } } @@ -178,11 +172,8 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { logger.info(added.size() + " objects added in this tx."); //$NON-NLS-1$ for (Object object : added) { - - PersistenceContextFactoryDelegator ctxFactoryDelegator = this.realm.getCtxFactoryDelegator(); - PersistenceContext ctx = ctxFactoryDelegator.getCtxFactory(object.getClass()) - .createCtx(this.realm.getObjectRefCache(), object); - ctx.setObject(object); + @SuppressWarnings("unchecked") + PersistenceContext ctx = (PersistenceContext) object; this.fileDao.performCreate(ctx); } } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java index 9feb80282..a97b4f681 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java @@ -28,6 +28,8 @@ public abstract class ObjectRef extends LockableObject { public abstract boolean isLeaf(); + public abstract String getType(); + public abstract ObjectRef getParent(PersistenceTransaction tx); public abstract ObjectRef getChildIdRef(PersistenceTransaction tx, String id); @@ -37,4 +39,9 @@ public abstract class ObjectRef extends LockableObject { public abstract File getPath(PathBuilder pathBuilder); public abstract PersistenceContext createPersistenceContext(PersistenceTransaction tx); + + @Override + public String toString() { + return getName(); + } } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java b/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java index d899a410f..b47e87d2a 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java @@ -6,6 +6,8 @@ public class RefNameCreator { protected static final String SLASH = "/"; //$NON-NLS-1$ + // FIXME validate each name part that it is a valid literal for file names... + public static String createRootName(String realmName) { assertRealmName(realmName); return SLASH + realmName + SLASH; @@ -21,7 +23,7 @@ public class RefNameCreator { assertRealmName(realmName); assertType(type); assertId(id); - return SLASH + realmName + SLASH + type + SLASH + id + SLASH; + return SLASH + realmName + SLASH + type + SLASH + id; } public static String createSubTypeName(String realmName, String type, String subType) { @@ -36,7 +38,7 @@ public class RefNameCreator { assertType(type); assertSubType(subType); assertId(id); - return SLASH + realmName + SLASH + type + SLASH + subType + SLASH + id + SLASH; + return SLASH + realmName + SLASH + type + SLASH + subType + SLASH + id; } private static void assertRealmName(String realmName) { diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java index 204ceec8c..cb621a3d9 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java @@ -23,6 +23,12 @@ public class RootRef extends ObjectRef { return false; } + @Override + public String getType() { + String msg = MessageFormat.format("RootRef has no type: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + @Override public ObjectRef getParent(PersistenceTransaction tx) { String msg = MessageFormat.format("RootRef has no parent: {0}", getName()); //$NON-NLS-1$ diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java deleted file mode 100644 index d5756af25..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomDao.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import ch.eitchnet.xmlpers.test.model.Book; - -/** - * @author Robert von Burg - * - */ -@SuppressWarnings("nls") -public class BookDomDao { - - public Element serializeToDom(Book book, Document document) { - - Element element = document.createElement("Book"); - document.appendChild(element); - element.setAttribute("id", Long.toString(book.getId())); - element.setAttribute("title", book.getTitle()); - element.setAttribute("author", book.getAuthor()); - element.setAttribute("press", book.getPress()); - element.setAttribute("price", Double.toString(book.getPrice())); - - return element; - } - - public Book parseFromDom(Element element) { - - String idS = element.getAttribute("id"); - long id = Long.parseLong(idS); - String title = element.getAttribute("title"); - String author = element.getAttribute("author"); - String press = element.getAttribute("press"); - String priceS = element.getAttribute("price"); - double price = Double.parseDouble(priceS); - - Book book = new Book(id, title, author, press, price); - return book; - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java index f98f57576..291a09250 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java @@ -21,39 +21,67 @@ */ package ch.eitchnet.xmlpers.test.impl; +import javax.xml.parsers.DocumentBuilder; + import org.w3c.dom.Document; +import org.w3c.dom.Element; import ch.eitchnet.xmlpers.api.DomParser; import ch.eitchnet.xmlpers.test.model.Book; +import ch.eitchnet.xmlpers.util.DomUtil; /** * @author Robert von Burg - * + * */ public class BookDomParser implements DomParser { + private Book book; + @Override public Book getObject() { - // TODO Auto-generated method stub - return null; + return this.book; } @Override public void setObject(Book object) { - // TODO Auto-generated method stub - + this.book = object; } + @SuppressWarnings("nls") @Override public Document toDom() { - // TODO Auto-generated method stub - return null; + + DocumentBuilder documentBuilder = DomUtil.createDocumentBuilder(); + Document document = documentBuilder.getDOMImplementation().createDocument(null, null, null); + + Element rootElement = document.createElement("Book"); + document.appendChild(rootElement); + rootElement.setAttribute("id", Long.toString(this.book.getId())); + rootElement.setAttribute("title", this.book.getTitle()); + rootElement.setAttribute("author", this.book.getAuthor()); + rootElement.setAttribute("press", this.book.getPress()); + rootElement.setAttribute("price", Double.toString(this.book.getPrice())); + + return document; + } + @SuppressWarnings("nls") @Override public void fromDom(Document document) { - // TODO Auto-generated method stub + Element rootElement = document.getDocumentElement(); + + String idS = rootElement.getAttribute("id"); + long id = Long.parseLong(idS); + String title = rootElement.getAttribute("title"); + String author = rootElement.getAttribute("author"); + String press = rootElement.getAttribute("press"); + String priceS = rootElement.getAttribute("price"); + double price = Double.parseDouble(priceS); + + Book book = new Book(id, title, author, press, price); + this.book = book; } - } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java deleted file mode 100644 index 3ef898df8..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxDao.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . - */ -package ch.eitchnet.xmlpers.test.impl; - -import javax.xml.stream.XMLStreamException; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; -import ch.eitchnet.xmlpers.test.model.Book; - -/** - * @author Robert von Burg - * - */ -@SuppressWarnings("nls") -public class BookSaxDao { - - private Book book; - - public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { - writer.writeEmptyElement("Book"); - writer.writeAttribute("id", Long.toString(this.book.getId())); - writer.writeAttribute("title", this.book.getTitle()); - writer.writeAttribute("author", this.book.getAuthor()); - writer.writeAttribute("press", this.book.getPress()); - writer.writeAttribute("price", Double.toString(this.book.getPrice())); - } - - private class BookDefaultHandler extends DefaultHandler { - - private Book book; - - public BookDefaultHandler() { - // default constructor - } - - public Book getBook() { - return this.book; - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - - switch (qName) { - case "Book": - String idS = attributes.getValue("id"); - long id = Long.parseLong(idS); - Book book = new Book(id); - book.setTitle(attributes.getValue("title")); - book.setAuthor(attributes.getValue("author")); - book.setPress(attributes.getValue("press")); - String priceS = attributes.getValue("price"); - double price = Double.parseDouble(priceS); - book.setPrice(price); - this.book = book; - break; - default: - throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); - } - } - } -} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java index 950dc0896..37acd83a0 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java @@ -23,6 +23,8 @@ package ch.eitchnet.xmlpers.test.impl; import javax.xml.stream.XMLStreamException; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.SaxParser; @@ -31,32 +33,59 @@ import ch.eitchnet.xmlpers.test.model.Book; /** * @author Robert von Burg - * + * */ -public class BookSaxParser implements SaxParser { +public class BookSaxParser extends DefaultHandler implements SaxParser { + + private Book book; @Override public Book getObject() { - // TODO Auto-generated method stub - return null; + return this.book; } @Override public void setObject(Book object) { - // TODO Auto-generated method stub + this.book = object; } @Override public DefaultHandler getDefaultHandler() { - // TODO Auto-generated method stub - return null; + return this; } + @SuppressWarnings("nls") @Override - public void write(XmlPersistenceStreamWriter xmlWriter) throws XMLStreamException { - // TODO Auto-generated method stub + public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { + writer.writeEmptyElement("Book"); + writer.writeAttribute("id", Long.toString(this.book.getId())); + writer.writeAttribute("title", this.book.getTitle()); + writer.writeAttribute("author", this.book.getAuthor()); + writer.writeAttribute("press", this.book.getPress()); + writer.writeAttribute("price", Double.toString(this.book.getPrice())); } + @SuppressWarnings("nls") + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (qName) { + case "Book": + String idS = attributes.getValue("id"); + long id = Long.parseLong(idS); + Book book = new Book(id); + book.setTitle(attributes.getValue("title")); + book.setAuthor(attributes.getValue("author")); + book.setPress(attributes.getValue("press")); + String priceS = attributes.getValue("price"); + double price = Double.parseDouble(priceS); + book.setPrice(price); + this.book = book; + break; + default: + throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); + } + } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java deleted file mode 100644 index d555eb2dd..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomDao.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.test.impl; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import ch.eitchnet.xmlpers.test.model.Parameter; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - * - */ -@SuppressWarnings("nls") -public class ResourceDomDao { - - public Element serializeToDom(Resource resource, Document document) { - - Element element = document.createElement("Resource"); - document.appendChild(element); - - element.setAttribute("id", resource.getId()); - element.setAttribute("name", resource.getName()); - element.setAttribute("type", resource.getType()); - - for (String paramId : resource.getParameterKeySet()) { - Parameter param = resource.getParameterBy(paramId); - Element paramElement = document.createElement("Parameter"); - element.appendChild(paramElement); - - paramElement.setAttribute("id", param.getId()); - paramElement.setAttribute("name", param.getName()); - paramElement.setAttribute("type", param.getType()); - paramElement.setAttribute("value", param.getValue()); - } - - return element; - } - - public Resource parseFromDom(Element element) { - - String id = element.getAttribute("id"); - String name = element.getAttribute("name"); - String type = element.getAttribute("type"); - - Resource resource = new Resource(id, name, type); - - NodeList children = element.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - Node item = children.item(i); - if (!item.getNodeName().equals("Parameter")) - continue; - - Element paramElement = (Element) item; - String paramId = paramElement.getAttribute("id"); - String paramName = paramElement.getAttribute("name"); - String paramType = paramElement.getAttribute("type"); - String paramValue = paramElement.getAttribute("value"); - - Parameter param = new Parameter(paramId, paramName, paramType, paramValue); - resource.addParameter(param); - } - - return resource; - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java deleted file mode 100644 index 742b9cc63..000000000 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxDao.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . - * - */ -package ch.eitchnet.xmlpers.test.impl; - -import javax.xml.stream.XMLStreamException; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; -import ch.eitchnet.xmlpers.test.model.Parameter; -import ch.eitchnet.xmlpers.test.model.Resource; - -/** - * @author Robert von Burg - * - */ -@SuppressWarnings("nls") -public class ResourceSaxDao { - - private Resource resource; - - public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { - writer.writeElement("Resource"); - writer.writeAttribute("id", this.resource.getId()); - writer.writeAttribute("name", this.resource.getName()); - writer.writeAttribute("type", this.resource.getType()); - for (String paramId : this.resource.getParameterKeySet()) { - Parameter param = this.resource.getParameterBy(paramId); - writer.writeElement("Parameter"); - writer.writeAttribute("id", param.getId()); - writer.writeAttribute("name", param.getName()); - writer.writeAttribute("type", param.getType()); - writer.writeAttribute("value", param.getValue()); - } - } - - private class ResourceDefaultHandler extends DefaultHandler { - - private Resource resource; - - public ResourceDefaultHandler() { - // default constructor - } - - public Resource getResource() { - return this.resource; - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - - switch (qName) { - case "Resource": - String id = attributes.getValue("id"); - String name = attributes.getValue("name"); - String type = attributes.getValue("type"); - Resource resource = new Resource(id, name, type); - this.resource = resource; - break; - case "Parameter": - id = attributes.getValue("id"); - name = attributes.getValue("name"); - type = attributes.getValue("type"); - String value = attributes.getValue("value"); - Parameter param = new Parameter(id, name, type, value); - this.resource.addParameter(param); - break; - default: - throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); - } - } - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java index 7aa72e6c0..238f11464 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -70,6 +70,11 @@ public class ModelBuilder { return book; } + public static Book createBook(long id, String title, String author, String press, double price) { + Book book = new Book(id, title, author, press, price); + return book; + } + public static void updateBook(Book book) { book.setPress(BOOK_PRESS_2); } From 977d604a95bc1543d281d60b9b5d4f43c019afd1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 19 Oct 2013 17:44:59 +0200 Subject: [PATCH 188/457] [New] created tests for object with no subType This is implemented with the Book model in the test folder --- .../xmlpers/test/ObjectDaoBookTest.java | 232 ++++++++++++++++++ ...aoTest.java => ObjectDaoResourceTest.java} | 55 ++++- 2 files changed, 277 insertions(+), 10 deletions(-) create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java rename src/test/java/ch/eitchnet/xmlpers/test/{ObjectDaoTest.java => ObjectDaoResourceTest.java} (75%) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java new file mode 100644 index 000000000..591dcc542 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.BOOK_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBook; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBookUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createBook; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateBook; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.ObjectDao; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.objref.IdOfTypeRef; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.TypeRef; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Book; + +/** + * @author Robert von Burg + * + */ +public class ObjectDaoBookTest extends AbstractPersistenceTest { + + private static final String BASEPATH = "target/db/ObjectDaoTest/"; //$NON-NLS-1$ + + @Before + public void before() { + cleanPath(BASEPATH); + } + + private void setup(IoMode ioMode) { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASEPATH + ioMode.name()); + setup(properties); + } + + @Test + public void testCrudSax() { + setup(IoMode.SAX); + testCrud(IoMode.SAX); + } + + @Test + public void testCrudDom() { + setup(IoMode.DOM); + testCrud(IoMode.DOM); + } + + private PersistenceTransaction freshTx(IoMode ioMode) { + PersistenceTransaction tx = this.persistenceManager.openTx(); + tx.setIoMode(ioMode); + return tx; + } + + private void testCrud(IoMode ioMode) { + + ObjectDao objectDao; + + // create new book + Book book = createBook(); + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.add(book); + } + + // read book + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfTypeRef bookRef = tx.getObjectRefCache() + .getIdOfTypeRef(TestConstants.TYPE_BOOK, Long.toString(BOOK_ID)); + objectDao = tx.getObjectDao(); + book = objectDao.queryById(bookRef); + assertBook(book); + + // modify book + updateBook(book); + objectDao.update(book); + } + + // read modified book + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfTypeRef bookRef = tx.getObjectRefCache() + .getIdOfTypeRef(TestConstants.TYPE_BOOK, Long.toString(BOOK_ID)); + objectDao = tx.getObjectDao(); + book = objectDao.queryById(bookRef); + assertBookUpdated(book); + } + + // delete book + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.remove(book); + } + + // fail to read + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfTypeRef bookRef = tx.getObjectRefCache() + .getIdOfTypeRef(TestConstants.TYPE_BOOK, Long.toString(BOOK_ID)); + objectDao = tx.getObjectDao(); + book = objectDao.queryById(bookRef); + assertNull(book); + + // and create again + book = createBook(); + assertBook(book); + objectDao.add(book); + } + } + + @Test + public void testBulkSax() { + setup(IoMode.SAX); + testBulk(IoMode.SAX); + } + + @Test + public void testBulkDom() { + setup(IoMode.DOM); + testBulk(IoMode.DOM); + } + + private void testBulk(IoMode ioMode) { + + // create a list of books + List books = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + long id = i; + String title = "Bulk Test Book. " + i; //$NON-NLS-1$ + String author = "Nick Hornby"; //$NON-NLS-1$ + String press = "Penguin Books"; //$NON-NLS-1$ + double price = 21.30; + + Book book = createBook(id, title, author, press, price); + books.add(book); + } + + // save all + try (PersistenceTransaction tx = freshTx(ioMode);) { + ObjectDao objectDao = tx.getObjectDao(); + objectDao.addAll(books); + books.clear(); + } + + // query all + try (PersistenceTransaction tx = freshTx(ioMode);) { + TypeRef typeRef = tx.getObjectRefCache().getTypeRef(TestConstants.TYPE_BOOK); + ObjectDao objectDao = tx.getObjectDao(); + books = objectDao.queryAll(typeRef); + assertEquals("Expected to find 10 entries!", 10, books.size()); //$NON-NLS-1$ + + // delete them all + objectDao.removeAll(books); + } + + // now query them again + try (PersistenceTransaction tx = freshTx(ioMode);) { + TypeRef typeRef = tx.getObjectRefCache().getTypeRef(TestConstants.TYPE_BOOK); + ObjectDao objectDao = tx.getObjectDao(); + books = objectDao.queryAll(typeRef); + assertEquals("Expected to find 0 entries!", 0, books.size()); //$NON-NLS-1$ + } + } + + @Test + public void shouldPersistById() { + setup(IoMode.SAX); + + String classType = TestConstants.TYPE_BOOK; + long id = System.currentTimeMillis(); + String title = "About a boy"; //$NON-NLS-1$ + String author = "Nick Hornby"; //$NON-NLS-1$ + String press = "Penguin Books"; //$NON-NLS-1$ + double price = 21.30; + + // create a book + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + Book book = createBook(id, title, author, press, price); + tx.getObjectDao().add(book); + } + + // read by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfTypeRef(classType, Long.toString(id)); + Book book = tx.getObjectDao().queryById(objectRef); + assertNotNull("Expected to read book by ID", book); //$NON-NLS-1$ + } + + // delete by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfTypeRef(classType, Long.toString(id)); + tx.getObjectDao().removeById(objectRef); + } + + // fail to read by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfTypeRef(classType, Long.toString(id)); + Book book = tx.getObjectDao().queryById(objectRef); + assertNull("Expected that book was deleted by ID, thus can not be read anymore", book); //$NON-NLS-1$ + } + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java similarity index 75% rename from src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java rename to src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java index c13712a8a..0932971f2 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java @@ -28,6 +28,7 @@ import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import java.util.ArrayList; @@ -42,15 +43,17 @@ import ch.eitchnet.xmlpers.api.ObjectDao; import ch.eitchnet.xmlpers.api.PersistenceConstants; import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; +import ch.eitchnet.xmlpers.objref.ObjectRef; import ch.eitchnet.xmlpers.objref.SubTypeRef; import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.ModelBuilder; import ch.eitchnet.xmlpers.test.model.Resource; /** * @author Robert von Burg * */ -public class ObjectDaoTest extends AbstractPersistenceTest { +public class ObjectDaoResourceTest extends AbstractPersistenceTest { private static final String BASEPATH = "target/db/ObjectDaoTest/"; //$NON-NLS-1$ @@ -137,15 +140,13 @@ public class ObjectDaoTest extends AbstractPersistenceTest { @Test public void testBulkSax() { setup(IoMode.SAX); - IoMode ioMode = IoMode.SAX; - testBulk(ioMode); + testBulk(IoMode.SAX); } @Test public void testBulkDom() { setup(IoMode.DOM); - IoMode ioMode = IoMode.DOM; - testBulk(ioMode); + testBulk(IoMode.DOM); } private void testBulk(IoMode ioMode) { @@ -163,11 +164,9 @@ public class ObjectDaoTest extends AbstractPersistenceTest { resources.add(resource); } - ObjectDao objectDao; - // save all try (PersistenceTransaction tx = freshTx(ioMode);) { - objectDao = tx.getObjectDao(); + ObjectDao objectDao = tx.getObjectDao(); objectDao.addAll(resources); resources.clear(); } @@ -175,7 +174,7 @@ public class ObjectDaoTest extends AbstractPersistenceTest { // query all try (PersistenceTransaction tx = freshTx(ioMode);) { SubTypeRef subTypeRef = tx.getObjectRefCache().getSubTypeRef(TestConstants.TYPE_RES, type); - objectDao = tx.getObjectDao(); + ObjectDao objectDao = tx.getObjectDao(); resources = objectDao.queryAll(subTypeRef); assertEquals("Expected to find 10 entries!", 10, resources.size()); //$NON-NLS-1$ @@ -186,9 +185,45 @@ public class ObjectDaoTest extends AbstractPersistenceTest { // now query them again try (PersistenceTransaction tx = freshTx(ioMode);) { SubTypeRef subTypeRef = tx.getObjectRefCache().getSubTypeRef(TestConstants.TYPE_RES, type); - objectDao = tx.getObjectDao(); + ObjectDao objectDao = tx.getObjectDao(); resources = objectDao.queryAll(subTypeRef); assertEquals("Expected to find 0 entries!", 0, resources.size()); //$NON-NLS-1$ } } + + @Test + public void shouldPersistById() { + setup(IoMode.SAX); + + String classType = TestConstants.TYPE_RES; + String subType = ModelBuilder.RES_TYPE; + String id = "shouldPersistById"; //$NON-NLS-1$ + String name = "shouldPersistById "; //$NON-NLS-1$ + + // create a resource + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + Resource resource = createResource(id, name, subType); + tx.getObjectDao().add(resource); + } + + // read by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(classType, subType, id); + Resource resource = tx.getObjectDao().queryById(objectRef); + assertNotNull("Expected to read resource by ID", resource); //$NON-NLS-1$ + } + + // delete by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(classType, subType, id); + tx.getObjectDao().removeById(objectRef); + } + + // fail to read by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(classType, subType, id); + Resource resource = tx.getObjectDao().queryById(objectRef); + assertNull("Expected that resource was deleted by ID, thus can not be read anymore", resource); //$NON-NLS-1$ + } + } } From 30ddf3cfbf8ff1106d5268e553a93c280d3aee64 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 19 Oct 2013 17:45:33 +0200 Subject: [PATCH 189/457] [New] implemented a basic test for multiple realms (mandates) --- .../ch/eitchnet/xmlpers/test/RealmTest.java | 101 +++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java b/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java index 755a09c56..aeef634ca 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java @@ -1,11 +1,110 @@ package ch.eitchnet.xmlpers.test; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.Properties; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.ModelBuilder; +import ch.eitchnet.xmlpers.test.model.Resource; + @SuppressWarnings("nls") public class RealmTest extends AbstractPersistenceTest { - protected static final String BASE_PATH = "target/db/RealmTest"; + private static final String REALM_2 = "Realm2"; + private static final String REALM_1 = "Realm1"; + protected static final String BASE_PATH = "target/db/RealmTest/"; + @BeforeClass public static void beforeClass() { cleanPath(BASE_PATH); } + + @Before + public void before() { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASE_PATH + IoMode.DOM.name()); + setup(properties); + } + + @Test + public void shouldNotFindObjInBothRealms() { + + // object details + String objType = TestConstants.TYPE_RES; + String type = ModelBuilder.RES_TYPE; + String name = ModelBuilder.RES_NAME; + String id = "shouldNotFindObjInBothRealms"; + + // create in first realm + try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { + Resource resource1 = ModelBuilder.createResource(id, name, type); + txRealm1.getObjectDao().add(resource1); + } + + // find in first realm + try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { + ObjectRef objectRef = txRealm1.getObjectRefCache().getIdOfSubTypeRef(objType, type, id); + Resource resource = txRealm1.getObjectDao().queryById(objectRef); + assertNotNull("Resource was not found in first realm!", resource); + } + + // fail to find in second realm + try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { + ObjectRef objectRef = txRealm2.getObjectRefCache().getIdOfSubTypeRef(objType, type, id); + Resource resource = txRealm2.getObjectDao().queryById(objectRef); + assertNull("Resource was not created in second realm, thus not expected to be found there!", resource); + } + } + + @Test + public void shouldNotDeleteObjInWrongRealm() { + + // object details + String objType = TestConstants.TYPE_RES; + String subType = ModelBuilder.RES_TYPE; + String name = ModelBuilder.RES_NAME; + String id = "shouldNotDeleteObjInWrongRealm"; + + // create in first realm + try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { + Resource resource1 = ModelBuilder.createResource(id, name, subType); + txRealm1.getObjectDao().add(resource1); + } + + // create in second realm + try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { + Resource resource1 = ModelBuilder.createResource(id, name, subType); + txRealm2.getObjectDao().add(resource1); + } + + // delete in second realm + try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { + ObjectRef objectRef = txRealm2.getObjectRefCache().getIdOfSubTypeRef(objType, subType, id); + txRealm2.getObjectDao().removeById(objectRef); + } + + // fail to find in second realm + try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { + ObjectRef objectRef = txRealm2.getObjectRefCache().getIdOfSubTypeRef(objType, subType, id); + Resource resource = txRealm2.getObjectDao().queryById(objectRef); + assertNull("Resource was not deleted from second realm, thus not expected to be found there!", resource); + } + + // find in first realm + try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { + ObjectRef objectRef = txRealm1.getObjectRefCache().getIdOfSubTypeRef(objType, subType, id); + Resource resource = txRealm1.getObjectDao().queryById(objectRef); + assertNotNull("Resource was not found in first realm!", resource); + } + } } From 5be5cd3fa098ff7a978e7553b949fe3f916af9e6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 19 Oct 2013 17:49:27 +0200 Subject: [PATCH 190/457] [Minor] fixed compiler warnings --- src/main/java/ch/eitchnet/xmlpers/api/IoMode.java | 8 ++++++++ .../ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java | 3 +++ .../java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java | 1 + src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java | 1 + src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java | 1 + src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java | 1 + src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java | 7 +++---- 7 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java b/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java index 7464bff31..2b80e64b0 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java @@ -50,10 +50,18 @@ public enum IoMode { } }; + /** + * @param ctx + * @param fileIo + */ public void write(PersistenceContext ctx, FileIo fileIo) { throw new UnsupportedOperationException("Override me!"); //$NON-NLS-1$ } + /** + * @param ctx + * @param fileIo + */ public void read(PersistenceContext ctx, FileIo fileIo) { throw new UnsupportedOperationException("Override me!"); //$NON-NLS-1$ } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java index a535fe5c6..7f93e02a9 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java @@ -15,6 +15,9 @@ public enum TransactionCloseStrategy { } }; + /** + * @param tx + */ public void close(PersistenceTransaction tx) { throw new UnsupportedOperationException("Override in enum!"); //$NON-NLS-1$ } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java index d2c4b37a6..8312a93da 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java @@ -22,6 +22,7 @@ public class IdOfSubTypeRef extends ObjectRef { this.id = id; } + @Override public String getType() { return this.type; } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java index 40db517c3..a6f09dbef 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java @@ -20,6 +20,7 @@ public class IdOfTypeRef extends ObjectRef { this.id = id; } + @Override public String getType() { return this.type; } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java index 29064486c..560526b3d 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java @@ -18,6 +18,7 @@ public class SubTypeRef extends ObjectRef { this.subType = subType; } + @Override public String getType() { return this.type; } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java index 6a588527f..44e2770a1 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java @@ -16,6 +16,7 @@ public class TypeRef extends ObjectRef { this.type = type; } + @Override public String getType() { return this.type; } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java index 2ba28bcd7..150b18f36 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java @@ -80,7 +80,7 @@ public class FileDaoTest extends AbstractPersistenceTest { try (PersistenceTransaction tx = new DefaultPersistenceTransaction(this.realm, VERBOSE)) { FileDao fileDao = new FileDao(tx, this.pathBuilder, VERBOSE); tx.setIoMode(IoMode.SAX); - testCrud(tx, this.realm.getCtxFactoryDelegator(), fileDao); + testCrud(this.realm.getCtxFactoryDelegator(), fileDao); } } @@ -90,12 +90,11 @@ public class FileDaoTest extends AbstractPersistenceTest { try (PersistenceTransaction tx = new DefaultPersistenceTransaction(this.realm, VERBOSE)) { FileDao fileDao = new FileDao(tx, this.pathBuilder, VERBOSE); tx.setIoMode(IoMode.DOM); - testCrud(tx, this.realm.getCtxFactoryDelegator(), fileDao); + testCrud(this.realm.getCtxFactoryDelegator(), fileDao); } } - private void testCrud(PersistenceTransaction tx, PersistenceContextFactoryDelegator ctxFactoryDelegator, - FileDao fileDao) { + private void testCrud(PersistenceContextFactoryDelegator ctxFactoryDelegator, FileDao fileDao) { Resource resource = createResource(); assertResource(resource); From 3c623bd20cd57e7ce2eb5ac27c4ab6c7d4634eff Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 19 Oct 2013 21:28:02 +0200 Subject: [PATCH 191/457] [Project] Set maven parent for this project to ch.eitchnet.parent --- pom.xml | 118 ++++---------------------------------------------------- 1 file changed, 8 insertions(+), 110 deletions(-) diff --git a/pom.xml b/pom.xml index bd8741828..a865ef0ea 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,14 @@ 4.0.0 - ch.eitchnet + + + ch.eitchnet + ch.eitchnet.parent + 0.0.1-SNAPSHOT + ../ch.eitchnet.parent/pom.xml + + ch.eitchnet.xmlpers jar 0.3.0-SNAPSHOT @@ -15,95 +22,24 @@ 2011 - - - GNU Lesser General Public License - http://www.gnu.org/licenses/lgpl.html - repo - - - - eitchnet.ch - http://blog.eitchnet.ch - - - - eitch - Robert von Vurg - eitch@eitchnet.ch - http://blog.eitchnet.ch - eitchnet.ch - http://blog.eitchnet.ch - - architect - developer - - +1 - - http://localhost - - - Github Issues https://github.com/eitch/ch.eitchnet.xmlpers/issues - - scm:git:https://github.com/eitch/ch.eitchnet.xmlpers.git scm:git:git@github.com:eitch/ch.eitchnet.xmlpers.git https://github.com/eitch/ch.eitchnet.xmlpers - - - - - deployment - Internal Releases - http://nexus.eitchnet.ch/content/repositories/releases/ - - - deployment - Internal Releases - http://nexus.eitchnet.ch/content/repositories/snapshots/ - - - - - junit - junit - 4.10 - test - ch.eitchnet ch.eitchnet.utils 0.2.0-SNAPSHOT - - org.slf4j - slf4j-api - 1.7.2 - - - org.slf4j - slf4j-log4j12 - 1.7.2 - test - @@ -112,60 +48,22 @@ org.apache.maven.plugins maven-eclipse-plugin - 2.9 - - true - true - - org.apache.maven.plugins maven-compiler-plugin - 2.3.2 - - 1.7 - 1.7 - - org.apache.maven.plugins maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar-no-fork - - - - org.apache.maven.plugins maven-jar-plugin - 2.4 - - - - true - true - - - - - org.apache.maven.plugins maven-site-plugin - 2.3 - - UTF-8 - From 3c006b541a24ae29b4a5b0de7aebbb07dbf25889 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 19 Oct 2013 21:28:11 +0200 Subject: [PATCH 192/457] [New] implemented a test to allow multiple operations This test makes sure that updating after adding, or any other operation in the same TX works properly. Found a problem where the PersistenceContext didn't implement hashCode() and equals() which lead to each operation being added even though a previous operation was already registered --- .../xmlpers/api/PersistenceContext.java | 39 +++++ .../xmlpers/objref/IdOfSubTypeRef.java | 46 +++++- .../eitchnet/xmlpers/objref/IdOfTypeRef.java | 40 +++++- .../ch/eitchnet/xmlpers/objref/ObjectRef.java | 10 +- .../ch/eitchnet/xmlpers/objref/RootRef.java | 25 ++++ .../eitchnet/xmlpers/objref/SubTypeRef.java | 37 +++++ .../ch/eitchnet/xmlpers/objref/TypeRef.java | 31 ++++ .../java/javanet/staxutils/Indentation.java | 3 +- .../staxutils/IndentingXMLStreamWriter.java | 2 + .../xmlpers/test/ObjectDaoResourceTest.java | 134 ++++++++++++++++++ 10 files changed, 360 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java index 5a074832f..9655d5e35 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java @@ -52,4 +52,43 @@ public class PersistenceContext { public void setParserFactory(ParserFactory parserFactory) { this.parserFactory = parserFactory; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.objectRef == null) ? 0 : this.objectRef.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; + PersistenceContext other = (PersistenceContext) obj; + if (this.objectRef == null) { + if (other.objectRef != null) + return false; + } else if (!this.objectRef.equals(other.objectRef)) + return false; + return true; + } + + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PersistenceContext [objectRef="); + builder.append(this.objectRef); + builder.append(", object="); + builder.append(this.object); + builder.append(", parserFactory="); + builder.append(this.parserFactory); + builder.append("]"); + return builder.toString(); + } } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java index 8312a93da..7c7060e8a 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java @@ -73,8 +73,50 @@ public class IdOfSubTypeRef extends ObjectRef { @Override public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { PersistenceContextFactoryDelegator ctxFactoryDelegator = tx.getRealm().getCtxFactoryDelegator(); - PersistenceContextFactory persistenceContextFactory = ctxFactoryDelegator - . getCtxFactory(this.type); + PersistenceContextFactory persistenceContextFactory = ctxFactoryDelegator. getCtxFactory(this.type); return persistenceContextFactory.createCtx(this); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.hashCode()); + result = prime * result + ((this.subType == null) ? 0 : this.subType.hashCode()); + result = prime * result + ((this.type == null) ? 0 : this.type.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; + IdOfSubTypeRef other = (IdOfSubTypeRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + if (this.id == null) { + if (other.id != null) + return false; + } else if (!this.id.equals(other.id)) + return false; + if (this.subType == null) { + if (other.subType != null) + return false; + } else if (!this.subType.equals(other.subType)) + return false; + if (this.type == null) { + if (other.type != null) + return false; + } else if (!this.type.equals(other.type)) + return false; + return true; + } } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java index a6f09dbef..dec93808e 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java @@ -67,8 +67,44 @@ public class IdOfTypeRef extends ObjectRef { @Override public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { PersistenceContextFactoryDelegator ctxFactoryDelegator = tx.getRealm().getCtxFactoryDelegator(); - PersistenceContextFactory persistenceContextFactory = ctxFactoryDelegator - . getCtxFactory(this.type); + PersistenceContextFactory persistenceContextFactory = ctxFactoryDelegator. getCtxFactory(this.type); return persistenceContextFactory.createCtx(this); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.hashCode()); + result = prime * result + ((this.type == null) ? 0 : this.type.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; + IdOfTypeRef other = (IdOfTypeRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + if (this.id == null) { + if (other.id != null) + return false; + } else if (!this.id.equals(other.id)) + return false; + if (this.type == null) { + if (other.type != null) + return false; + } else if (!this.type.equals(other.type)) + return false; + return true; + } } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java index a97b4f681..356bdff4e 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java @@ -8,8 +8,8 @@ import ch.eitchnet.xmlpers.impl.PathBuilder; public abstract class ObjectRef extends LockableObject { - private String realmName; - private String name; + protected final String realmName; + protected final String name; protected ObjectRef(String realmName, String name) { this.realmName = realmName; @@ -44,4 +44,10 @@ public abstract class ObjectRef extends LockableObject { public String toString() { return getName(); } + + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java index cb621a3d9..65640ab24 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java @@ -56,4 +56,29 @@ public class RootRef extends ObjectRef { String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ throw new UnsupportedOperationException(msg); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.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; + RootRef other = (RootRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + return true; + } } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java index 560526b3d..fefcba6ef 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java @@ -63,4 +63,41 @@ public class SubTypeRef extends ObjectRef { String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ throw new UnsupportedOperationException(msg); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.hashCode()); + result = prime * result + ((this.subType == null) ? 0 : this.subType.hashCode()); + result = prime * result + ((this.type == null) ? 0 : this.type.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; + SubTypeRef other = (SubTypeRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + if (this.subType == null) { + if (other.subType != null) + return false; + } else if (!this.subType.equals(other.subType)) + return false; + if (this.type == null) { + if (other.type != null) + return false; + } else if (!this.type.equals(other.type)) + return false; + return true; + } } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java index 44e2770a1..be34fff63 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java @@ -56,4 +56,35 @@ public class TypeRef extends ObjectRef { String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ throw new UnsupportedOperationException(msg); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.hashCode()); + result = prime * result + ((this.type == null) ? 0 : this.type.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; + TypeRef other = (TypeRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + if (this.type == null) { + if (other.type != null) + return false; + } else if (!this.type.equals(other.type)) + return false; + return true; + } } diff --git a/src/main/java/javanet/staxutils/Indentation.java b/src/main/java/javanet/staxutils/Indentation.java index 4dfa7b48e..e7f554b92 100644 --- a/src/main/java/javanet/staxutils/Indentation.java +++ b/src/main/java/javanet/staxutils/Indentation.java @@ -4,10 +4,11 @@ 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 = " "; + public static final String DEFAULT_INDENT = " "; /** * Set the characters used for one level of indentation. The default is diff --git a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java index dd5438e24..aa9ffc3b6 100644 --- a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java +++ b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java @@ -32,6 +32,7 @@ package javanet.staxutils; import javanet.staxutils.helpers.StreamWriterDelegate; + import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -68,6 +69,7 @@ import javax.xml.stream.XMLStreamWriter; * * @author John Kristian */ +@SuppressWarnings("nls") public class IndentingXMLStreamWriter extends StreamWriterDelegate implements Indentation { public IndentingXMLStreamWriter(XMLStreamWriter out) { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java index 0932971f2..69b1f2edf 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java @@ -21,12 +21,14 @@ */ package ch.eitchnet.xmlpers.test; +import static ch.eitchnet.xmlpers.test.impl.TestConstants.TYPE_RES; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -36,12 +38,15 @@ import java.util.List; import java.util.Properties; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.ObjectDao; import ch.eitchnet.xmlpers.api.PersistenceConstants; import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; import ch.eitchnet.xmlpers.objref.ObjectRef; import ch.eitchnet.xmlpers.objref.SubTypeRef; @@ -55,6 +60,9 @@ import ch.eitchnet.xmlpers.test.model.Resource; */ public class ObjectDaoResourceTest extends AbstractPersistenceTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + private static final String BASEPATH = "target/db/ObjectDaoTest/"; //$NON-NLS-1$ @BeforeClass @@ -226,4 +234,130 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { assertNull("Expected that resource was deleted by ID, thus can not be read anymore", resource); //$NON-NLS-1$ } } + + @Test + public void shouldFailModifyNotExisting() { + setup(IoMode.SAX); + + this.thrown.expect(XmlPersistenceException.class); + this.thrown.expectMessage(containsString("Persistence unit does not exist for")); //$NON-NLS-1$ + + // update + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + Resource resource = createResource(); + tx.getObjectDao().update(resource); + } + } + + @Test + public void shouldFailDeleteNotExisting() { + setup(IoMode.SAX); + + this.thrown.expect(XmlPersistenceException.class); + this.thrown.expectMessage(containsString("Persistence unit does not exist for")); //$NON-NLS-1$ + + // delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + Resource resource = createResource(); + tx.getObjectDao().remove(resource); + } + } + + @Test + public void shouldAllowAllOperationsInSameTx() { + setup(IoMode.SAX); + + String subType = ModelBuilder.RES_TYPE; + String name = "shouldPersistById "; //$NON-NLS-1$ + + // create + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + String id = "shouldAllowAllOperationsInSameTx_create"; //$NON-NLS-1$ + Resource resource = createResource(id, name, subType); + + tx.getObjectDao().add(resource); + } + + // create / modify + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + String id = "shouldAllowAllOperationsInSameTx_create_modify"; //$NON-NLS-1$ + Resource resource = createResource(id, name, subType); + + tx.getObjectDao().add(resource); + tx.getObjectDao().update(resource); + } + + // create / delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + String id = "shouldAllowAllOperationsInSameTx_create_delete"; //$NON-NLS-1$ + Resource resource = createResource(id, name, subType); + + tx.getObjectDao().add(resource); + tx.getObjectDao().remove(resource); + } + + // create / modify / delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + String id = "shouldAllowAllOperationsInSameTx_create_modify_delete"; //$NON-NLS-1$ + Resource resource = createResource(id, name, subType); + + tx.getObjectDao().add(resource); + tx.getObjectDao().update(resource); + tx.getObjectDao().remove(resource); + } + + String id = "shouldAllowAllOperationsInSameTx_read_modify"; //$NON-NLS-1$ + + // prepare for read/modify + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + Resource resource = createResource(id, name, subType); + tx.getObjectDao().add(resource); + } + + // read / modify + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, subType, id); + Object resource = tx.getObjectDao().queryById(objectRef); + assertNotNull(resource); + tx.getObjectDao().update(resource); + } + + // read / delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, subType, id); + Object resource = tx.getObjectDao().queryById(objectRef); + assertNotNull(resource); + tx.getObjectDao().remove(resource); + } + + // make sure deleted, then recreate + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, subType, id); + Object resource = tx.getObjectDao().queryById(objectRef); + assertNull(resource); + + // recreate + resource = createResource(id, name, subType); + tx.getObjectDao().add(resource); + } + + // read / modify / delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, subType, id); + Object resource = tx.getObjectDao().queryById(objectRef); + assertNotNull(resource); + tx.getObjectDao().update(resource); + tx.getObjectDao().remove(resource); + } + } } From f176960ba92c6beca0d8e60127213fc4b34715cc Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Oct 2013 00:17:20 +0200 Subject: [PATCH 193/457] [New] added ObjectFilter.getAll() further informations added ObjectFilter.getAll(Class, String) and ObjectFilter.getAll(Class clazz) --- .../utils/objectfilter/ObjectFilter.java | 96 ++++++++++++++++--- 1 file changed, 83 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index 9eacf63f8..cc0f9123c 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -125,7 +125,7 @@ public class ObjectFilter { public void add(String key, Object objectToAdd) { if (ObjectFilter.logger.isDebugEnabled()) - ObjectFilter.logger.debug("add object " + objectToAdd + " with key " + key); + 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); @@ -140,7 +140,7 @@ public class ObjectFilter { 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}"; + 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())); } @@ -150,9 +150,9 @@ public class ObjectFilter { Operation op = cached.getOperation(); switch (op) { case ADD: - throw new IllegalStateException("Stale State exception: Invalid + after +"); + throw new IllegalStateException("Stale State exception: Invalid + after +"); //$NON-NLS-1$ case MODIFY: - throw new IllegalStateException("Stale State exception: Invalid + after +="); + throw new IllegalStateException("Stale State exception: Invalid + after +="); //$NON-NLS-1$ case REMOVE: // replace key if necessary replaceKey(cached.getObject(), objectToAdd); @@ -161,6 +161,8 @@ public class ObjectFilter { 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 @@ -171,7 +173,7 @@ public class ObjectFilter { 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}"; + 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); } @@ -207,7 +209,7 @@ public class ObjectFilter { public void update(String key, Object objectToUpdate) { if (ObjectFilter.logger.isDebugEnabled()) - ObjectFilter.logger.debug("update object " + objectToUpdate + " with key " + key); + 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); @@ -222,7 +224,7 @@ public class ObjectFilter { 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}"; + 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())); } @@ -239,7 +241,9 @@ public class ObjectFilter { cached.setObject(objectToUpdate); break; case REMOVE: - throw new IllegalStateException("Stale State exception: Invalid += after -"); + 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 @@ -274,7 +278,7 @@ public class ObjectFilter { public void remove(String key, Object objectToRemove) { if (ObjectFilter.logger.isDebugEnabled()) - ObjectFilter.logger.debug("remove object " + objectToRemove + " with key " + key); + 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); @@ -287,7 +291,7 @@ public class ObjectFilter { 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}"; + 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())); } @@ -309,7 +313,9 @@ public class ObjectFilter { cached.setOperation(Operation.REMOVE); break; case REMOVE: - throw new IllegalStateException("Stale State exception: Invalid - after -"); + 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 } @@ -433,6 +439,7 @@ public class ObjectFilter { * * @param key * The registration key of the objects to match + * * @return The list of all objects registered under the given key and that need to be added. */ public List getAdded(String key) { @@ -453,6 +460,7 @@ public class ObjectFilter { * The class type of the object to be retrieved, that acts as an additional filter criterion. * @param key * The registration key of the objects to match + * * @return The list of all objects registered under the given key and that need to be added. */ public List getAdded(Class clazz, String key) { @@ -475,6 +483,7 @@ public class ObjectFilter { * * @param key * registration key of the objects to match + * * @return The list of all objects registered under the given key and that need to be updated. */ public List getUpdated(String key) { @@ -491,8 +500,11 @@ public class ObjectFilter { /** * Get all objects that were registered under the given key and that have as a resulting final action an update. * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. * @param key * registration key of the objects to match + * * @return The list of all objects registered under the given key and that need to be updated. */ public List getUpdated(Class clazz, String key) { @@ -515,6 +527,7 @@ public class ObjectFilter { * * @param key * The registration key of the objects to match + * * @return The list of object registered under the given key that have, as a final action, removal. */ public List getRemoved(String key) { @@ -531,8 +544,11 @@ public class ObjectFilter { /** * Get all objects that were registered under the given key that have as a resulting final action their removal. * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. * @param key * The registration key of the objects to match + * * @return The list of object registered under the given key that have, as a final action, removal. */ public List getRemoved(Class clazz, String key) { @@ -550,12 +566,59 @@ public class ObjectFilter { return removedObjects; } + /** + * Get all objects that were registered under the given key + * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. + * @param key + * The registration key of the objects to match + * + * @return The list of object registered under the given key that have, as a final action, removal. + */ + public List getAll(Class clazz, String key) { + List objects = new LinkedList(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getKey().equals(key)) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + objects.add(object); + } + } + } + return objects; + } + + /** + * Get all objects that of the given class + * + * @param clazz + * The class type of the object to be retrieved, that acts as an additional filter criterion. + * + * @return The list of all objects that of the given class + */ + public List getAll(Class clazz) { + List objects = new LinkedList(); + Collection allObjs = this.cache.values(); + for (ObjectCache objectCache : allObjs) { + if (objectCache.getObject().getClass() == clazz) { + @SuppressWarnings("unchecked") + V object = (V) objectCache.getObject(); + objects.add(object); + } + } + return objects; + } + /** * Get all the objects that were processed in this transaction, that were registered under the given key. No action * is associated to the object. * * @param key * The registration key for which the objects shall be retrieved + * * @return The list of objects matching the given key. */ public List getAll(String key) { @@ -575,6 +638,7 @@ public class ObjectFilter { * * @param key * The registration key for which the objects shall be retrieved + * * @return The list of objects matching the given key. */ public List getCache(String key) { @@ -598,17 +662,23 @@ public class ObjectFilter { } /** - * Clear the cache. + * 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(); } @@ -619,7 +689,7 @@ public class ObjectFilter { 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."); + ObjectFilter.logger.error("Rolling IDs of objectFilter back to 1. Hope this is fine."); //$NON-NLS-1$ ObjectFilter.id = 1; } return ObjectFilter.id; From 87f82e19797e88052e6995bd51f08ce835483653 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Oct 2013 01:14:59 +0200 Subject: [PATCH 194/457] [New] implemented locking for all persistence operations A LockingTest tests the locking, but still needs to be extended to test further use cases, but basic locking works. --- .../java/ch/eitchnet/xmlpers/api/FileDao.java | 76 ++++---- .../ch/eitchnet/xmlpers/api/MetadataDao.java | 84 +++++---- .../ch/eitchnet/xmlpers/api/ObjectDao.java | 96 ++++++++--- .../xmlpers/api/PersistenceConstants.java | 1 + .../impl/DefaultPersistenceManager.java | 15 +- .../impl/DefaultPersistenceTransaction.java | 15 +- .../xmlpers/objref/LockableObject.java | 45 ++++- .../xmlpers/test/AbstractPersistenceTest.java | 5 + .../ch/eitchnet/xmlpers/test/LockingTest.java | 162 ++++++++++++++++++ .../xmlpers/test/model/ModelBuilder.java | 4 + 10 files changed, 402 insertions(+), 101 deletions(-) create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java index c646d19f1..ffd55b2e9 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java @@ -112,33 +112,41 @@ public class FileDao { throw new IllegalArgumentException("IdRefs don't reference directories!"); //$NON-NLS-1$ } - File directoryPath = objectRef.getPath(this.pathBuilder); - if (!directoryPath.isDirectory()) { - String msg = "The path for {0} is not a directory: {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath()); - throw new IllegalArgumentException(msg); + objectRef.lock(); + + try { + + File directoryPath = objectRef.getPath(this.pathBuilder); + if (!directoryPath.isDirectory()) { + String msg = "The path for {0} is not a directory: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath()); + throw new IllegalArgumentException(msg); + } + + // stop if empty + if (directoryPath.list().length != 0) + return; + + // delete + if (!directoryPath.delete()) { + String msg = "Deletion of empty directory for {0} at {1} failed! Check file permissions!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath()); + throw new XmlPersistenceException(msg); + } + + // log + if (this.verbose) { + String msg = "Deleted empty directory for {0} at {1}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, objectRef.getName(), directoryPath)); + } + + // recursively delete + ObjectRef parent = objectRef.getParent(this.tx); + deleteEmptyDirectories(parent); + + } finally { + objectRef.unlock(); } - - // stop if empty - if (directoryPath.list().length != 0) - return; - - // delete - if (!directoryPath.delete()) { - String msg = "Deletion of empty directory for {0} at {1} failed! Check file permissions!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath()); - throw new XmlPersistenceException(msg); - } - - // log - if (this.verbose) { - String msg = "Deleted empty directory for {0} at {1}"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, objectRef.getName(), directoryPath)); - } - - // recursively delete - ObjectRef parent = objectRef.getParent(this.tx); - deleteEmptyDirectories(parent); } private void logPath(IoOperation operation, File path, ObjectRef objectRef) { @@ -150,11 +158,17 @@ public class FileDao { } private void createMissingParents(File path, ObjectRef objectRef) { - File parentFile = path.getParentFile(); - if (!parentFile.exists() && !parentFile.mkdirs()) { - String msg = "Could not create parent path for {0} at {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath()); - throw new XmlPersistenceException(msg); + ObjectRef parentRef = objectRef.getParent(this.tx); + parentRef.lock(); + try { + File parentFile = parentRef.getPath(this.pathBuilder); + if (!parentFile.exists() && !parentFile.mkdirs()) { + String msg = "Could not create parent path for {0} at {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath()); + throw new XmlPersistenceException(msg); + } + } finally { + parentRef.unlock(); } } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java b/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java index b2e57e7e6..c2f80da0d 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java @@ -56,16 +56,21 @@ public class MetadataDao { assertNotClosed(this.tx); assertNotIdRef(parentRef); - File queryPath = parentRef.getPath(this.pathBuilder); - Set keySet = queryTypeSet(queryPath); + parentRef.lock(); + try { + File queryPath = parentRef.getPath(this.pathBuilder); + Set keySet = queryTypeSet(queryPath); - if (this.verbose) { - String msg = "Found {0} types for {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, keySet.size(), parentRef.getName()); - logger.info(msg); + if (this.verbose) { + String msg = "Found {0} types for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, keySet.size(), parentRef.getName()); + logger.info(msg); + } + + return keySet; + } finally { + parentRef.unlock(); } - - return keySet; } public Set queryKeySet(ObjectRef parentRef) { @@ -73,16 +78,21 @@ public class MetadataDao { assertNotRootRef(parentRef); assertNotIdRef(parentRef); - File queryPath = parentRef.getPath(this.pathBuilder); - Set keySet = queryKeySet(queryPath); + parentRef.lock(); + try { + File queryPath = parentRef.getPath(this.pathBuilder); + Set keySet = queryKeySet(queryPath); - if (this.verbose) { - String msg = "Found {0} objects for {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, keySet.size(), parentRef.getName()); - logger.info(msg); + if (this.verbose) { + String msg = "Found {0} objects for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, keySet.size(), parentRef.getName()); + logger.info(msg); + } + + return keySet; + } finally { + parentRef.unlock(); } - - return keySet; } public long queryTypeSize(ObjectRef parentRef) { @@ -90,31 +100,41 @@ public class MetadataDao { assertNotRootRef(parentRef); assertNotIdRef(parentRef); - File queryPath = parentRef.getPath(this.pathBuilder); - long numberOfFiles = queryTypeSize(queryPath); + parentRef.lock(); + try { + File queryPath = parentRef.getPath(this.pathBuilder); + long numberOfFiles = queryTypeSize(queryPath); - if (this.verbose) { - String msg = "Found {0} types for {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName()); - logger.info(msg); + if (this.verbose) { + String msg = "Found {0} types for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName()); + logger.info(msg); + } + + return numberOfFiles; + } finally { + parentRef.unlock(); } - - return numberOfFiles; } public long querySize(ObjectRef parentRef) { assertNotClosed(this.tx); - File queryPath = parentRef.getPath(this.pathBuilder); - long numberOfFiles = querySize(queryPath); + parentRef.lock(); + try { + File queryPath = parentRef.getPath(this.pathBuilder); + long numberOfFiles = querySize(queryPath); - if (this.verbose) { - String msg = "Found {0} objects for {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName()); - logger.info(msg); + if (this.verbose) { + String msg = "Found {0} objects for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName()); + logger.info(msg); + } + + return numberOfFiles; + } finally { + parentRef.unlock(); } - - return numberOfFiles; } /** diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index 872c37a3b..c884db65f 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -57,6 +57,7 @@ public class ObjectDao { assertNotNull(object); PersistenceContext ctx = createCtx(object); ctx.setObject(object); + ctx.getObjectRef().lock(); this.objectFilter.add(object.getClass().getName(), ctx); } @@ -67,6 +68,7 @@ public class ObjectDao { for (T object : objects) { PersistenceContext ctx = createCtx(object); ctx.setObject(object); + ctx.getObjectRef().lock(); this.objectFilter.add(object.getClass().getName(), ctx); } } @@ -77,6 +79,7 @@ public class ObjectDao { assertNotNull(object); PersistenceContext ctx = createCtx(object); ctx.setObject(object); + ctx.getObjectRef().lock(); this.objectFilter.update(object.getClass().getName(), ctx); } @@ -87,6 +90,7 @@ public class ObjectDao { for (T object : objects) { PersistenceContext ctx = createCtx(object); ctx.setObject(object); + ctx.getObjectRef().lock(); this.objectFilter.update(object.getClass().getName(), ctx); } } @@ -97,6 +101,7 @@ public class ObjectDao { assertNotNull(object); PersistenceContext ctx = createCtx(object); ctx.setObject(object); + ctx.getObjectRef().lock(); this.objectFilter.remove(object.getClass().getName(), ctx); } @@ -107,6 +112,7 @@ public class ObjectDao { for (T object : objects) { PersistenceContext ctx = createCtx(object); ctx.setObject(object); + ctx.getObjectRef().lock(); this.objectFilter.remove(object.getClass().getName(), ctx); } } @@ -116,6 +122,7 @@ public class ObjectDao { assertNotClosed(); assertIsIdRef(objectRef); PersistenceContext ctx = createCtx(objectRef); + ctx.getObjectRef().lock(); this.objectFilter.remove(objectRef.getType(), ctx); } @@ -124,60 +131,99 @@ public class ObjectDao { assertIsNotIdRef(parentRef); assertIsNotRootRef(parentRef); - Set keySet = queryKeySet(parentRef); - for (String id : keySet) { + parentRef.lock(); + try { - ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); - PersistenceContext ctx = createCtx(childRef); - this.objectFilter.remove(childRef.getType(), ctx); + Set keySet = queryKeySet(parentRef); + for (String id : keySet) { + + ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); + PersistenceContext ctx = createCtx(childRef); + ctx.getObjectRef().lock(); + this.objectFilter.remove(childRef.getType(), ctx); + } + } finally { + parentRef.unlock(); } } public T queryById(ObjectRef objectRef) { assertNotClosed(); assertIsIdRef(objectRef); - PersistenceContext ctx = objectRef. createPersistenceContext(this.tx); - this.fileDao.performRead(ctx); - return ctx.getObject(); + + objectRef.lock(); + try { + PersistenceContext ctx = objectRef. createPersistenceContext(this.tx); + ctx.getObjectRef().lock(); + try { + this.fileDao.performRead(ctx); + return ctx.getObject(); + } finally { + ctx.getObjectRef().unlock(); + } + } finally { + objectRef.unlock(); + } } public List queryAll(ObjectRef parentRef) { assertNotClosed(); assertIsNotIdRef(parentRef); - MetadataDao metadataDao = this.tx.getMetadataDao(); - Set keySet = metadataDao.queryKeySet(parentRef); + parentRef.lock(); + try { - List result = new ArrayList<>(); - for (String id : keySet) { + MetadataDao metadataDao = this.tx.getMetadataDao(); + Set keySet = metadataDao.queryKeySet(parentRef); - ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); - PersistenceContext childCtx = childRef.createPersistenceContext(this.tx); + List result = new ArrayList<>(); + for (String id : keySet) { - this.fileDao.performRead(childCtx); - assertObjectRead(childCtx); - result.add(childCtx.getObject()); + ObjectRef childRef = parentRef.getChildIdRef(this.tx, id); + PersistenceContext childCtx = childRef.createPersistenceContext(this.tx); + childCtx.getObjectRef().lock(); + try { + this.fileDao.performRead(childCtx); + assertObjectRead(childCtx); + result.add(childCtx.getObject()); + } finally { + childCtx.getObjectRef().unlock(); + } + } + + return result; + + } finally { + parentRef.unlock(); } - - return result; } public Set queryKeySet(ObjectRef parentRef) { assertNotClosed(); assertIsNotIdRef(parentRef); - MetadataDao metadataDao = this.tx.getMetadataDao(); - Set keySet = metadataDao.queryKeySet(parentRef); - return keySet; + parentRef.lock(); + try { + MetadataDao metadataDao = this.tx.getMetadataDao(); + Set keySet = metadataDao.queryKeySet(parentRef); + return keySet; + } finally { + parentRef.unlock(); + } } public long querySize(ObjectRef parentRef) { assertNotClosed(); assertIsNotIdRef(parentRef); - MetadataDao metadataDao = this.tx.getMetadataDao(); - long size = metadataDao.querySize(parentRef); - return size; + parentRef.lock(); + try { + MetadataDao metadataDao = this.tx.getMetadataDao(); + long size = metadataDao.querySize(parentRef); + return size; + } finally { + parentRef.unlock(); + } } private PersistenceContext createCtx(T object) { diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java index c06b54717..85ed328f9 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java @@ -33,4 +33,5 @@ public class PersistenceConstants { public static final String PROP_BASEPATH = PROP_PREFIX + "basePath"; public static final String PROP_DAO_FACTORY_CLASS = PROP_PREFIX + "daoFactoryClass"; public static final String PROP_XML_IO_MOD = PROP_PREFIX + "ioMode"; + public static final String PROP_XML_LOCK_TIME_MILLIS = PROP_PREFIX + "lockTimeSeconds"; } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java index 950f1dfeb..262efed02 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java @@ -22,6 +22,7 @@ package ch.eitchnet.xmlpers.impl; import java.io.File; +import java.lang.reflect.Field; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; @@ -37,6 +38,7 @@ import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; import ch.eitchnet.xmlpers.api.PersistenceManager; import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.objref.LockableObject; import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; /** @@ -60,12 +62,23 @@ public class DefaultPersistenceManager implements PersistenceManager { String context = DefaultPersistenceManager.class.getSimpleName(); - // get verbose flag + // get properties boolean verbose = PropertiesHelper.getPropertyBool(properties, context, PersistenceConstants.PROP_VERBOSE, Boolean.FALSE).booleanValue(); String ioModeS = PropertiesHelper.getProperty(properties, context, PersistenceConstants.PROP_XML_IO_MOD, IoMode.DOM.name()); IoMode ioMode = IoMode.valueOf(ioModeS); + long lockTime = PropertiesHelper.getPropertyLong(properties, context, + PersistenceConstants.PROP_XML_LOCK_TIME_MILLIS, 10000L); + + // set lock time on LockableObject + try { + Field lockTimeField = LockableObject.class.getDeclaredField("tryLockTime");//$NON-NLS-1$ + lockTimeField.setAccessible(true); + lockTimeField.setLong(null, lockTime); + } catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException("Failed to configure tryLockTime on LockableObject!", e); //$NON-NLS-1$ + } // validate base path validateBasePath(properties); diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index d9c524212..225085423 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -112,6 +112,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { throw new IllegalStateException("Transaction has already been committed!"); //$NON-NLS-1$ if (!this.closed) { + unlockObjectRefs(); this.closed = true; this.objectFilter.clearCache(); } @@ -124,13 +125,12 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { try { - Set keySet = this.objectFilter.keySet(); - if (this.verbose) { String msg = "Committing {0} operations in TX...";//$NON-NLS-1$ - logger.info(MessageFormat.format(msg, keySet.size())); + logger.info(MessageFormat.format(msg, this.objectFilter.sizeCache())); } + Set keySet = this.objectFilter.keySet(); for (String key : keySet) { List removed = this.objectFilter.getRemoved(key); @@ -191,11 +191,20 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { } finally { // clean up + unlockObjectRefs(); this.objectFilter.clearCache(); this.committed = true; } } + @SuppressWarnings("rawtypes") + private void unlockObjectRefs() { + List lockedObjects = this.objectFilter.getAll(PersistenceContext.class); + for (PersistenceContext lockedObject : lockedObjects) { + lockedObject.getObjectRef().unlock(); + } + } + @Override public boolean isOpen() { return !this.closed && !this.committed; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java index 250f3e488..2de5447e6 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java @@ -1,29 +1,56 @@ package ch.eitchnet.xmlpers.objref; -import java.util.concurrent.locks.Lock; +import java.text.MessageFormat; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; + public class LockableObject { - private final Lock lock; + private static final Logger logger = LoggerFactory.getLogger(LockableObject.class); + private static long tryLockTime = 10000L; + + private final ReentrantLock lock; public LockableObject() { - this.lock = new ReentrantLock(); + this.lock = new ReentrantLock(true); + } + + public static long getLockTime() { + return tryLockTime; + } + + public boolean isLockedByCurrentThread() { + return this.lock.isHeldByCurrentThread(); } /** - * @return - * @see java.util.concurrent.locks.Lock#tryLock() + * @see java.util.concurrent.locks.ReentrantLock#tryLock(long, TimeUnit) */ - public boolean tryLock() { - return this.lock.tryLock(); + public void lock() { + try { + + logger.info("locking " + this.toString()); //$NON-NLS-1$ + if (!this.lock.tryLock(tryLockTime, TimeUnit.MILLISECONDS)) { + String msg = "Failed to acquire lock after {0} for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, StringHelper.formatMillisecondsDuration(tryLockTime), this.toString()); + throw new XmlPersistenceException(msg); + } + } catch (InterruptedException e) { + throw new XmlPersistenceException("Thread interrupted: " + e.getMessage(), e); //$NON-NLS-1$ + } } /** - * - * @see java.util.concurrent.locks.Lock#unlock() + * @see java.util.concurrent.locks.ReentrantLock#unlock() */ public void unlock() { this.lock.unlock(); + logger.info("unlocking " + this.toString()); //$NON-NLS-1$ } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java index 5a47834d8..9b6086e84 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java @@ -3,6 +3,9 @@ package ch.eitchnet.xmlpers.test; import java.io.File; import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.PersistenceConstants; @@ -16,6 +19,8 @@ import ch.eitchnet.xmlpers.test.model.Resource; public abstract class AbstractPersistenceTest { + protected static final Logger logger = LoggerFactory.getLogger(AbstractPersistenceTest.class); + protected PersistenceManager persistenceManager; @SuppressWarnings("nls") diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java new file mode 100644 index 000000000..9793a66b2 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.objref.LockableObject; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class LockingTest extends AbstractPersistenceTest { + + private static final String BASE_PATH = "target/db/LockingTest/"; //$NON-NLS-1$ + + long waitForWorkersTime = LockableObject.getLockTime() + 2000L; + + @BeforeClass + public static void beforeClass() { + cleanPath(BASE_PATH); + } + + @Before + public void before() { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASE_PATH + IoMode.DOM.name()); + setup(properties); + } + + @Test + public void shouldLockObjects() throws InterruptedException { + + List workers = new ArrayList<>(5); + + for (int i = 0; i < 5; i++) { + + String workerName = "worker_" + i; //$NON-NLS-1$ + Worker worker = new Worker(workerName, workerName); + worker.start(); + workers.add(worker); + logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ + } + + int nrOfSuccess = runWorkers(workers); + + assertEquals("Only one thread should be able to perform the TX!", 5, nrOfSuccess); //$NON-NLS-1$ + } + + @Test + public void shouldFailIfLockNotAcquirable() throws InterruptedException { + + List workers = new ArrayList<>(5); + + for (int i = 0; i < 5; i++) { + + String workerName = "workerRes"; //$NON-NLS-1$ + Worker worker = new Worker(workerName, workerName); + worker.start(); + workers.add(worker); + logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ + } + + int nrOfSuccess = runWorkers(workers); + + assertEquals("Only one thread should be able to perform the TX!", 1, nrOfSuccess); //$NON-NLS-1$ + } + + private int runWorkers(List workers) throws InterruptedException { + + synchronized (this) { + this.notifyAll(); + } + + for (Worker worker : workers) { + worker.join(this.waitForWorkersTime + 2000L); + } + + int nrOfSuccess = 0; + for (Worker worker : workers) { + if (worker.isSuccess()) + nrOfSuccess++; + } + + return nrOfSuccess; + } + + public class Worker extends Thread { + + private boolean success; + private String resourceId; + + public Worker(String name, String resourceId) { + super(name); + this.resourceId = resourceId; + } + + public void run() { + + synchronized (LockingTest.this) { + try { + logger.info("Waiting for ok to work..."); //$NON-NLS-1$ + LockingTest.this.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + logger.info("Starting work..."); //$NON-NLS-1$ + try (PersistenceTransaction tx = LockingTest.this.persistenceManager.openTx()) { + + Resource resource = createResource(this.resourceId); + tx.getObjectDao().add(resource); + } + this.success = true; + + try { + Thread.sleep(LockingTest.this.waitForWorkersTime); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + logger.info("Work completed."); //$NON-NLS-1$ + } + + public boolean isSuccess() { + return this.success; + } + } +} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java index 238f11464..1f6dfc432 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -52,6 +52,10 @@ public class ModelBuilder { public static Resource createResource() { return createResource(RES_ID, RES_NAME, RES_TYPE); } + + public static Resource createResource(String id) { + return createResource(id, RES_NAME, RES_TYPE); + } public static Resource createResource(String id, String name, String type) { Resource resource = new Resource(id, name, type); From f24a10992c40a4f8325d9be05c08ab927c258493 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 21 Oct 2013 18:48:23 +0200 Subject: [PATCH 195/457] [New] added test to check acquiring locks Added a test which checks that a TX fails if a lock can not be acquired, but succeeds before the timeout to acquire the lock is exceeded. Further fixed the brittle tests by using a different semaphore to synchronize the threads. --- .../xmlpers/api/PersistenceConstants.java | 2 +- .../impl/DefaultPersistenceManager.java | 4 +- .../ch/eitchnet/xmlpers/test/LockingTest.java | 137 ++++++++++++++---- 3 files changed, 110 insertions(+), 33 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java index 85ed328f9..482389e60 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java @@ -33,5 +33,5 @@ public class PersistenceConstants { public static final String PROP_BASEPATH = PROP_PREFIX + "basePath"; public static final String PROP_DAO_FACTORY_CLASS = PROP_PREFIX + "daoFactoryClass"; public static final String PROP_XML_IO_MOD = PROP_PREFIX + "ioMode"; - public static final String PROP_XML_LOCK_TIME_MILLIS = PROP_PREFIX + "lockTimeSeconds"; + public static final String PROP_LOCK_TIME_MILLIS = PROP_PREFIX + "lockTimeSeconds"; } diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java index 262efed02..21f2ee04b 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java @@ -32,6 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.PropertiesHelper; +import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.PersistenceConstants; import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; @@ -69,13 +70,14 @@ public class DefaultPersistenceManager implements PersistenceManager { IoMode.DOM.name()); IoMode ioMode = IoMode.valueOf(ioModeS); long lockTime = PropertiesHelper.getPropertyLong(properties, context, - PersistenceConstants.PROP_XML_LOCK_TIME_MILLIS, 10000L); + PersistenceConstants.PROP_LOCK_TIME_MILLIS, 10000L); // set lock time on LockableObject try { Field lockTimeField = LockableObject.class.getDeclaredField("tryLockTime");//$NON-NLS-1$ lockTimeField.setAccessible(true); lockTimeField.setLong(null, lockTime); + logger.info("Using a max lock acquire time of " + StringHelper.formatMillisecondsDuration(lockTime)); //$NON-NLS-1$ } catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException("Failed to configure tryLockTime on LockableObject!", e); //$NON-NLS-1$ } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java index 9793a66b2..f70ab8a21 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -21,8 +21,12 @@ */ package ch.eitchnet.xmlpers.test; +import static ch.eitchnet.xmlpers.test.impl.TestConstants.TYPE_RES; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import java.util.ArrayList; import java.util.List; @@ -35,6 +39,7 @@ import org.junit.Test; import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.PersistenceConstants; import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; import ch.eitchnet.xmlpers.objref.LockableObject; import ch.eitchnet.xmlpers.test.model.Resource; @@ -46,7 +51,8 @@ public class LockingTest extends AbstractPersistenceTest { private static final String BASE_PATH = "target/db/LockingTest/"; //$NON-NLS-1$ - long waitForWorkersTime = LockableObject.getLockTime() + 2000L; + private long waitForWorkersTime; + private boolean run; @BeforeClass public static void beforeClass() { @@ -57,18 +63,21 @@ public class LockingTest extends AbstractPersistenceTest { public void before() { Properties properties = new Properties(); properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASE_PATH + IoMode.DOM.name()); + properties.setProperty(PersistenceConstants.PROP_LOCK_TIME_MILLIS, Long.toString(500L)); setup(properties); + + this.waitForWorkersTime = LockableObject.getLockTime() + (long) ((double) this.getWaitForWorkersTime() * .2); } @Test public void shouldLockObjects() throws InterruptedException { - List workers = new ArrayList<>(5); + List workers = new ArrayList<>(5); + String resoureId = "worker"; //$NON-NLS-1$ for (int i = 0; i < 5; i++) { - - String workerName = "worker_" + i; //$NON-NLS-1$ - Worker worker = new Worker(workerName, workerName); + String workerName = resoureId + "_" + i; //$NON-NLS-1$ + CreateResourceWorker worker = new CreateResourceWorker(workerName, workerName); worker.start(); workers.add(worker); logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ @@ -80,14 +89,13 @@ public class LockingTest extends AbstractPersistenceTest { } @Test - public void shouldFailIfLockNotAcquirable() throws InterruptedException { + public void shouldFailIfResourceAlreadyExists() throws InterruptedException { - List workers = new ArrayList<>(5); + List workers = new ArrayList<>(5); + String resourceId = "createWorkerRes"; //$NON-NLS-1$ for (int i = 0; i < 5; i++) { - - String workerName = "workerRes"; //$NON-NLS-1$ - Worker worker = new Worker(workerName, workerName); + CreateResourceWorker worker = new CreateResourceWorker(resourceId, resourceId); worker.start(); workers.add(worker); logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ @@ -98,18 +106,41 @@ public class LockingTest extends AbstractPersistenceTest { assertEquals("Only one thread should be able to perform the TX!", 1, nrOfSuccess); //$NON-NLS-1$ } - private int runWorkers(List workers) throws InterruptedException { + @Test + public void shouldFailUpdateIfLockNotAcquirable() throws InterruptedException { - synchronized (this) { - this.notifyAll(); + // prepare workers + List workers = new ArrayList<>(5); + String resourceId = "updatWorkerRes"; //$NON-NLS-1$ + for (int i = 0; i < 5; i++) { + String workerName = resourceId + "_" + i; //$NON-NLS-1$ + UpdateResourceWorker worker = new UpdateResourceWorker(workerName, resourceId); + worker.start(); + workers.add(worker); + logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ } - for (Worker worker : workers) { - worker.join(this.waitForWorkersTime + 2000L); + // create resource which is to be updated + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + Resource resource = createResource(resourceId); + tx.getObjectDao().add(resource); + } + + int nrOfSuccess = runWorkers(workers); + + assertEquals("Only one thread should be able to perform the TX!", 1, nrOfSuccess); //$NON-NLS-1$ + } + + private int runWorkers(List workers) throws InterruptedException { + + this.setRun(true); + + for (AbstractWorker worker : workers) { + worker.join(this.getWaitForWorkersTime() + 2000L); } int nrOfSuccess = 0; - for (Worker worker : workers) { + for (AbstractWorker worker : workers) { if (worker.isSuccess()) nrOfSuccess++; } @@ -117,22 +148,34 @@ public class LockingTest extends AbstractPersistenceTest { return nrOfSuccess; } - public class Worker extends Thread { + public long getWaitForWorkersTime() { + return this.waitForWorkersTime; + } - private boolean success; - private String resourceId; + public boolean isRun() { + return this.run; + } - public Worker(String name, String resourceId) { + public void setRun(boolean run) { + this.run = run; + } + + public abstract class AbstractWorker extends Thread { + + protected boolean success; + protected String resourceId; + + public AbstractWorker(String name, String resourceId) { super(name); this.resourceId = resourceId; } public void run() { - synchronized (LockingTest.this) { + logger.info("Waiting for ok to work..."); //$NON-NLS-1$ + while (!LockingTest.this.isRun()) { try { - logger.info("Waiting for ok to work..."); //$NON-NLS-1$ - LockingTest.this.wait(); + Thread.sleep(10L); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -140,23 +183,55 @@ public class LockingTest extends AbstractPersistenceTest { logger.info("Starting work..."); //$NON-NLS-1$ try (PersistenceTransaction tx = LockingTest.this.persistenceManager.openTx()) { + doWork(tx); - Resource resource = createResource(this.resourceId); - tx.getObjectDao().add(resource); - } - this.success = true; + try { + Thread.sleep(getWaitForWorkersTime()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } - try { - Thread.sleep(LockingTest.this.waitForWorkersTime); - } catch (InterruptedException e) { - throw new RuntimeException(e); + this.success = true; } logger.info("Work completed."); //$NON-NLS-1$ } + protected abstract void doWork(PersistenceTransaction tx); + public boolean isSuccess() { return this.success; } } + + public class CreateResourceWorker extends AbstractWorker { + + public CreateResourceWorker(String name, String resourceId) { + super(name, resourceId); + } + + @Override + protected void doWork(PersistenceTransaction tx) { + Resource resource = createResource(this.resourceId); + tx.getObjectDao().add(resource); + } + } + + public class UpdateResourceWorker extends AbstractWorker { + + public UpdateResourceWorker(String name, String resourceId) { + super(name, resourceId); + } + + @Override + protected void doWork(PersistenceTransaction tx) { + + IdOfSubTypeRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, RES_TYPE, this.resourceId); + Resource resource = tx.getObjectDao().queryById(objectRef); + assertNotNull(resource); + updateResource(resource); + + tx.getObjectDao().update(resource); + } + } } From 5df4b9edc14342a76e87f5ae7c504f1b5f1a0d68 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 24 Oct 2013 21:43:06 +0200 Subject: [PATCH 196/457] [New] added MathHelper for precision assertions --- .../ch/eitchnet/utils/helper/MathHelper.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/helper/MathHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/MathHelper.java b/src/main/java/ch/eitchnet/utils/helper/MathHelper.java new file mode 100644 index 000000000..6f46850b2 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/MathHelper.java @@ -0,0 +1,121 @@ +/* + * Created on 05.01.2004 + */ + +/* + * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 + * Olten + * + * All rights reserved. + * + */ + +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 msmock, gattom + */ +public class MathHelper { + + public static final double PRECISION = 1.0E08; + public static final int PRECISION_DIGITS = 3; + + /** + * Check if the two values are equal with respect to the precision + * + * @param firstValue + * the first value to compare + * @param secondValue + * the second value to compare to + * @return boolean True, if the two values are equal under the set precision. Fales, otherwise. + */ + public static boolean isEqualPrecision(double firstValue, double secondValue) { + + return (java.lang.Math.abs(firstValue - secondValue) < (1.0d / PRECISION)); + } + + /** + * Comparison between the two values. Given the precision, this function determines if the given value is smaller + * than the given bound. + *

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

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

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

    + * + *

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

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

    + * + * @param value + * the double value to round + * @param decimals + * number of decimals + * + * @return the rounded number + */ + public static double toPrecision(double value, int decimals) { + if (value == 0.0) + return 0.0; + if (Double.isNaN(value)) + return Double.NaN; + if (value == Double.NEGATIVE_INFINITY) + return Double.NEGATIVE_INFINITY; + if (value == Double.POSITIVE_INFINITY) + return Double.POSITIVE_INFINITY; + return new BigDecimal(value).setScale(decimals, RoundingMode.HALF_EVEN).doubleValue(); + } + + /** + * Returns the value with the precision where precision is set to {@link #PRECISION_DIGITS} + * + * @see #toPrecision(double, int) + */ + public static double toPrecision(double value) { + return toPrecision(value, PRECISION_DIGITS); + } +} From 2ccccd6b9ed0ef7543fc3ba098b3a83677da5ee2 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 24 Oct 2013 21:43:16 +0200 Subject: [PATCH 197/457] [New] added IS8601 formatting helpers --- .../ch/eitchnet/utils/iso8601/DateFormat.java | 34 +++ .../utils/iso8601/DurationFormat.java | 34 +++ .../eitchnet/utils/iso8601/FormatFactory.java | 85 ++++++ .../ch/eitchnet/utils/iso8601/ISO8601.java | 264 ++++++++++++++++++ .../utils/iso8601/ISO8601Duration.java | 256 +++++++++++++++++ .../utils/iso8601/ISO8601FormatFactory.java | 72 +++++ .../utils/iso8601/ISO8601Worktime.java | 233 ++++++++++++++++ .../utils/iso8601/WorktimeFormat.java | 34 +++ 8 files changed, 1012 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java create mode 100644 src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java create mode 100644 src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java create mode 100644 src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java create mode 100644 src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java create mode 100644 src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java create mode 100644 src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java create mode 100644 src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java new file mode 100644 index 000000000..d7d918dea --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 + * Olten + * + * All rights reserved. + * + */ + +package ch.eitchnet.utils.iso8601; + +/** + * interface for all date formats internally used by rsp applications + * + * @author msmock + */ +public interface DateFormat { + + /** + * format a long to string + * + * @param l + * @return the formatted string of the long value + */ + public String format(long l); + + /** + * parse a string to long + * + * @param s + * @return the value parsed + */ + public long parse(String s); + +} diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java new file mode 100644 index 000000000..d7d7c6f00 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 + * Olten + * + * All rights reserved. + * + */ + +package ch.eitchnet.utils.iso8601; + +/** + * interface for all duration formats internally used by the platform + * + * @author msmock + */ +public interface DurationFormat { + + /** + * format a long to string + * + * @param l + * @return formatted string if the long argument + */ + public String format(long l); + + /** + * parse a string to long + * + * @param s + * @return the long value parsed + */ + public long parse(String s); + +} diff --git a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java new file mode 100644 index 000000000..99d43ee76 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java @@ -0,0 +1,85 @@ +package ch.eitchnet.utils.iso8601; + +/** + * 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 + * + * @author msmock + */ +public interface FormatFactory { + + /** + * return the formatter for dates + * + * @return RSPDurationFormat + */ + public DateFormat getDateFormat(); + + /** + * return the formatter for durations + * + * @return RSPDurationFormat + */ + public DurationFormat getDurationFormat(); + + /** + * return the formatter for work time + * + * @return RSPWorktimeFormat + */ + public WorktimeFormat getWorktimeFormat(); + + /** + * the date format used in xml import and export + * + * @return RSPDateFormat + */ + public DateFormat getXmlDateFormat(); + + /** + * the duration format used in xml import and export + * + * @return RSPDurationFormat + */ + 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(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); +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java new file mode 100644 index 000000000..496b49e96 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java @@ -0,0 +1,264 @@ +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.apache.log4j.Logger; + +/** + * + */ +@SuppressWarnings("nls") +public class ISO8601 implements DateFormat { + + private static final Logger logger = Logger.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) { + + if (text == null) { + throw new IllegalArgumentException("argument can not be null"); + } + + // 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(); + } + + /** + * 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, e); + return null; + } + } + + /** + * parse ISO8601 date to long + * + * @param s + * the string to parse + * @return time point as long + * @throws NumberFormatException + */ + @Override + public long parse(String s) { + + if (s.equals("-")) + return Long.MAX_VALUE; + + Calendar cal = parseToCalendar(s); + if (cal != null) { + return cal.getTime().getTime(); + } + + String msg = "Input string " + s + " cannot be parsed to date."; + throw new NumberFormatException(msg); + } +} diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java new file mode 100644 index 000000000..810b7b34d --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 + * Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.utils.iso8601; + +/** + *

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

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

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

    + *

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

    + * + * @author msmock + * @author gattom (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; + } else + 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; + } else + 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; + } else { + 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.length() < 3) + throw new NumberFormatException(s + " cannot be parsed to ISO 8601 Duration"); + + char p = s.charAt(0); + + // the first char must be a P for ISO8601 duration + if (p != 'P') + throw new NumberFormatException(s + " cannot be parsed to ISO 8601 Duration"); + + int newposition = 1; + do { + if (s.charAt(newposition) == 'T') { + // skip the separator specifying where the time starts. + newposition++; + } + + // read the string representing the numeric value + String val = parseNumber(newposition, s); + double numVal = Double.parseDouble(val); + newposition += val.length(); + + // get the time unit + TimeDuration unit = TimeDuration.getTimeDurationFor(s, newposition); + + // skip the time duration character + newposition++; + + // increment the value. + newResult += unit.duration() * numVal; + + } while (newposition < s.length()); + + return newResult; + } + + /** + * Return the substring of s starting at index i (in s) that contains a numeric string. + * + * @param index + * The start index in string s + * @param s + * The string to analyze + * @return the substring containing the numeric portion of s starting at index i. + */ + private String parseNumber(int index, String s) { + int i = index; + int start = i; + while (i < s.length()) { + if (!isNumber(s.charAt(i))) + break; + i++; + } + String substring = s.substring(start, i); + return substring; + } + + /** + * Format the given time duration unit into the string buffer. This function displays the given duration in units of + * the given unit, and returns the remainder. + *

    + * Thus, a duration of 86401000 (one day and one second) will add the representation of one day if unit is DAY (1D) + * and return 1000 as the remainder with respect of this unit. If the given unit is HOUR, then this function adds + * 24H to the {@link StringBuilder}, and returns 1000 as the remainder. + * + * @param sb + * The {@link StringBuilder} to add the given duration with the right unit + * @param duration + * The duration to add + * @param unit + * The unit of this duration + * @return The remainder of the given duration, modulo the time unit. + */ + private long formatTimeDuration(StringBuilder sb, long duration, TimeDuration unit) { + + long remainder = duration; + if (unit.equals(TimeDuration.SECOND) || remainder >= unit.duration()) { + + long quantity = remainder / unit.duration(); + remainder = remainder % unit.duration(); + sb.append(quantity); + + if (unit.equals(TimeDuration.SECOND)) { + long millis = remainder; + if (millis == 0) { + // to not have the decimal point + } else if (millis > 99) { + sb.append("." + millis); + } else if (millis > 9) { + sb.append(".0" + millis); + } else { + sb.append(".00" + millis); + } + } + + sb.append(unit.isoChar); + } + + return remainder; + } + + /** + * Formats the given time duration to a pseudo ISO 8601 duration string + * + * @param duration + * @return String the duration formatted as a ISO8601 duration string + */ + @Override + public String format(long duration) { + + // XXX this is a preliminary help to solve the situation where this method sometimes returns P + if (duration < 0l) + throw new RuntimeException("A duration can not be negative!"); + + if (duration == 0l) + return "PT0S"; + + StringBuilder sb = new StringBuilder(); + sb.append('P'); + + long remainder = formatTimeDuration(sb, duration, TimeDuration.YEAR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MONTH); + remainder = formatTimeDuration(sb, remainder, TimeDuration.DAY); + if (remainder > 0) { + sb.append('T'); + remainder = formatTimeDuration(sb, remainder, TimeDuration.HOUR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MINUTE); + remainder = formatTimeDuration(sb, remainder, TimeDuration.SECOND); + } + return sb.toString(); + } + +} diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java new file mode 100644 index 000000000..1c1fed122 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java @@ -0,0 +1,72 @@ +package ch.eitchnet.utils.iso8601; + +import ch.eitchnet.utils.helper.MathHelper; + +/** + * Default factory for date formats used for serialization. + * + * @author msmock + */ +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(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)); + } +} diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java new file mode 100644 index 000000000..ffd0c6c08 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 + * Olten + * + * All rights reserved. + * + */ +package ch.eitchnet.utils.iso8601; + +/** + *

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

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

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

    + *

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

    + * + * @author msmock + * @author gattom (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; + } else + 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; + } else + 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; + + } else { + throw new NumberFormatException(s + " cannot be parsed to ISO 8601 Duration"); + } + + } + + /** + * Return the substring of s starting at index i (in s) that contains a numeric string. + * + * @param index + * The start index in string s + * @param s + * The string to analyze + * @return the substring containing the numeric portion of s starting at index i. + */ + private String parseNumber(int index, String s) { + int i = index; + int start = i; + while (i < s.length()) { + if (!isNumber(s.charAt(i))) + break; + i++; + } + String substring = s.substring(start, i); + return substring; + } + + /** + * Format the given time duration unit into the string buffer. This function displays the given duration in units of + * the given unit, and returns the remainder. + *

    + * Thus, a duration of 86401000 (one day and one second) will add the representation of one day if unit is DAY (1D) + * and return 1000 as the remainder with respect of this unit. If the given unit is HOUR, then this function adds + * 24H to the {@link StringBuilder}, and returns 1000 as the remainder. + * + * @param sb + * The {@link StringBuilder} to add the given duration with the right unit + * @param duration + * The duration to add + * @param unit + * The unit of this duration + * @return The remainder of the given duration, modulo the time unit. + */ + private long formatTimeDuration(StringBuilder sb, long duration, TimeDuration unit) { + + long remainder = duration; + + if (unit.equals(TimeDuration.SECOND) || remainder >= unit.duration()) { + + long quantity = remainder / unit.duration(); + remainder = remainder % unit.duration(); + sb.append(quantity); + + if (unit.equals(TimeDuration.SECOND)) { + + long millis = remainder; + if (millis == 0) { + // to not have the decimal point + } else if (millis > 99) { + sb.append("." + millis); + } else if (millis > 9) { + sb.append(".0" + millis); + } else { + sb.append(".00" + millis); + } + } + + sb.append(unit.isoChar); + } + return remainder; + } + + /** + * Formats the given time duration to a pseudo ISO 8601 duration string + * + * @param duration + * @return String the duration formatted as a ISO8601 duration string + */ + @Override + public String format(long duration) { + + if (duration == 0) + return "PT0S"; + + StringBuilder sb = new StringBuilder(); + sb.append('P'); + sb.append('T'); + long remainder = formatTimeDuration(sb, duration, TimeDuration.HOUR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MINUTE); + remainder = formatTimeDuration(sb, remainder, TimeDuration.SECOND); + + return sb.toString(); + } + +} diff --git a/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java new file mode 100644 index 000000000..058a5f7c3 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 + * Olten + * + * All rights reserved. + * + */ + +package ch.eitchnet.utils.iso8601; + +/** + * interface for the worktime format + * + * @author msmock + */ +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); + +} From 59635eddf195875361102180495d1d31939f7a4e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 24 Oct 2013 21:45:11 +0200 Subject: [PATCH 198/457] [Minor] modified how objects are added to the ObjectFilter Now objects are not added by their class names, but the type is retrieved from the object reference --- src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java | 12 ++++++------ .../api/PersistenceContextFactoryDelegator.java | 2 -- .../ch/eitchnet/xmlpers/test/impl/TestConstants.java | 7 ++----- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index c884db65f..aba2fc7b5 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -58,7 +58,7 @@ public class ObjectDao { PersistenceContext ctx = createCtx(object); ctx.setObject(object); ctx.getObjectRef().lock(); - this.objectFilter.add(object.getClass().getName(), ctx); + this.objectFilter.add(ctx.getObjectRef().getType(), ctx); } public void addAll(List objects) { @@ -69,7 +69,7 @@ public class ObjectDao { PersistenceContext ctx = createCtx(object); ctx.setObject(object); ctx.getObjectRef().lock(); - this.objectFilter.add(object.getClass().getName(), ctx); + this.objectFilter.add(ctx.getObjectRef().getType(), ctx); } } } @@ -80,7 +80,7 @@ public class ObjectDao { PersistenceContext ctx = createCtx(object); ctx.setObject(object); ctx.getObjectRef().lock(); - this.objectFilter.update(object.getClass().getName(), ctx); + this.objectFilter.update(ctx.getObjectRef().getType(), ctx); } public void updateAll(List objects) { @@ -91,7 +91,7 @@ public class ObjectDao { PersistenceContext ctx = createCtx(object); ctx.setObject(object); ctx.getObjectRef().lock(); - this.objectFilter.update(object.getClass().getName(), ctx); + this.objectFilter.update(ctx.getObjectRef().getType(), ctx); } } } @@ -102,7 +102,7 @@ public class ObjectDao { PersistenceContext ctx = createCtx(object); ctx.setObject(object); ctx.getObjectRef().lock(); - this.objectFilter.remove(object.getClass().getName(), ctx); + this.objectFilter.remove(ctx.getObjectRef().getType(), ctx); } public void removeAll(List objects) { @@ -113,7 +113,7 @@ public class ObjectDao { PersistenceContext ctx = createCtx(object); ctx.setObject(object); ctx.getObjectRef().lock(); - this.objectFilter.remove(object.getClass().getName(), ctx); + this.objectFilter.remove(ctx.getObjectRef().getType(), ctx); } } } diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java index bddabd33c..da0a8056e 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java @@ -44,8 +44,6 @@ public class PersistenceContextFactoryDelegator { this.contextFactoryCacheByClass.put(classType, ctxFactory); this.contextFactoryCacheByType.put(type, ctxFactory); - if (!classType.getName().equals(type)) - this.contextFactoryCacheByType.put(classType.getName(), ctxFactory); } public PersistenceContextFactory getCtxFactory(Class classType) { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java index 1ac98b08c..18ec8cafd 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java @@ -21,15 +21,12 @@ */ package ch.eitchnet.xmlpers.test.impl; -import ch.eitchnet.xmlpers.test.model.Book; -import ch.eitchnet.xmlpers.test.model.Resource; - /** * @author Robert von Burg * */ public class TestConstants { - public static final String TYPE_RES = Resource.class.getSimpleName(); - public static final String TYPE_BOOK = Book.class.getSimpleName(); + public static final String TYPE_RES = "Resource"; //$NON-NLS-1$ + public static final String TYPE_BOOK = "Book"; //$NON-NLS-1$ } From 459a8c096bcdd530eceaa13366f42a4cfc677d96 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 24 Oct 2013 21:53:51 +0200 Subject: [PATCH 199/457] [Minor] removed dependency to log4j - using slf4j --- src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java index 496b49e96..30be5b495 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java @@ -6,7 +6,8 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @@ -14,7 +15,7 @@ import org.apache.log4j.Logger; @SuppressWarnings("nls") public class ISO8601 implements DateFormat { - private static final Logger logger = Logger.getLogger(ISO8601.class); + private static final Logger logger = LoggerFactory.getLogger(ISO8601.class); /** * misc. numeric formats used in formatting @@ -234,7 +235,7 @@ public class ISO8601 implements DateFormat { cal.setTime(date); return format(cal); } catch (Exception e) { - logger.error(e, e); + logger.error(e.getMessage(), e); return null; } } From bac94f6075afa4858f73929ecce0e533be96511a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 27 Oct 2013 23:06:19 +0100 Subject: [PATCH 200/457] [Minor] set parent to 0.1.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bd92a91a2..110c03a32 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 0.0.1-SNAPSHOT + 0.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml From ef256706e48fe5f22104b9925e32914914d825de Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 27 Oct 2013 23:06:30 +0100 Subject: [PATCH 201/457] [Minor] set parent to 0.1.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 03a84e647..143e056af 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 0.0.1-SNAPSHOT + 0.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml From 9ea4929497e51f93dd2e67c81c045b382377fef0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 27 Oct 2013 23:06:35 +0100 Subject: [PATCH 202/457] [Minor] set parent to 0.1.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a865ef0ea..cb6be517c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 0.0.1-SNAPSHOT + 0.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml From 549d8fb8a7ca84aad0ac2795ec4dca8bf02d46a3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 15:40:21 +0100 Subject: [PATCH 203/457] Update LockableObject.java --- src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java index 2de5447e6..af499f317 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java @@ -35,12 +35,12 @@ public class LockableObject { public void lock() { try { - logger.info("locking " + this.toString()); //$NON-NLS-1$ if (!this.lock.tryLock(tryLockTime, TimeUnit.MILLISECONDS)) { String msg = "Failed to acquire lock after {0} for {1}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, StringHelper.formatMillisecondsDuration(tryLockTime), this.toString()); throw new XmlPersistenceException(msg); } + logger.info("locked " + this.toString()); //$NON-NLS-1$ } catch (InterruptedException e) { throw new XmlPersistenceException("Thread interrupted: " + e.getMessage(), e); //$NON-NLS-1$ } From ff5304a1a025631e92e7dbdadb41d9040e878146 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 15:46:08 +0100 Subject: [PATCH 204/457] Update LockingTest.java --- src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java index f70ab8a21..69ee0d3f7 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -95,7 +95,8 @@ public class LockingTest extends AbstractPersistenceTest { String resourceId = "createWorkerRes"; //$NON-NLS-1$ for (int i = 0; i < 5; i++) { - CreateResourceWorker worker = new CreateResourceWorker(resourceId, resourceId); + String workerName = resourceId + "_" + i; + CreateResourceWorker worker = new CreateResourceWorker(workerName, resourceId); worker.start(); workers.add(worker); logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ From c69ebe8e245f61fdc2575b7f4c6121abb17c4532 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 15:49:48 +0100 Subject: [PATCH 205/457] Update LockingTest.java --- src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java index 69ee0d3f7..4351a7c47 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -66,7 +66,7 @@ public class LockingTest extends AbstractPersistenceTest { properties.setProperty(PersistenceConstants.PROP_LOCK_TIME_MILLIS, Long.toString(500L)); setup(properties); - this.waitForWorkersTime = LockableObject.getLockTime() + (long) ((double) this.getWaitForWorkersTime() * .2); + this.waitForWorkersTime = LockableObject.getLockTime() + this.getWaitForWorkersTime() + 300L; } @Test From 33d7fb2e3367eba058dd1062eaed1b3fc14ec5f4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 18:56:38 +0100 Subject: [PATCH 206/457] [Minor] cleaned up .gitignore --- .gitignore | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 8d6b8f627..2945707a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,4 @@ -bin -dist - -# Maven target target/ - -# Eclipse settings .classpath .project -.settings/ - +.settings/ \ No newline at end of file From 60e75219bb59e2d672b07e8aa927c676fec85052 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 18:56:44 +0100 Subject: [PATCH 207/457] [Minor] cleaned up .gitignore --- .gitignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitignore b/.gitignore index 76c4111c6..18d2ca6cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,4 @@ -*.class - -tmp/ - -# Maven target target/ - -# Eclipse settings .classpath .project .settings/ From a5914793791347ba44c733d80c64cae7477daab9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 18:56:51 +0100 Subject: [PATCH 208/457] [Minor] cleaned up .gitignore --- .gitignore | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.gitignore b/.gitignore index 3487fb9f8..18d2ca6cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,4 @@ -*.class - -# Maven target target/ - -# Project files -tmp/ -logs/ - -# Eclipse settings .classpath .project .settings/ - -# External libraries -lib/ From f2fd1233d961eb9cddfa2a40f589d215f52476f3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 22:00:33 +0100 Subject: [PATCH 209/457] [Minor] renamed validateIsPrivilegeAdmin to assertIsPrivilegeAdmin --- .../handler/DefaultPrivilegeHandler.java | 30 +++++++++---------- .../privilege/handler/PrivilegeHandler.java | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 8f1a7aeb2..31014d2a5 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -55,7 +55,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * The following list describes implementation details: *

      *
    • any methods which change the model are first validated by checking if the certificate is for an admin user by - * calling {@link #validateIsPrivilegeAdmin(Certificate)}
    • + * calling {@link #assertIsPrivilegeAdmin(Certificate)} *
    • all model requests are delegated to the configured {@link PrivilegeHandler}, except for the session id to * {@link Certificate} map, no model data is kept in this implementation. This also means that to return the * representation objects, for every new model query, a new representation object is created
    • @@ -273,7 +273,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // create new role from RoleRep Role role = new Role(roleRep); @@ -294,7 +294,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { try { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); String passwordHash = null; if (password != null) { @@ -323,7 +323,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // validate PrivilegeRep privilegeRep.validate(); @@ -363,7 +363,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void addRoleToUser(Certificate certificate, String username, String roleName) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get user User user = this.persistenceHandler.getUser(username); @@ -398,7 +398,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get role Role role = this.persistenceHandler.getRole(roleName); @@ -430,7 +430,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public RoleRep removeRole(Certificate certificate, String roleName) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // delegate role removal to persistence handler Role removedRole = this.persistenceHandler.removeRole(roleName); @@ -446,7 +446,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void removeRoleFromUser(Certificate certificate, String username, String roleName) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get User User user = this.persistenceHandler.getUser(username); @@ -475,7 +475,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep removeUser(Certificate certificate, String username) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // delegate user removal to persistence handler User removedUser = this.persistenceHandler.removeUser(username); @@ -493,7 +493,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void setUserLocale(Certificate certificate, String username, Locale locale) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get User User user = this.persistenceHandler.getUser(username); @@ -513,7 +513,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void setUserName(Certificate certificate, String username, String firstname, String surname) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get User User user = this.persistenceHandler.getUser(username); @@ -546,7 +546,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } else { // otherwise validate the the certificate is for a privilege admin - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); } // get User @@ -586,7 +586,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void setUserState(Certificate certificate, String username, UserState state) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get User User user = this.persistenceHandler.getUser(username); @@ -785,7 +785,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { + public void assertIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { // validate certificate isCertificateValid(certificate); @@ -826,7 +826,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public boolean persist(Certificate certificate) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); return this.persistenceHandler.persist(); } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 5c0e397bd..eac3b4422 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -389,7 +389,7 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user does not not have admin privileges */ - public void validateIsPrivilegeAdmin(Certificate certificate) throws AccessDeniedException; + public void assertIsPrivilegeAdmin(Certificate certificate) throws AccessDeniedException; /** * Validate that the given password meets certain requirements. What these requirements are is a decision made by From f35ba7b13c1720a2be76fe4e4e87b9ab4e3ab35a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 31 Oct 2013 00:06:51 +0100 Subject: [PATCH 210/457] [Minor] cleaned up XmlHelper --- .../ch/eitchnet/utils/helper/XmlHelper.java | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java index aa38e70f3..d866c002a 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -21,9 +21,9 @@ package ch.eitchnet.utils.helper; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.text.MessageFormat; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -57,17 +57,17 @@ 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"; + 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"; + 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"; + public static final String DEFAULT_ENCODING = "utf-8"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); @@ -79,11 +79,16 @@ public class XmlHelper { */ public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { - try { - XmlHelper.logger.info("Parsing XML document " + xmlFile.getAbsolutePath()); - parseDocument(new FileInputStream(xmlFile), xmlHandler); - } catch (FileNotFoundException e) { - throw new XmlException("The XML file could not be read: " + xmlFile.getAbsolutePath(), e); + 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); } } @@ -103,11 +108,11 @@ public class XmlHelper { sp.parse(xmlFileInputStream, xmlHandler); } catch (ParserConfigurationException e) { - throw new XmlException("Failed to initialize a SAX Parser: " + e.getMessage(), 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); + 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); + throw new XmlException("The XML stream not be read: " + e.getMessage(), e); //$NON-NLS-1$ } } @@ -124,30 +129,33 @@ public class XmlHelper { */ public static void writeDocument(Document document, File file) throws RuntimeException { - XmlHelper.logger.info("Exporting document element " + document.getNodeName() + " to " + file.getAbsolutePath()); + String msg = "Exporting document element {0} to {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, document.getNodeName(), file.getAbsolutePath()); + XmlHelper.logger.info(msg); String lineSep = System.getProperty(PROP_LINE_SEPARATOR); try { String encoding = document.getInputEncoding(); if (encoding == null || encoding.isEmpty()) { - XmlHelper.logger.info("No encoding passed. Using default encoding " + XmlHelper.DEFAULT_ENCODING); + XmlHelper.logger.info(MessageFormat.format( + "No encoding passed. Using default encoding {0}", XmlHelper.DEFAULT_ENCODING)); //$NON-NLS-1$ encoding = XmlHelper.DEFAULT_ENCODING; } - if (!lineSep.equals("\n")) { - XmlHelper.logger.info("Overriding line separator to \\n"); + 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"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + 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, encoding); - transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + 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 @@ -157,7 +165,7 @@ public class XmlHelper { } catch (Exception e) { - throw new XmlException("Exception while exporting to file: " + e, e); + throw new XmlException("Exception while exporting to file: " + e, e); //$NON-NLS-1$ } finally { @@ -203,9 +211,9 @@ public class XmlHelper { return document; } catch (DOMException e) { - throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), 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); + throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); //$NON-NLS-1$ } } } From 5047ad9ff0f4e2e4999ba1c93990242ca50d2ae1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 31 Oct 2013 00:41:50 +0100 Subject: [PATCH 211/457] [Major] cleaned up FileHelper by using Java 7 auto resource close - Also cleaned up all compiler warnings. - Added new method to copy a list of files to a destination --- .../ch/eitchnet/utils/helper/FileHelper.java | 175 +++++++----------- 1 file changed, 67 insertions(+), 108 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index a175dc4c5..389572504 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -32,6 +32,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -59,15 +60,13 @@ public class FileHelper { */ 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", + 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; - BufferedInputStream in = null; - try { - in = new BufferedInputStream(new FileInputStream(file)); + try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));) { byte[] bytes = new byte[8192]; int read; while ((read = in.read(bytes)) != -1) { @@ -75,17 +74,9 @@ public class FileHelper { pos += read; } } catch (FileNotFoundException e) { - throw new RuntimeException("Filed does not exist " + file.getAbsolutePath()); + throw new RuntimeException("Filed does not exist " + file.getAbsolutePath()); //$NON-NLS-1$ } catch (IOException e) { - throw new RuntimeException("Could not read file " + file.getAbsolutePath()); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - FileHelper.logger.error("Failed to close InputStream: " + e.getLocalizedMessage()); - } - } + throw new RuntimeException("Could not read file " + file.getAbsolutePath()); //$NON-NLS-1$ } return data; @@ -101,32 +92,22 @@ public class FileHelper { */ public static final String readFileToString(File file) { - BufferedReader bufferedReader = null; - try { + try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file));) { - bufferedReader = new BufferedReader(new FileReader(file)); StringBuilder sb = new StringBuilder(); String line; while ((line = bufferedReader.readLine()) != null) { - sb.append(line + "\n"); + sb.append(line + "\n"); //$NON-NLS-1$ } return sb.toString(); } catch (FileNotFoundException e) { - throw new RuntimeException("Filed does not exist " + file.getAbsolutePath()); + throw new RuntimeException("Filed does not exist " + file.getAbsolutePath()); //$NON-NLS-1$ } catch (IOException e) { - throw new RuntimeException("Could not read file " + file.getAbsolutePath()); - } finally { - if (bufferedReader != null) { - try { - bufferedReader.close(); - } catch (IOException e) { - FileHelper.logger.error("Failed to close BufferedReader: " + e.getLocalizedMessage()); - } - } + throw new RuntimeException("Could not read file " + file.getAbsolutePath()); //$NON-NLS-1$ } } @@ -140,24 +121,14 @@ public class FileHelper { */ public static final void writeToFile(byte[] bytes, File dstFile) { - BufferedOutputStream out = null; - try { + try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dstFile));) { - out = new BufferedOutputStream(new FileOutputStream(dstFile)); out.write(bytes); } catch (FileNotFoundException e) { - throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath()); + 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()); - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - FileHelper.logger.error("Failed to close OutputStream: " + e.getLocalizedMessage()); - } - } + throw new RuntimeException("Could not write to file " + dstFile.getAbsolutePath()); //$NON-NLS-1$ } } @@ -171,26 +142,15 @@ public class FileHelper { */ public static final void writeStringToFile(String string, File dstFile) { - BufferedWriter bufferedwriter = null; - try { + try (BufferedWriter bufferedwriter = new BufferedWriter(new FileWriter(dstFile));) { - bufferedwriter = new BufferedWriter(new FileWriter(dstFile)); bufferedwriter.write(string); - bufferedwriter.close(); } catch (FileNotFoundException e) { - throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath()); + 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()); - } finally { - if (bufferedwriter != null) { - try { - bufferedwriter.close(); - } catch (IOException e) { - FileHelper.logger.error("Failed to close BufferedWriter: " + e.getLocalizedMessage()); - } - } + throw new RuntimeException("Could not write to file " + dstFile.getAbsolutePath()); //$NON-NLS-1$ } } @@ -223,31 +183,60 @@ public class FileHelper { boolean done = FileHelper.deleteFiles(file.listFiles(), log); if (!done) { worked = false; - FileHelper.logger.warn("Could not empty the directory: " + file.getAbsolutePath()); + 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()); + FileHelper.logger.info("Deleted DIR " + file.getAbsolutePath()); //$NON-NLS-1$ } else { worked = false; - FileHelper.logger.warn("Could not delete the directory: " + file.getAbsolutePath()); + 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()); + FileHelper.logger.info("Deleted FILE " + file.getAbsolutePath()); //$NON-NLS-1$ } else { worked = false; - FileHelper.logger.warn(("Could not delete the file: " + file.getAbsolutePath())); + FileHelper.logger.warn(("Could not delete the file: " + file.getAbsolutePath())); //$NON-NLS-1$ } } } return worked; } + /** + * Copy a given list of {@link File Files}. The renameTo method does not allow action across NFS mounted filesystems + * this method is the workaround + * + * @param srcFiles + * The source files to copy + * @param dstDirectory + * The destination where to copy the files + * @param checksum + * if true, then a MD5 checksum is made to validate copying + * @return true if and only if the renaming succeeded; false otherwise + */ + public final static boolean copy(File[] srcFiles, File dstDirectory, boolean checksum) { + + if (!dstDirectory.isDirectory() || !dstDirectory.canWrite()) { + String msg = "Destination is not a directory or is not writeable: {0}"; //$NON-NLS-1$ + throw new IllegalArgumentException(MessageFormat.format(msg, dstDirectory.getAbsolutePath())); + } + + for (File srcFile : srcFiles) { + + File dstFile = new File(dstDirectory, srcFile.getName()); + if (!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 @@ -262,12 +251,8 @@ public class FileHelper { */ public final static boolean copy(File fromFile, File toFile, boolean checksum) { - BufferedInputStream inBuffer = null; - BufferedOutputStream outBuffer = null; - try { - - inBuffer = new BufferedInputStream(new FileInputStream(fromFile)); - outBuffer = new BufferedOutputStream(new FileOutputStream(toFile)); + try (BufferedInputStream inBuffer = new BufferedInputStream(new FileInputStream(fromFile)); + BufferedOutputStream outBuffer = new BufferedOutputStream(new FileOutputStream(toFile));) { int theByte = 0; @@ -283,8 +268,8 @@ public class FileHelper { String fromFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(fromFile)); String toFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(toFile)); if (!fromFileMD5.equals(toFileMD5)) { - FileHelper.logger.error("Copying failed, as MD5 sums are not equal: " + fromFileMD5 + " / " - + 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; @@ -293,8 +278,9 @@ public class FileHelper { // cleanup if files are not the same length if (fromFile.length() != toFile.length()) { - FileHelper.logger.error("Copying failed, as new files are not the same length: " + fromFile.length() - + " / " + toFile.length()); + String msg = MessageFormat.format("Copying failed, as new files are not the same length: {0} / {1}", //$NON-NLS-1$ + fromFile.length(), toFile.length()); + FileHelper.logger.error(msg); toFile.delete(); return false; @@ -304,24 +290,6 @@ public class FileHelper { FileHelper.logger.error(e.getMessage(), e); return false; - - } finally { - - if (inBuffer != null) { - try { - inBuffer.close(); - } catch (IOException e) { - FileHelper.logger.error("Error closing BufferedInputStream" + e); - } - } - - if (outBuffer != null) { - try { - outBuffer.close(); - } catch (IOException e) { - FileHelper.logger.error("Error closing BufferedOutputStream" + e); - } - } } return true; @@ -343,11 +311,11 @@ public class FileHelper { return true; } - FileHelper.logger.warn("Simple File.renameTo failed, trying copy/delete..."); + 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()); + FileHelper.logger.info("Deleting fromFile: " + fromFile.getAbsolutePath()); //$NON-NLS-1$ return fromFile.delete(); } @@ -473,6 +441,7 @@ public class FileHelper { * * @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); @@ -496,7 +465,7 @@ public class FileHelper { * @return the hash as a byte array */ public static byte[] hashFileMd5(File file) { - return FileHelper.hashFile(file, "MD5"); + return FileHelper.hashFile(file, "MD5"); //$NON-NLS-1$ } /** @@ -509,7 +478,7 @@ public class FileHelper { * @return the hash as a byte array */ public static byte[] hashFileSha1(File file) { - return FileHelper.hashFile(file, "SHA-1"); + return FileHelper.hashFile(file, "SHA-1"); //$NON-NLS-1$ } /** @@ -522,7 +491,7 @@ public class FileHelper { * @return the hash as a byte array */ public static byte[] hashFileSha256(File file) { - return FileHelper.hashFile(file, "SHA-256"); + return FileHelper.hashFile(file, "SHA-256"); //$NON-NLS-1$ } /** @@ -537,8 +506,7 @@ public class FileHelper { * @return the hash as a byte array */ public static byte[] hashFile(File file, String algorithm) { - try { - InputStream fis = new FileInputStream(file); + try (InputStream fis = new FileInputStream(file);) { byte[] buffer = new byte[1024]; MessageDigest complete = MessageDigest.getInstance(algorithm); @@ -553,7 +521,7 @@ public class FileHelper { return complete.digest(); } catch (Exception e) { - throw new RuntimeException("Something went wrong while hashing file: " + file.getAbsolutePath()); + throw new RuntimeException("Something went wrong while hashing file: " + file.getAbsolutePath()); //$NON-NLS-1$ } } @@ -567,23 +535,14 @@ public class FileHelper { * the bytes to append */ public static void appendFilePart(File dstFile, byte[] bytes) { - FileOutputStream outputStream = null; - try { - outputStream = new FileOutputStream(dstFile, true); + 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()); - } finally { - if (outputStream != null) { - try { - outputStream.close(); - } catch (IOException e) { - FileHelper.logger.error("Exception while closing FileOutputStream " + e.getLocalizedMessage()); - } - } + throw new RuntimeException("Could not create and append the bytes to the file " + dstFile.getAbsolutePath()); //$NON-NLS-1$ } } } From 9c547af5bbdb448cea113649507a749b8457062b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 31 Oct 2013 22:42:38 +0100 Subject: [PATCH 212/457] [Major] cleaned up the FileServer/Client implentation Also renamed it from RMI to FileServer/Client. There is no reason this has to be RMI bound, as a client must simply implement FileClient where the upload/download methods can be implemented to be done over any kind of remote connection --- .../FileClient.java} | 14 +- .../FileClientUtil.java} | 116 +++++++------ .../FileDeletion.java} | 7 +- .../FileHandler.java} | 162 +++++++++++------- .../FilePart.java} | 13 +- 5 files changed, 180 insertions(+), 132 deletions(-) rename src/main/java/ch/eitchnet/{rmi/RMIFileClient.java => fileserver/FileClient.java} (84%) rename src/main/java/ch/eitchnet/{rmi/RmiHelper.java => fileserver/FileClientUtil.java} (54%) rename src/main/java/ch/eitchnet/{rmi/RmiFileDeletion.java => fileserver/FileDeletion.java} (91%) rename src/main/java/ch/eitchnet/{rmi/RmiFileHandler.java => fileserver/FileHandler.java} (53%) rename src/main/java/ch/eitchnet/{rmi/RmiFilePart.java => fileserver/FilePart.java} (90%) diff --git a/src/main/java/ch/eitchnet/rmi/RMIFileClient.java b/src/main/java/ch/eitchnet/fileserver/FileClient.java similarity index 84% rename from src/main/java/ch/eitchnet/rmi/RMIFileClient.java rename to src/main/java/ch/eitchnet/fileserver/FileClient.java index 9ddbab772..85bc54760 100644 --- a/src/main/java/ch/eitchnet/rmi/RMIFileClient.java +++ b/src/main/java/ch/eitchnet/fileserver/FileClient.java @@ -17,7 +17,7 @@ * along with ch.eitchnet.java.utils. If not, see . * */ -package ch.eitchnet.rmi; +package ch.eitchnet.fileserver; import java.rmi.RemoteException; @@ -25,7 +25,7 @@ import java.rmi.RemoteException; * @author Robert von Burg * */ -public interface RMIFileClient { +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 @@ -36,23 +36,23 @@ public interface RMIFileClient { * @throws RemoteException * if something goes wrong with the remote call */ - public void uploadFilePart(RmiFilePart filePart) throws RemoteException; + 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 RmiFileDeletion} defining the deletion request + * 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(RmiFileDeletion fileDeletion) throws RemoteException; + 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 RmiFilePart} with + * 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 @@ -65,5 +65,5 @@ public interface RMIFileClient { * @throws RemoteException * if something goes wrong with the remote call */ - public RmiFilePart requestFile(RmiFilePart filePart) throws RemoteException; + public FilePart requestFile(FilePart filePart) throws RemoteException; } diff --git a/src/main/java/ch/eitchnet/rmi/RmiHelper.java b/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java similarity index 54% rename from src/main/java/ch/eitchnet/rmi/RmiHelper.java rename to src/main/java/ch/eitchnet/fileserver/FileClientUtil.java index 049e4dbd7..934d406bc 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiHelper.java +++ b/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java @@ -17,13 +17,14 @@ * along with ch.eitchnet.java.utils. If not, see . * */ -package ch.eitchnet.rmi; +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; @@ -35,25 +36,27 @@ import ch.eitchnet.utils.helper.StringHelper; * @author Robert von Burg * */ -public class RmiHelper { +public class FileClientUtil { - private static final Logger logger = LoggerFactory.getLogger(RmiHelper.class); + private static final Logger logger = LoggerFactory.getLogger(FileClientUtil.class); /** * @param rmiFileClient * @param origFilePart * @param dstFile */ - public static void downloadFile(RMIFileClient rmiFileClient, RmiFilePart origFilePart, File 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()) - throw new RuntimeException("The destination file " + dstFile.getAbsolutePath() - + " already exists. Delete it first, if you want to overwrite it!"); + 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 { - RmiFilePart tmpPart = origFilePart; + FilePart tmpPart = origFilePart; int loops = 0; int startLength = tmpPart.getPartLength(); @@ -64,14 +67,17 @@ public class RmiHelper { tmpPart = rmiFileClient.requestFile(tmpPart); // validate length of data - if (tmpPart.getPartLength() != tmpPart.getPartBytes().length) - throw new RuntimeException("Invalid tmpPart. Part length is not as long as the bytes passed " - + tmpPart.getPartLength() + " / " + tmpPart.getPartBytes().length); + 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()) { - throw new RuntimeException("The part offset $offset is not at the end of the file " - + 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 @@ -84,28 +90,33 @@ public class RmiHelper { if (tmpPart.getPartOffset() >= tmpPart.getFileLength()) break; } - RmiHelper.logger.info(tmpPart.getFileType() + ": " + tmpPart.getFileName() + ": Requested " + loops - + " parts. StartSize: " + startLength + " EndSize: " + tmpPart.getPartLength()); + + 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()) { - throw new RuntimeException("Offset " + tmpPart.getPartOffset() + " is not at file length " - + origFilePart.getFileLength() + " after reading all the file parts!"); + 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())) { - throw new RuntimeException("Downloading the file " + origFilePart.getFileName() - + " failed because the hashes don't match. Expected: " + origFilePart.getFileHash() - + " / Actual: " + dstFileHash); + 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; - throw new RuntimeException("Downloading the file " + origFilePart.getFileName() - + " failed because of an underlying exception " + e.getLocalizedMessage()); + 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); } } @@ -114,28 +125,29 @@ public class RmiHelper { * @param srcFile * @param fileType */ - public static void uploadFile(RMIFileClient rmiFileClient, File srcFile, String fileType) { + public static void uploadFile(FileClient rmiFileClient, File srcFile, String fileType) { // make sure the source file exists - if (!srcFile.canRead()) - throw new RuntimeException("The source file does not exist at " + srcFile.getAbsolutePath()); + if (!srcFile.canRead()) { + String msg = MessageFormat.format("The source file does not exist at {0}", srcFile.getAbsolutePath()); //$NON-NLS-1$ + throw new RuntimeException(msg); + } - BufferedInputStream inputStream = null; - try { + 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 - RmiFilePart filePart = new RmiFilePart(srcFile.getName(), fileType); + 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 > RmiFileHandler.MAX_PART_SIZE) - partLength = RmiFileHandler.MAX_PART_SIZE; + if (fileLength > FileHandler.MAX_PART_SIZE) + partLength = FileHandler.MAX_PART_SIZE; else partLength = (int) fileLength; @@ -143,8 +155,6 @@ public class RmiHelper { byte[] bytes = new byte[partLength]; // open the stream to the file - inputStream = new BufferedInputStream(new FileInputStream(srcFile)); - int read = 0; int offset = 0; @@ -159,11 +169,11 @@ public class RmiHelper { // 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!"); + throw new IOException("Something went wrong while reading the bytes as -1 was returned!"); //$NON-NLS-1$ if (read != bytes.length) { - throw new IOException( - "Something went wrong while reading the bytes as the wrong number of bytes were read. Expected " - + bytes.length + " Actual: " + read); + 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 @@ -187,9 +197,11 @@ public class RmiHelper { // the last part of the file if (nextOffset + bytes.length > fileLength) { long remaining = fileLength - nextOffset; - if (remaining > RmiFileHandler.MAX_PART_SIZE) - throw new RuntimeException("Something went wrong as the remaining part " + remaining - + " is larger than MAX_PART_SIZE " + RmiFileHandler.MAX_PART_SIZE + "!"); + 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]; } @@ -198,22 +210,17 @@ public class RmiHelper { offset = nextOffset; } - RmiHelper.logger.info(filePart.getFileType() + ": " + filePart.getFileName() + ": Sent " + loops - + " parts. StartSize: " + startLength + " EndSize: " + filePart.getPartLength()); + 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; - throw new RuntimeException("Uploading the file " + srcFile.getAbsolutePath() - + " failed because of an underlying exception " + e.getLocalizedMessage()); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - RmiHelper.logger.error("Exception while closing FileInputStream " + e.getLocalizedMessage()); - } - } + 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); } } @@ -222,13 +229,14 @@ public class RmiHelper { * @param fileDeletion * @param dstFile */ - public static void deleteFile(RMIFileClient rmiFileClient, RmiFileDeletion fileDeletion, File dstFile) { + public static void deleteFile(FileClient rmiFileClient, FileDeletion fileDeletion, File dstFile) { try { rmiFileClient.deleteFile(fileDeletion); } catch (RemoteException e) { - throw new RuntimeException("Deleting the file " + fileDeletion.getFileName() - + " failed because of an underlying exception " + e.getLocalizedMessage()); + String msg = "Deleting the file {0} failed because of an underlying exception {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, fileDeletion.getFileName(), e.getLocalizedMessage()); + throw new RuntimeException(msg); } } } diff --git a/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java b/src/main/java/ch/eitchnet/fileserver/FileDeletion.java similarity index 91% rename from src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java rename to src/main/java/ch/eitchnet/fileserver/FileDeletion.java index 3071092f0..eecbb7050 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFileDeletion.java +++ b/src/main/java/ch/eitchnet/fileserver/FileDeletion.java @@ -17,16 +17,15 @@ * along with ch.eitchnet.java.utils. If not, see . * */ -package ch.eitchnet.rmi; +package ch.eitchnet.fileserver; import java.io.Serializable; /** * @author Robert von Burg */ -public class RmiFileDeletion implements Serializable { +public class FileDeletion implements Serializable { - // private static final long serialVersionUID = 1L; private String fileName; @@ -38,7 +37,7 @@ public class RmiFileDeletion implements Serializable { * @param fileType * the type of file to delete. This defines in which path the file resides */ - public RmiFileDeletion(String fileName, String fileType) { + public FileDeletion(String fileName, String fileType) { this.fileName = fileName; this.fileType = fileType; } diff --git a/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java b/src/main/java/ch/eitchnet/fileserver/FileHandler.java similarity index 53% rename from src/main/java/ch/eitchnet/rmi/RmiFileHandler.java rename to src/main/java/ch/eitchnet/fileserver/FileHandler.java index 6dcc256b4..7267c562f 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFileHandler.java +++ b/src/main/java/ch/eitchnet/fileserver/FileHandler.java @@ -17,12 +17,13 @@ * along with ch.eitchnet.java.utils. If not, see . * */ -package ch.eitchnet.rmi; +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; @@ -32,13 +33,13 @@ 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(RmiFilePart)} and the downloading a file is done by calling {@link #requestFile(RmiFilePart)} + * {@link #handleFilePart(FilePart)} and the downloading a file is done by calling {@link #requestFile(FilePart)} * * @author Robert von Burg */ -public class RmiFileHandler { +public class FileHandler { - private static final Logger logger = LoggerFactory.getLogger(RmiFileHandler.class); + private static final Logger logger = LoggerFactory.getLogger(FileHandler.class); /** * DEF_PART_SIZE = default part size which is set to 1048576 bytes (1 MiB) @@ -46,23 +47,29 @@ public class RmiFileHandler { public static final int MAX_PART_SIZE = 1048576; private String basePath; + private boolean verbose; /** * */ - public RmiFileHandler(String basePath) { + public FileHandler(String basePath, boolean verbose) { File basePathF = new File(basePath); - if (!basePathF.exists()) - throw new RuntimeException("Base Path does not exist " + basePathF.getAbsolutePath()); - if (!basePathF.canWrite()) - throw new RuntimeException("Can not write to base path " + basePathF.getAbsolutePath()); + 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 RmiFilePart} with a byte + * 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 @@ -70,7 +77,7 @@ public class RmiFileHandler { * @param filePart * the part of the file */ - public RmiFilePart requestFile(RmiFilePart filePart) { + public FilePart requestFile(FilePart filePart) { // validate file name is legal String fileName = filePart.getFileName(); @@ -81,12 +88,15 @@ public class RmiFileHandler { validateFileType(fileType); // evaluate the path where the file should reside - File file = new File(this.basePath + "/" + fileType, filePart.getFileName()); + 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()) { - throw new RuntimeException("The file " + fileName - + " could not be found in the location for files of type " + fileType); + 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 @@ -103,18 +113,21 @@ public class RmiFileHandler { // variables defining the part of the file we're going to return long requestOffset = filePart.getPartOffset(); int requestSize = filePart.getPartLength(); - if (requestSize > RmiFileHandler.MAX_PART_SIZE) { - throw new RuntimeException("The requested part size " + requestSize + " is greater than the allowed " - + RmiFileHandler.MAX_PART_SIZE); + 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) { - throw new RuntimeException("The part request has a file size " + filePart.getFileLength() - + ", but the file is actually " + 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) { - throw new RuntimeException("The requested file part offset " + requestOffset - + " is greater than the size of the file " + 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 @@ -126,8 +139,11 @@ public class RmiFileHandler { long l = Math.min(requestSize, remaining); // this is a fail safe - if (l > RmiFileHandler.MAX_PART_SIZE) - throw new RuntimeException("Something went wrong. Min of requestSize and remaining is > MAX_PART_SIZE!"); + 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; @@ -136,40 +152,42 @@ public class RmiFileHandler { } // now read the part of the file and set it as bytes for the file part - FileInputStream fin = null; - try { + try (FileInputStream fin = new FileInputStream(file);) { // position the stream - fin = new FileInputStream(file); long skip = fin.skip(requestOffset); - if (skip != requestOffset) - throw new IOException("Asked to skip " + requestOffset + " but only skipped " + skip); + 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) - throw new IOException("Asked to read " + requestSize + " but only read " + read); + 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) { - throw new RuntimeException("The file " + fileName - + " could not be found in the location for files of type " + fileType); + String msg = MessageFormat.format(fileNotFoundMsg, fileName, fileType); + throw new RuntimeException(msg); } catch (IOException e) { - throw new RuntimeException("There was an error while reading from the file " + fileName); - } finally { - if (fin != null) { - try { - fin.close(); - } catch (IOException e) { - RmiFileHandler.logger.error("Error while closing FileInputStream: " + e.getLocalizedMessage()); - } - } + 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 edited + // 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; } @@ -180,7 +198,7 @@ public class RmiFileHandler { * @param filePart * the part of the file */ - public void handleFilePart(RmiFilePart filePart) { + public void handleFilePart(FilePart filePart) { // validate file name is legal String fileName = filePart.getFileName(); @@ -191,11 +209,14 @@ public class RmiFileHandler { validateFileType(fileType); // evaluate the path where the file should reside - File dstFile = new File(this.basePath + "/" + fileType, filePart.getFileName()); + 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()) { - throw new RuntimeException("The file " + fileName + " already exist for type " + fileType); + 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 @@ -205,23 +226,34 @@ public class RmiFileHandler { if (filePart.isLastPart()) { String dstFileHash = StringHelper.getHexString(FileHelper.hashFileSha256(dstFile)); if (!dstFileHash.equals(filePart.getFileHash())) { - throw new RuntimeException("Uploading the file " + filePart.getFileName() - + " failed because the hashes don't match. Expected: " + filePart.getFileHash() + " / Actual: " - + dstFileHash); + 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 RmiFileDeletion} defining the deletion request + * the {@link FileDeletion} defining the deletion request * * @return true if the file was deleted, false if the file did not exist * */ - public boolean deleteFile(RmiFileDeletion fileDeletion) { + public boolean deleteFile(FileDeletion fileDeletion) { // validate file name is legal String fileName = fileDeletion.getFileName(); @@ -232,10 +264,20 @@ public class RmiFileHandler { validateFileType(fileType); // evaluate the path where the file should reside - File fileToDelete = new File(this.basePath + "/" + fileType, fileDeletion.getFileName()); + String fileTypePath = this.basePath + "/" + fileType; //$NON-NLS-1$ + File fileToDelete = new File(fileTypePath, fileDeletion.getFileName()); // delete the file - return FileHelper.deleteFiles(new File[] { fileToDelete }, true); + 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; } /** @@ -246,10 +288,10 @@ public class RmiFileHandler { 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!"); - } else if (fileName.contains("/")) { - throw new RuntimeException( - "The given file name contains illegal characters. The file name may not contain slashes!"); + 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); } } @@ -260,10 +302,10 @@ public class RmiFileHandler { */ 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!"); - } else if (fileType.contains("/")) { - throw new RuntimeException( - "The given file type contains illegal characters. The file type may not contain slashes!"); + throw new RuntimeException("The file type was not given! Can not find a file without a type!"); //$NON-NLS-1$ + } else if (fileType.contains("/")) { //$NON-NLS-1$ + String msg = "The given file type contains illegal characters. The file type may not contain slashes!"; //$NON-NLS-1$ + throw new RuntimeException(msg); } } } diff --git a/src/main/java/ch/eitchnet/rmi/RmiFilePart.java b/src/main/java/ch/eitchnet/fileserver/FilePart.java similarity index 90% rename from src/main/java/ch/eitchnet/rmi/RmiFilePart.java rename to src/main/java/ch/eitchnet/fileserver/FilePart.java index 627fcc373..dfdc19615 100644 --- a/src/main/java/ch/eitchnet/rmi/RmiFilePart.java +++ b/src/main/java/ch/eitchnet/fileserver/FilePart.java @@ -17,16 +17,15 @@ * along with ch.eitchnet.java.utils. If not, see . * */ -package ch.eitchnet.rmi; +package ch.eitchnet.fileserver; import java.io.Serializable; /** * @author Robert von Burg */ -public class RmiFilePart implements Serializable { +public class FilePart implements Serializable { - // private static final long serialVersionUID = 1L; private String fileName; @@ -45,18 +44,18 @@ public class RmiFilePart implements Serializable { * @param fileType * defines the type of file being uploaded or retrieved. This defines in which path the file resides */ - public RmiFilePart(String fileName, String fileType) { + public FilePart(String fileName, String fileType) { if (fileName == null || fileName.isEmpty()) - throw new RuntimeException("fileName may not be empty!"); + throw new RuntimeException("fileName may not be empty!"); //$NON-NLS-1$ if (fileType == null || fileType.isEmpty()) - throw new RuntimeException("fileType may not be empty!"); + throw new RuntimeException("fileType may not be empty!"); //$NON-NLS-1$ this.fileName = fileName; this.fileType = fileType; this.partOffset = 0; - this.partLength = RmiFileHandler.MAX_PART_SIZE; + this.partLength = FileHandler.MAX_PART_SIZE; this.partBytes = null; } From 994de0241ad7c64e08824b676f385c5a75b29477 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 1 Nov 2013 18:48:57 +0100 Subject: [PATCH 213/457] [Minor] cleaned up compiler warnings in BaseEncoding --- .../eitchnet/utils/helper/BaseEncoding.java | 78 ++++++++++++------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java index 49d5f0e9e..7a0513c43 100644 --- a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java +++ b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java @@ -21,6 +21,8 @@ */ package ch.eitchnet.utils.helper; +import java.text.MessageFormat; + /** *

      * This class implements the encoding and decoding of RFC 4648 https://tools.ietf.org/html/rfc4648. @@ -368,8 +370,10 @@ public class BaseEncoding { public static byte[] toBase64(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) return new byte[0]; - if (alphabet.length != 64) - throw new RuntimeException("Alphabet does not have expected size 64 but is " + alphabet.length); + if (alphabet.length != 64) { + String msg = MessageFormat.format("Alphabet does not have expected size 64 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } // 6 bits input for every 8 bits (1 byte) output // least common multiple of 6 bits input and 8 bits output = 24 @@ -476,8 +480,10 @@ public class BaseEncoding { public static byte[] toBase32(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) return new byte[0]; - if (alphabet.length != 32) - throw new RuntimeException("Alphabet does not have expected size 32 but is " + alphabet.length); + if (alphabet.length != 32) { + String msg = MessageFormat.format("Alphabet does not have expected size 32 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } // 5 bits input for every 8 bits (1 byte) output // least common multiple of 5 bits input and 8 bits output = 40 @@ -598,8 +604,10 @@ public class BaseEncoding { public static byte[] toBase16(byte[] alphabet, byte[] bytes) { if (bytes.length == 0) return new byte[0]; - if (alphabet.length != 16) - throw new RuntimeException("Alphabet does not have expected size 16 but is " + alphabet.length); + if (alphabet.length != 16) { + String msg = MessageFormat.format("Alphabet does not have expected size 16 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } // calculate output text length int nrOfInputBytes = bytes.length; @@ -651,15 +659,19 @@ public class BaseEncoding { if (inputLength == 0) return new byte[0]; if ((inputLength % 4) != 0) { - throw new RuntimeException("The input bytes to be decoded must be multiples of 4, but is multiple of " - + (inputLength % 4)); + String msg = MessageFormat.format( + "The input bytes to be decoded must be multiples of 4, but is multiple of {0}", //$NON-NLS-1$ + (inputLength % 4)); + throw new RuntimeException(msg); } - if (alphabet.length != 128) - throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + if (alphabet.length != 128) { + String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } if (!isEncodedByAlphabet(alphabet, bytes, PADDING_64)) - throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ // find how much padding we have int nrOfBytesPadding = 0; @@ -786,15 +798,18 @@ public class BaseEncoding { if (inputLength == 0) return new byte[0]; if ((inputLength % 8) != 0) { - throw new RuntimeException("The input bytes to be decoded must be multiples of 8, but is multiple of " - + (inputLength % 8)); + String msg = "The input bytes to be decoded must be multiples of 8, but is multiple of {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, (inputLength % 8)); + throw new RuntimeException(msg); } - if (alphabet.length != 128) - throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + if (alphabet.length != 128) { + String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } if (!isEncodedByAlphabet(alphabet, bytes, PADDING_32)) - throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ // find how much padding we have int nrOfBytesPadding = 0; @@ -945,15 +960,18 @@ public class BaseEncoding { if (bytes.length == 0) return new byte[0]; if ((bytes.length % 2) != 0) { - throw new RuntimeException("The input bytes to be decoded must be multiples of 4, but is multiple of " - + (bytes.length % 4)); + String msg = "The input bytes to be decoded must be multiples of 4, but is multiple of {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, (bytes.length % 4)); + throw new RuntimeException(msg); } - if (alphabet.length != 128) - throw new RuntimeException("Alphabet does not have expected size 128 but is " + alphabet.length); + if (alphabet.length != 128) { + String msg = MessageFormat.format("Alphabet does not have expected size 128 but is {0}", alphabet.length); //$NON-NLS-1$ + throw new RuntimeException(msg); + } if (!isEncodedByAlphabet(alphabet, bytes, 0)) - throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); + throw new RuntimeException("The data contains illegal values which are not mapped by the given alphabet!"); //$NON-NLS-1$ int dataLength = bytes.length / 2; @@ -963,25 +981,27 @@ public class BaseEncoding { byte b1 = bytes[i++]; byte b2 = bytes[i++]; + String msgOutOfRange = "Value at index {0} is not in range of alphabet (0-127){1}"; //$NON-NLS-1$ if (b1 < 0) { - throw new IllegalArgumentException("Value at index " + (i - 2) + " is not in range of alphabet (0-127)" - + b1); + msgOutOfRange = MessageFormat.format(msgOutOfRange, (i - 2), b1); + throw new IllegalArgumentException(msgOutOfRange); } if (b2 < 0) { - throw new IllegalArgumentException("Value at index " + (i - 1) + " is not in range of alphabet (0-127)" - + b2); + msgOutOfRange = MessageFormat.format(msgOutOfRange, (i - 1), b2); + throw new IllegalArgumentException(msgOutOfRange); } byte c1 = alphabet[b1]; byte c2 = alphabet[b2]; + String msgIllegalValue = "Value at index {0} is referencing illegal value in alphabet: {1}"; //$NON-NLS-1$ if (c1 == -1) { - throw new IllegalArgumentException("Value at index " + (i - 2) - + " is referencing illegal value in alphabet: " + b1); + msgIllegalValue = MessageFormat.format(msgIllegalValue, (i - 2), b1); + throw new IllegalArgumentException(msgIllegalValue); } if (c2 == -1) { - throw new IllegalArgumentException("Value at index " + (i - 2) - + " is referencing illegal value in alphabet: " + b2); + msgIllegalValue = MessageFormat.format(msgIllegalValue, (i - 2), b2); + throw new IllegalArgumentException(msgIllegalValue); } int dataIndex = (i / 2) - 1; From 2665aa31b6f41c8fab1fc9d5ca56336b9c60cb4c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 14 Nov 2013 19:21:49 +0100 Subject: [PATCH 214/457] [New] implemented a TransactionResult This allows reflecting on actual changes performed by the transaction if this is needed by the caller. One use case would be to perform observer updates after a transaction was completed. --- .../xmlpers/api/ModificationResult.java | 37 ++++ .../xmlpers/api/PersistenceTransaction.java | 16 ++ .../xmlpers/api/TransactionResult.java | 194 ++++++++++++++++++ .../xmlpers/api/TransactionState.java | 8 + .../impl/DefaultPersistenceTransaction.java | 126 ++++++++++-- .../ch/eitchnet/xmlpers/test/LockingTest.java | 2 +- .../xmlpers/test/TransactionResultTest.java | 141 +++++++++++++ 7 files changed, 509 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java create mode 100644 src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java create mode 100644 src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java b/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java new file mode 100644 index 000000000..c6e947365 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java @@ -0,0 +1,37 @@ +package ch.eitchnet.xmlpers.api; + +import java.util.List; + +public class ModificationResult { + + private final String key; + private final List created; + private final List updated; + private final List deleted; + + public ModificationResult(String key, List created, List updated, List deleted) { + this.key = key; + this.created = created; + this.updated = updated; + this.deleted = deleted; + } + + public String getKey() { + return this.key; + } + + @SuppressWarnings("unchecked") + public List getCreated() { + return (List) this.created; + } + + @SuppressWarnings("unchecked") + public List getUpdated() { + return (List) this.updated; + } + + @SuppressWarnings("unchecked") + public List getDeleted() { + return (List) this.deleted; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java index 0600aa2cf..a507a5204 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java @@ -29,6 +29,22 @@ import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; */ public interface PersistenceTransaction extends AutoCloseable { + /** + * Returns the {@link TransactionResult} for this transaction + * + * @return the {@link TransactionResult} + * + * @throws IllegalStateException + * if the transaction has not yet been closed + */ + public TransactionResult getTransactionResult() throws IllegalStateException; + + /** + * @throws IllegalStateException + * if a result is already set + */ + public void setTransactionResult(TransactionResult txResult) throws IllegalStateException; + public void setCloseStrategy(TransactionCloseStrategy closeStrategy); public void autoCloseableCommit(); diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java new file mode 100644 index 000000000..489b69c40 --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java @@ -0,0 +1,194 @@ +package ch.eitchnet.xmlpers.api; + +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import ch.eitchnet.utils.helper.StringHelper; + +public class TransactionResult { + + private String realm; + private TransactionState state; + private Exception failCause; + + private Date startTime; + private long txDuration; + private long closeDuration; + + private Map modificationByKey; + + /** + * @return the realm + */ + public String getRealm() { + return this.realm; + } + + /** + * @param realm + * the realm to set + */ + public void setRealm(String realm) { + this.realm = realm; + } + + /** + * @return the state + */ + public TransactionState getState() { + return this.state; + } + + /** + * @param state + * the state to set + */ + public void setState(TransactionState state) { + this.state = state; + } + + /** + * @return the failCause + */ + public Exception getFailCause() { + return this.failCause; + } + + /** + * @param failCause + * the failCause to set + */ + public void setFailCause(Exception failCause) { + this.failCause = failCause; + } + + /** + * @return the startTime + */ + public Date getStartTime() { + return this.startTime; + } + + /** + * @param startTime + * the startTime to set + */ + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + /** + * @return the txDuration + */ + public long getTxDuration() { + return this.txDuration; + } + + /** + * @param txDuration + * the txDuration to set + */ + public void setTxDuration(long txDuration) { + this.txDuration = txDuration; + } + + /** + * @return the closeDuration + */ + public long getCloseDuration() { + return this.closeDuration; + } + + /** + * @param closeDuration + * the closeDuration to set + */ + public void setCloseDuration(long closeDuration) { + this.closeDuration = closeDuration; + } + + /** + * @return the modificationByKey + */ + public Map getModificationByKey() { + return this.modificationByKey; + } + + /** + * @param modificationByKey + * the modificationByKey to set + */ + public void setModificationByKey(Map modificationByKey) { + this.modificationByKey = modificationByKey; + } + + /** + * @return + */ + public Set getKeys() { + return this.modificationByKey.keySet(); + } + + /** + * @param key + * @return + */ + public ModificationResult getModificationResult(String key) { + return this.modificationByKey.get(key); + } + + @SuppressWarnings("nls") + public String getLogMessage() { + + int nrOfObjects = 0; + for (ModificationResult result : this.modificationByKey.values()) { + nrOfObjects += result.getCreated().size(); + nrOfObjects += result.getUpdated().size(); + nrOfObjects += result.getDeleted().size(); + } + + StringBuilder sb = new StringBuilder(); + switch (this.state) { + case OPEN: + sb.append("TX is still open after "); + break; + case COMMITTED: + sb.append("TX was completed after "); + break; + case ROLLED_BACK: + sb.append("TX was rolled back after "); + break; + case FAILED: + sb.append("TX has failed after "); + break; + default: + sb.append("TX is in unhandled state "); + sb.append(this.state); + sb.append(" after "); + } + + sb.append(StringHelper.formatNanoDuration(this.txDuration)); + sb.append(" with close operation taking "); + sb.append(StringHelper.formatNanoDuration(this.closeDuration)); + sb.append(". "); + sb.append(nrOfObjects); + sb.append(" objects in "); + sb.append(this.modificationByKey.size()); + sb.append(" types were modified."); + + return sb.toString(); + } + + /** + * Clears all fields of this result, allowing it to be reused + */ + public void clear() { + this.realm = null; + this.state = null; + this.failCause = null; + this.startTime = null; + this.txDuration = 0L; + this.closeDuration = 0L; + } +} diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java new file mode 100644 index 000000000..15b6a3ced --- /dev/null +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java @@ -0,0 +1,8 @@ +package ch.eitchnet.xmlpers.api; + +public enum TransactionState { + OPEN, // + COMMITTED, // + ROLLED_BACK, // + FAILED; +} diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index 225085423..c74ce27d1 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -22,7 +22,11 @@ package ch.eitchnet.xmlpers.impl; import java.text.MessageFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import org.slf4j.Logger; @@ -33,11 +37,14 @@ import ch.eitchnet.utils.objectfilter.ObjectFilter; import ch.eitchnet.xmlpers.api.FileDao; import ch.eitchnet.xmlpers.api.IoMode; import ch.eitchnet.xmlpers.api.MetadataDao; +import ch.eitchnet.xmlpers.api.ModificationResult; import ch.eitchnet.xmlpers.api.ObjectDao; import ch.eitchnet.xmlpers.api.PersistenceContext; import ch.eitchnet.xmlpers.api.PersistenceRealm; import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.api.TransactionCloseStrategy; +import ch.eitchnet.xmlpers.api.TransactionResult; +import ch.eitchnet.xmlpers.api.TransactionState; import ch.eitchnet.xmlpers.api.XmlPersistenceException; import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; @@ -57,15 +64,18 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { private final MetadataDao metadataDao; private FileDao fileDao; - - private boolean committed; - private boolean closed; - private IoMode ioMode; private TransactionCloseStrategy closeStrategy; + private TransactionState state; + private long startTime; + private Date startTimeDate; + private TransactionResult txResult; + public DefaultPersistenceTransaction(DefaultPersistenceRealm realm, boolean verbose) { + this.startTime = System.nanoTime(); + this.startTimeDate = new Date(); this.realm = realm; this.verbose = verbose; this.objectFilter = new ObjectFilter(); @@ -74,6 +84,25 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { this.metadataDao = new MetadataDao(realm.getPathBuilder(), this, verbose); this.closeStrategy = TransactionCloseStrategy.COMMIT; + this.state = TransactionState.OPEN; + } + + @Override + public void setTransactionResult(TransactionResult txResult) throws IllegalStateException { + if (this.txResult != null) { + String msg = "The transaction already has a result set!"; //$NON-NLS-1$ + throw new IllegalStateException(msg); + } + this.txResult = txResult; + } + + @Override + public TransactionResult getTransactionResult() throws IllegalStateException { + if (isOpen()) { + String msg = "The transaction is still open thus has no result yet! Either commit or rollback before calling this method"; //$NON-NLS-1$ + throw new IllegalStateException(msg); + } + return this.txResult; } @Override @@ -108,13 +137,26 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { @Override public void autoCloseableRollback() { - if (this.committed) + long start = System.nanoTime(); + if (this.state == TransactionState.COMMITTED) throw new IllegalStateException("Transaction has already been committed!"); //$NON-NLS-1$ - if (!this.closed) { + if (this.state != TransactionState.ROLLED_BACK) { unlockObjectRefs(); - this.closed = true; + this.state = TransactionState.ROLLED_BACK; this.objectFilter.clearCache(); + + long end = System.nanoTime(); + long txDuration = end - this.startTime; + long closeDuration = end - start; + + this.txResult.clear(); + this.txResult.setState(this.state); + this.txResult.setStartTime(this.startTimeDate); + this.txResult.setTxDuration(txDuration); + this.txResult.setCloseDuration(closeDuration); + this.txResult.setRealm(this.realm.getRealmName()); + this.txResult.setModificationByKey(Collections. emptyMap()); } } @@ -131,6 +173,12 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { } Set keySet = this.objectFilter.keySet(); + Map modifications; + if (this.txResult == null) + modifications = null; + else + modifications = new HashMap<>(keySet.size()); + for (String key : keySet) { List removed = this.objectFilter.getRemoved(key); @@ -177,23 +225,73 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { this.fileDao.performCreate(ctx); } } + + if (modifications != null) { + ModificationResult result = new ModificationResult(key, added, updated, removed); + modifications.put(key, result); + } } - long end = System.nanoTime(); - logger.info("TX completed in " + StringHelper.formatNanoDuration(end - start)); //$NON-NLS-1$ + if (this.txResult != null) { + this.txResult.clear(); + this.txResult.setState(TransactionState.COMMITTED); + this.txResult.setModificationByKey(modifications); + } } catch (Exception e) { - long end = System.nanoTime(); - logger.info("TX failed after " + StringHelper.formatNanoDuration(end - start)); //$NON-NLS-1$ + if (this.txResult == null) { - throw e; + long end = System.nanoTime(); + long txDuration = end - this.startTime; + long closeDuration = end - start; + + StringBuilder sb = new StringBuilder(); + sb.append("TX has failed after "); //$NON-NLS-1$ + sb.append(StringHelper.formatNanoDuration(txDuration)); + sb.append(" with close operation taking "); //$NON-NLS-1$ + sb.append(StringHelper.formatNanoDuration(closeDuration)); + logger.info(sb.toString()); + + throw e; + } + + this.txResult.clear(); + this.txResult.setState(TransactionState.FAILED); + this.txResult.setModificationByKey(Collections. emptyMap()); } finally { + // clean up unlockObjectRefs(); this.objectFilter.clearCache(); - this.committed = true; + } + + long end = System.nanoTime(); + long txDuration = end - this.startTime; + long closeDuration = end - start; + + if (this.txResult == null) { + + StringBuilder sb = new StringBuilder(); + sb.append("TX was completed after "); //$NON-NLS-1$ + sb.append(StringHelper.formatNanoDuration(txDuration)); + sb.append(" with close operation taking "); //$NON-NLS-1$ + sb.append(StringHelper.formatNanoDuration(closeDuration)); + logger.info(sb.toString()); + + } else { + + this.txResult.setStartTime(this.startTimeDate); + this.txResult.setTxDuration(txDuration); + this.txResult.setCloseDuration(closeDuration); + this.txResult.setRealm(this.realm.getRealmName()); + + if (this.txResult.getState() == TransactionState.FAILED) { + String msg = "Failed to commit TX due to underlying exception: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.txResult.getFailCause().getMessage()); + throw new XmlPersistenceException(msg, this.txResult.getFailCause()); + } } } @@ -207,7 +305,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { @Override public boolean isOpen() { - return !this.closed && !this.committed; + return this.state == TransactionState.OPEN; } @Override diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java index 4351a7c47..61b366b5f 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -95,7 +95,7 @@ public class LockingTest extends AbstractPersistenceTest { String resourceId = "createWorkerRes"; //$NON-NLS-1$ for (int i = 0; i < 5; i++) { - String workerName = resourceId + "_" + i; + String workerName = resourceId + "_" + i; //$NON-NLS-1$ CreateResourceWorker worker = new CreateResourceWorker(workerName, resourceId); worker.start(); workers.add(worker); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java b/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java new file mode 100644 index 000000000..8aa381dc7 --- /dev/null +++ b/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.xmlpers.test; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.ModificationResult; +import ch.eitchnet.xmlpers.api.ObjectDao; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.api.TransactionResult; +import ch.eitchnet.xmlpers.test.model.Book; +import ch.eitchnet.xmlpers.test.model.Resource; + +/** + * @author Robert von Burg + * + */ +public class TransactionResultTest extends AbstractPersistenceTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static final String BASEPATH = "target/db/TxResultTest/"; //$NON-NLS-1$ + + @Before + public void setup() { + cleanPath(BASEPATH); + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASEPATH); + setup(properties); + } + + private PersistenceTransaction freshTx() { + PersistenceTransaction tx = this.persistenceManager.openTx(); + tx.setIoMode(IoMode.SAX); + return tx; + } + + @Test + public void testWithTxResult() { + + TransactionResult txResult = new TransactionResult(); + performChanges(txResult); + String logMessage = txResult.getLogMessage(); + logger.info(logMessage); + assertThat(logMessage, containsString("TX was completed after")); //$NON-NLS-1$ + assertThat(logMessage, containsString("30 objects in 2 types were modified")); //$NON-NLS-1$ + assertThat(txResult.getKeys(), containsInAnyOrder("Resource", "Book")); //$NON-NLS-1$ //$NON-NLS-2$ + + ModificationResult resourceModificationResult = txResult.getModificationResult("Resource"); //$NON-NLS-1$ + assertEquals(20, resourceModificationResult.getCreated().size()); + assertEquals(0, resourceModificationResult.getUpdated().size()); + assertEquals(0, resourceModificationResult.getDeleted().size()); + + ModificationResult bookModificationResult = txResult.getModificationResult("Book"); //$NON-NLS-1$ + assertEquals(10, bookModificationResult.getCreated().size()); + assertEquals(0, bookModificationResult.getUpdated().size()); + assertEquals(0, bookModificationResult.getDeleted().size()); + } + + @Test + public void testWithoutTxResult() { + performChanges(null); + } + + private void performChanges(TransactionResult txResult) { + + // create a list of resources + List resources = new ArrayList<>(10); + int i = 0; + for (; i < 10; i++) { + String id = RES_ID + "_" + i; //$NON-NLS-1$ + String name = "Tx Result Test 1 Object. " + i; //$NON-NLS-1$ + String type = "testTxResult1"; //$NON-NLS-1$ + + Resource resource = createResource(id, name, type); + resources.add(resource); + } + for (; i < 20; i++) { + String id = RES_ID + "_" + i; //$NON-NLS-1$ + String name = "Tx Result Test 2 Object. " + i; //$NON-NLS-1$ + String type = "testTxResult2"; //$NON-NLS-1$ + + Resource resource = createResource(id, name, type); + resources.add(resource); + } + + // create a list of books + List books = new ArrayList<>(10); + i = 0; + for (; i < 10; i++) { + String title = "Tx Result Test Book " + i; //$NON-NLS-1$ + Book book = new Book((long) i, title, "Douglas Adams", "Apress", Math.random() * i); //$NON-NLS-1$ //$NON-NLS-2$ + books.add(book); + } + + // save all + try (PersistenceTransaction tx = freshTx();) { + tx.setTransactionResult(txResult); + ObjectDao objectDao = tx.getObjectDao(); + objectDao.addAll(resources); + objectDao.addAll(books); + resources.clear(); + } + } +} From 23d8a1e0ceacd35b9dbe72539435ddd0bd83218b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 14 Nov 2013 21:29:45 +0100 Subject: [PATCH 215/457] [New] implemented a TransactionResult This allows reflecting on actual changes performed by the transaction if this is needed by the caller. One use case would be to perform observer updates after a transaction was completed. --- .../java/ch/eitchnet/xmlpers/api/ModificationResult.java | 8 ++++++++ .../java/ch/eitchnet/xmlpers/api/TransactionResult.java | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java b/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java index c6e947365..a67ad0b65 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java @@ -1,5 +1,6 @@ package ch.eitchnet.xmlpers.api; +import java.util.ArrayList; import java.util.List; public class ModificationResult { @@ -9,6 +10,13 @@ public class ModificationResult { private final List updated; private final List deleted; + public ModificationResult(String key) { + this.key = key; + this.created = new ArrayList<>(); + this.updated = new ArrayList<>(); + this.deleted = new ArrayList<>(); + } + public ModificationResult(String key, List created, List updated, List deleted) { this.key = key; this.created = created; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java index 489b69c40..02dc986cf 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java @@ -108,13 +108,6 @@ public class TransactionResult { this.closeDuration = closeDuration; } - /** - * @return the modificationByKey - */ - public Map getModificationByKey() { - return this.modificationByKey; - } - /** * @param modificationByKey * the modificationByKey to set From 01884029b84f4555a8b0fc4afc0ab46b4ed1d0e8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 15 Nov 2013 18:44:55 +0100 Subject: [PATCH 216/457] [Minor] removed unused file config/Model.xml --- config/Model.xml | 55 ------------------------------------------------ 1 file changed, 55 deletions(-) delete mode 100644 config/Model.xml diff --git a/config/Model.xml b/config/Model.xml deleted file mode 100644 index 74dabc4f4..000000000 --- a/config/Model.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - true - - - - - - true - - - true - - - - - - - - - Application - Administrator - ENABLED - en_GB - - PrivilegeAdmin - AppUser - - - - - - - - - System User - Administrator - SYSTEM - en_GB - - system_admin_privileges - - - - - - - From a8b0f378346cb5650fd4e0e05bc4bff6b0ee1de9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 16 Nov 2013 01:08:27 +0100 Subject: [PATCH 217/457] [Minor] Changed exceptions to contain cause and code clean up --- .../handler/XmlPersistenceHandler.java | 62 ++++++++++--------- .../helper/PrivilegeInitializationHelper.java | 33 +++++----- .../privilege/model/PrivilegeContext.java | 2 +- 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 84ddf5435..f3db54aba 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -20,6 +20,7 @@ package ch.eitchnet.privilege.handler; import java.io.File; +import java.text.MessageFormat; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -56,7 +57,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { private Map parameterMap; - private String modelPath; + private File modelPath; @Override public List getAllUsers() { @@ -125,22 +126,35 @@ public class XmlPersistenceHandler implements PersistenceHandler { String basePath = this.parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); File basePathF = new File(basePath); if (!basePathF.exists() && !basePathF.isDirectory()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); + String msg = "[{0}] Defined parameter {1} does not point to a valid path at {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_BASE_PATH, + basePathF.getAbsolutePath()); + throw new PrivilegeException(msg); } // get model file name String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); if (modelFileName == null || modelFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); + String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE); + throw new PrivilegeException(msg); + } + + // validate file exists + String modelPathS = basePath + "/" + modelFileName; //$NON-NLS-1$ + File modelPath = new File(modelPathS); + if (!modelPath.exists()) { + String msg = "[{0}] Defined parameter {1} is invalid as model file does not exist at path {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE, + modelPath.getAbsolutePath()); + throw new PrivilegeException(msg); } // save path to model - this.modelPath = basePath + "/" + modelFileName; + this.modelPath = modelPath; if (reload()) - XmlPersistenceHandler.logger.info("Privilege Data loaded."); + logger.info("Privilege Data loaded."); //$NON-NLS-1$ } /** @@ -152,22 +166,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public boolean reload() { - // validate file exists - File modelsFile = new File(this.modelPath); - if (!modelsFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " - + modelsFile.getAbsolutePath()); - } - this.roleMap = Collections.synchronizedMap(new HashMap()); this.userMap = Collections.synchronizedMap(new HashMap()); // parse models xml file to XML document PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); - XmlHelper.parseDocument(modelsFile, xmlHandler); + XmlHelper.parseDocument(this.modelPath, xmlHandler); - this.modelsFileDate = modelsFile.lastModified(); + this.modelsFileDate = this.modelPath.lastModified(); // ROLES List roles = xmlHandler.getRoles(); @@ -184,8 +190,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMapDirty = false; this.roleMapDirty = false; - XmlPersistenceHandler.logger.info("Read " + this.userMap.size() + " Users"); - XmlPersistenceHandler.logger.info("Read " + this.roleMap.size() + " Roles"); + logger.info(MessageFormat.format("Read {0} Users", this.userMap.size())); //$NON-NLS-1$ + logger.info(MessageFormat.format("Read {0} Roles", this.roleMap.size())); //$NON-NLS-1$ // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; @@ -198,8 +204,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { } if (!privilegeAdminExists) { - XmlPersistenceHandler.logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE - + "' exists. Privilege modifications will not be possible!"); + String msg = "No User with role ''{0}'' exists. Privilege modifications will not be possible!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + logger.warn(msg); } return true; @@ -214,21 +221,20 @@ public class XmlPersistenceHandler implements PersistenceHandler { // get models file name String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); if (modelFileName == null || modelFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); + String msg = "[{0}] Defined parameter {1} is invalid"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE); + throw new PrivilegeException(msg); } // get model file - File modelFile = new File(this.modelPath); - boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; + boolean modelFileUnchanged = this.modelPath.exists() && this.modelPath.lastModified() == this.modelsFileDate; if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { - XmlPersistenceHandler.logger - .warn("Not persisting as current file is unchanged and model data is not dirty"); + logger.warn("Not persisting as current file is unchanged and model data is not dirty"); //$NON-NLS-1$ return false; } // delegate writing - PrivilegeModelDomWriter modelWriter = new PrivilegeModelDomWriter(getAllUsers(), getAllRoles(), modelFile); + PrivilegeModelDomWriter modelWriter = new PrivilegeModelDomWriter(getAllUsers(), getAllRoles(), this.modelPath); modelWriter.write(); // reset dirty states diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index 35529dde9..0dde63e8c 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -22,11 +22,9 @@ package ch.eitchnet.privilege.helper; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; +import java.text.MessageFormat; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; import ch.eitchnet.privilege.handler.EncryptionHandler; @@ -45,8 +43,6 @@ import ch.eitchnet.utils.helper.XmlHelper; */ public class PrivilegeInitializationHelper { - private static final Logger logger = LoggerFactory.getLogger(PrivilegeInitializationHelper.class); - /** * Initializes the {@link DefaultPrivilegeHandler} from the configuration file * @@ -60,15 +56,18 @@ public class PrivilegeInitializationHelper { // make sure file exists if (!privilegeXmlFile.exists()) { - throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); + String msg = "Privilege file does not exist at path {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeXmlFile.getAbsolutePath()); + throw new PrivilegeException(msg); } // delegate using input stream try { return initializeFromXml(new FileInputStream(privilegeXmlFile)); } catch (Exception e) { - PrivilegeInitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("Failed to load configuration from " + privilegeXmlFile.getAbsolutePath()); + String msg = "Failed to load configuration from {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeXmlFile.getAbsolutePath()); + throw new PrivilegeException(msg, e); } } @@ -96,9 +95,9 @@ public class PrivilegeInitializationHelper { try { encryptionHandler.initialize(parameterMap); } catch (Exception e) { - PrivilegeInitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName - + " could not be initialized"); + String msg = "EncryptionHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, encryptionHandlerClassName); + throw new PrivilegeException(msg, e); } // initialize persistence handler @@ -108,9 +107,9 @@ public class PrivilegeInitializationHelper { try { persistenceHandler.initialize(parameterMap); } catch (Exception e) { - PrivilegeInitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("PersistenceHandler " + persistenceHandlerClassName - + " could not be initialized"); + String msg = "PersistenceHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, persistenceHandlerClassName); + throw new PrivilegeException(msg, e); } // initialize privilege handler @@ -120,9 +119,9 @@ public class PrivilegeInitializationHelper { try { privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { - PrivilegeInitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() - + " could not be initialized"); + String msg = "PrivilegeHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeHandler.getClass().getName()); + throw new PrivilegeException(msg, e); } return privilegeHandler; diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 661434a18..00a27d189 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -96,7 +96,7 @@ public class PrivilegeContext { */ public void validateAction(Restrictable restrictable) throws AccessDeniedException, PrivilegeException { - // the the privilege for the restrictable + // the privilege for the restrictable String privilegeName = restrictable.getPrivilegeName(); IPrivilege privilege = this.privileges.get(privilegeName); if (privilege == null) { From 03663ea8d330f0cca1610f45973a266c7190777d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 18 Nov 2013 19:18:59 +0100 Subject: [PATCH 218/457] [New] Refactoring code for ISO8601 formatting/parsing and added Date --- .../ch/eitchnet/utils/iso8601/DateFormat.java | 23 +++++++++-- .../eitchnet/utils/iso8601/FormatFactory.java | 4 +- .../ch/eitchnet/utils/iso8601/ISO8601.java | 41 +++++++++++++------ .../utils/iso8601/ISO8601FormatFactory.java | 4 +- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java index d7d918dea..45022a48b 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java @@ -8,6 +8,8 @@ package ch.eitchnet.utils.iso8601; +import java.util.Date; + /** * interface for all date formats internally used by rsp applications * @@ -18,10 +20,18 @@ public interface DateFormat { /** * format a long to string * - * @param l + * @param timepoint * @return the formatted string of the long value */ - public String format(long l); + 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 @@ -29,6 +39,13 @@ public interface DateFormat { * @param s * @return the value parsed */ - public long parse(String s); + public long parseLong(String s); + /** + * parse a string to Date + * + * @param s + * @return the value parsed + */ + public Date parse(String s); } diff --git a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java index 99d43ee76..e92175a02 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java @@ -1,5 +1,7 @@ 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 @@ -51,7 +53,7 @@ public interface FormatFactory { * * @return String representation of the date */ - public String formatDate(long date); + public String formatDate(Date date); /** * Formats a duration using {@link #getDateFormat()} diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java index 30be5b495..4788a9de0 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java @@ -9,6 +9,8 @@ import java.util.TimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.utils.helper.StringHelper; + /** * */ @@ -17,9 +19,7 @@ public class ISO8601 implements DateFormat { private static final Logger logger = LoggerFactory.getLogger(ISO8601.class); - /** - * misc. numeric formats used in formatting - */ + //misc. numeric formats used in formatting private DecimalFormat xxFormat = new DecimalFormat("00"); private DecimalFormat xxxFormat = new DecimalFormat("000"); private DecimalFormat xxxxFormat = new DecimalFormat("0000"); @@ -29,10 +29,6 @@ public class ISO8601 implements DateFormat { */ private Calendar parseToCalendar(String text) { - if (text == null) { - throw new IllegalArgumentException("argument can not be null"); - } - // check optional leading sign char sign; int start; @@ -214,6 +210,13 @@ public class ISO8601 implements DateFormat { 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 * @@ -240,6 +243,11 @@ public class ISO8601 implements DateFormat { } } + @Override + public long parseLong(String s) { + return parse(s).getTime(); + } + /** * parse ISO8601 date to long * @@ -249,17 +257,26 @@ public class ISO8601 implements DateFormat { * @throws NumberFormatException */ @Override - public long parse(String s) { + public Date parse(String s) { - if (s.equals("-")) - return Long.MAX_VALUE; + 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().getTime(); + return cal.getTime(); } String msg = "Input string " + s + " cannot be parsed to date."; - throw new NumberFormatException(msg); + throw new IllegalArgumentException(msg); } } diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java index 1c1fed122..93775a8b8 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java @@ -1,5 +1,7 @@ package ch.eitchnet.utils.iso8601; +import java.util.Date; + import ch.eitchnet.utils.helper.MathHelper; /** @@ -51,7 +53,7 @@ public class ISO8601FormatFactory implements FormatFactory { } @Override - public String formatDate(long date) { + public String formatDate(Date date) { return getDateFormat().format(date); } From f3a9f55007dbbfafd5ea0a1cc33b01b69dcf5e3f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Dec 2013 12:29:05 +0100 Subject: [PATCH 219/457] [Minor] Removed unused method --- src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java index af499f317..e810af235 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java @@ -24,10 +24,6 @@ public class LockableObject { public static long getLockTime() { return tryLockTime; } - - public boolean isLockedByCurrentThread() { - return this.lock.isHeldByCurrentThread(); - } /** * @see java.util.concurrent.locks.ReentrantLock#tryLock(long, TimeUnit) From 036c725a52e90d0642cfe649482c371e16ca3649 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Dec 2013 13:38:29 +0100 Subject: [PATCH 220/457] [Project] Changed all licence references to Apache License 2.0 --- COPYING | 674 ------------------ COPYING.LESSER | 165 ----- LICENSE | 202 ++++++ config/Privilege.xml | 23 - config/PrivilegeModel.xml | 6 - docs/TODO | 5 - .../privilege/base/AccessDeniedException.java | 26 +- .../privilege/base/PrivilegeException.java | 26 +- .../handler/DefaultEncryptionHandler.java | 26 +- .../handler/DefaultPrivilegeHandler.java | 26 +- .../privilege/handler/EncryptionHandler.java | 26 +- .../privilege/handler/PersistenceHandler.java | 26 +- .../privilege/handler/PrivilegeHandler.java | 26 +- .../privilege/handler/SystemUserAction.java | 28 +- .../handler/XmlPersistenceHandler.java | 26 +- .../helper/BootstrapConfigurationHelper.java | 26 +- .../privilege/helper/ClassHelper.java | 26 +- .../eitchnet/privilege/helper/HashHelper.java | 26 +- .../privilege/helper/PasswordCreaterUI.java | 26 +- .../privilege/helper/PasswordCreator.java | 26 +- .../helper/PrivilegeInitializationHelper.java | 26 +- .../privilege/helper/XmlConstants.java | 26 +- .../privilege/i18n/PrivilegeMessages.java | 32 +- .../eitchnet/privilege/model/Certificate.java | 26 +- .../eitchnet/privilege/model/IPrivilege.java | 32 +- .../privilege/model/PrivilegeContext.java | 32 +- .../privilege/model/PrivilegeRep.java | 26 +- .../privilege/model/Restrictable.java | 26 +- .../ch/eitchnet/privilege/model/RoleRep.java | 26 +- .../ch/eitchnet/privilege/model/UserRep.java | 26 +- .../eitchnet/privilege/model/UserState.java | 26 +- .../internal/PrivilegeContainerModel.java | 32 +- .../model/internal/PrivilegeImpl.java | 26 +- .../privilege/model/internal/Role.java | 26 +- .../privilege/model/internal/User.java | 26 +- .../privilege/policy/DefaultPrivilege.java | 26 +- .../privilege/policy/PrivilegePolicy.java | 26 +- .../eitchnet/privilege/xml/ElementParser.java | 32 +- .../privilege/xml/ElementParserAdapter.java | 32 +- .../xml/PrivilegeConfigDomWriter.java | 32 +- .../xml/PrivilegeConfigSaxReader.java | 32 +- .../xml/PrivilegeModelDomWriter.java | 32 +- .../xml/PrivilegeModelSaxReader.java | 32 +- src/main/resources/Privilege.xsd | 31 +- src/main/resources/PrivilegeModel.xsd | 31 +- .../privilege/test/PrivilegeTest.java | 26 +- .../ch/eitchnet/privilege/test/XmlTest.java | 32 +- .../test/model/TestRestrictable.java | 26 +- .../test/model/TestSystemRestrictable.java | 26 +- .../test/model/TestSystemUserAction.java | 28 +- .../test/model/TestSystemUserActionDeny.java | 28 +- 51 files changed, 726 insertions(+), 1601 deletions(-) delete mode 100644 COPYING delete mode 100644 COPYING.LESSER create mode 100644 LICENSE diff --git a/COPYING b/COPYING deleted file mode 100644 index 94a9ed024..000000000 --- a/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program 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. - - This program 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 this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/COPYING.LESSER b/COPYING.LESSER deleted file mode 100644 index 65c5ca88a..000000000 --- a/COPYING.LESSER +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/config/Privilege.xml b/config/Privilege.xml index 20eda653e..2c523ead3 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -1,27 +1,4 @@ - - diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index a58e6830c..2d1659459 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -1,10 +1,4 @@ - diff --git a/docs/TODO b/docs/TODO index 319d89b1f..e6c36ace6 100644 --- a/docs/TODO +++ b/docs/TODO @@ -4,13 +4,8 @@ A list of TODOs for Privilege - Write up a proper explanation on the idea on how Privilege, PrivilegePolicy, Restrictable and Roles fit together to grant privileges to Users -- Re-think the PrivilegePolicy and its - actionAllowed(Role, Privilege, Restrictable)-method. Maybe the Privilege - argument is not needed, as this should be on the Role anyhow... - - i18n for any messages and exceptions! - Finish the JavaDoc - Set up a website =) - diff --git a/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java b/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java index 0c8fa5283..2a14841d7 100644 --- a/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java +++ b/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.base; diff --git a/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java b/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java index dc254418a..9d34cd7ed 100644 --- a/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java +++ b/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.base; diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 237fee200..792e6b9bb 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.handler; diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 31014d2a5..efdfd9f2d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.handler; diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java index f6b734213..186789f4d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.handler; diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index a7dcc8b43..85b009a33 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.handler; diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index eac3b4422..52832c2c8 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.handler; diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java index 724d8a32d..caf484f8f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . + * Copyright 2013 Robert von Burg * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.handler; diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index f3db54aba..d94c6b246 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.handler; diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 31935e3ab..c6a052942 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.helper; diff --git a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java index 5852805d8..cad6e5fb5 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.helper; diff --git a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java index c038740bd..01fef9e5f 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.helper; diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index 122ba4011..fe58b86f5 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.helper; diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java index 8430e9be0..6285b4d5a 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.helper; diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index 0dde63e8c..435f4faec 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.helper; diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index 3a9a16bc3..068fc01a7 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.helper; diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java index da80d9f63..5b39df545 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.i18n; diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index afff0b2eb..28ea2420f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model; diff --git a/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java index f9ddbe93f..3d22e7b34 100644 --- a/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.model; diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 00a27d189..3889bfe11 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.model; diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index b2a316c5b..10c20da2e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model; diff --git a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java index 99795bb9e..87f889818 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model; diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index dfee0572c..9a4fe7548 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model; diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 118cb727c..2dd875b40 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model; diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index c34782e1b..4922ff67e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index b4fb6e929..39aabc5ae 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege 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. - * - * Privilege 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 Privilege. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.model.internal; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index c32623f41..8b28c442d 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model.internal; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index 9e85710de..91ddbeb6a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model.internal; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 7e50da571..7fe3a0ed2 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.model.internal; diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 818d1e4c6..98c170bd6 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.policy; diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 4f0372c25..a585ef589 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.policy; diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java index 82d513343..f35a1980e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege 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. - * - * Privilege 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 Privilege. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java index 5f90bb59c..11a11200e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege 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. - * - * Privilege 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 Privilege. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java index fae22d9f0..e2745a7eb 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege 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. - * - * Privilege 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 Privilege. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index d8c8b12e7..74547ea9e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege 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. - * - * Privilege 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 Privilege. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 03ef69f77..6e419a542 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege 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. - * - * Privilege 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 Privilege. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index ea2bb5058..b3add0f1b 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege 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. - * - * Privilege 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 Privilege. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.xml; diff --git a/src/main/resources/Privilege.xsd b/src/main/resources/Privilege.xsd index 0f9baa712..2fbcbdb1d 100644 --- a/src/main/resources/Privilege.xsd +++ b/src/main/resources/Privilege.xsd @@ -4,24 +4,19 @@ -Copyright (c) 2010, 2011 - -Robert von Burg <eitch@eitchnet.ch> - -This file is part of Privilege. - -Privilege is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Privilege 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with Privilege. If not, see <http://www.gnu.org/licenses/>. + 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. diff --git a/src/main/resources/PrivilegeModel.xsd b/src/main/resources/PrivilegeModel.xsd index 5ec6fd17c..7d0ea3d40 100644 --- a/src/main/resources/PrivilegeModel.xsd +++ b/src/main/resources/PrivilegeModel.xsd @@ -4,24 +4,19 @@ -Copyright (c) 2010, 2011 - -Robert von Burg <eitch@eitchnet.ch> - -This file is part of Privilege. - -Privilege is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Privilege 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 Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with Privilege. If not, see <http://www.gnu.org/licenses/>. +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. diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index d3e58bb41..c01a404c1 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.test; diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 9ff57422d..719e271d9 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege 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. - * - * Privilege 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 Privilege. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.test; diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java index 7b677373c..af7bb4e7f 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.test.model; diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java index d0e6145ba..d73c731f5 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.privilege.test.model; diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index c86a5fdb0..86c1613e2 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . + * Copyright 2013 Robert von Burg * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.test.model; diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java index d8ed59616..5cd8092f9 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . + * Copyright 2013 Robert von Burg * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.privilege.test.model; From 7ad38c1916836d76f6d26a5a576eb07baf8dfa63 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Dec 2013 13:38:33 +0100 Subject: [PATCH 221/457] [Project] Changed all licence references to Apache License 2.0 --- COPYING | 674 ------------------ COPYING.LESSER | 165 ----- LICENSE | 202 ++++++ .../ch/eitchnet/fileserver/FileClient.java | 26 +- .../eitchnet/fileserver/FileClientUtil.java | 26 +- .../ch/eitchnet/fileserver/FileDeletion.java | 26 +- .../ch/eitchnet/fileserver/FileHandler.java | 26 +- .../java/ch/eitchnet/fileserver/FilePart.java | 26 +- .../utils/exceptions/XmlException.java | 32 +- .../eitchnet/utils/helper/ArraysHelper.java | 32 +- .../eitchnet/utils/helper/BaseEncoding.java | 32 +- .../ch/eitchnet/utils/helper/ByteHelper.java | 32 +- .../ch/eitchnet/utils/helper/ClassHelper.java | 15 + .../ch/eitchnet/utils/helper/FileHelper.java | 26 +- .../ch/eitchnet/utils/helper/MathHelper.java | 19 +- .../eitchnet/utils/helper/ProcessHelper.java | 26 +- .../utils/helper/PropertiesHelper.java | 32 +- .../eitchnet/utils/helper/StringHelper.java | 26 +- .../eitchnet/utils/helper/SystemHelper.java | 26 +- .../ch/eitchnet/utils/helper/XmlHelper.java | 26 +- .../ch/eitchnet/utils/iso8601/DateFormat.java | 15 +- .../utils/iso8601/DurationFormat.java | 15 +- .../eitchnet/utils/iso8601/FormatFactory.java | 15 + .../ch/eitchnet/utils/iso8601/ISO8601.java | 15 + .../utils/iso8601/ISO8601Duration.java | 14 +- .../utils/iso8601/ISO8601FormatFactory.java | 15 + .../utils/iso8601/ISO8601Worktime.java | 14 +- .../utils/iso8601/WorktimeFormat.java | 15 +- .../utils/objectfilter/ObjectCache.java | 26 +- .../utils/objectfilter/ObjectFilter.java | 26 +- .../utils/objectfilter/Operation.java | 26 +- .../utils/helper/BaseDecodingTest.java | 32 +- .../utils/helper/BaseEncodingTest.java | 32 +- .../GenerateReverseBaseEncodingAlphabets.java | 32 +- .../utils/objectfilter/ObjectFilterTest.java | 28 +- 35 files changed, 587 insertions(+), 1228 deletions(-) delete mode 100644 COPYING delete mode 100644 COPYING.LESSER create mode 100644 LICENSE diff --git a/COPYING b/COPYING deleted file mode 100644 index 94a9ed024..000000000 --- a/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program 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. - - This program 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 this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/COPYING.LESSER b/COPYING.LESSER deleted file mode 100644 index 65c5ca88a..000000000 --- a/COPYING.LESSER +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/main/java/ch/eitchnet/fileserver/FileClient.java b/src/main/java/ch/eitchnet/fileserver/FileClient.java index 85bc54760..3fc356054 100644 --- a/src/main/java/ch/eitchnet/fileserver/FileClient.java +++ b/src/main/java/ch/eitchnet/fileserver/FileClient.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java b/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java index 934d406bc..102f205d6 100644 --- a/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java +++ b/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/fileserver/FileDeletion.java b/src/main/java/ch/eitchnet/fileserver/FileDeletion.java index eecbb7050..b669aa39e 100644 --- a/src/main/java/ch/eitchnet/fileserver/FileDeletion.java +++ b/src/main/java/ch/eitchnet/fileserver/FileDeletion.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/fileserver/FileHandler.java b/src/main/java/ch/eitchnet/fileserver/FileHandler.java index 7267c562f..bee06ab65 100644 --- a/src/main/java/ch/eitchnet/fileserver/FileHandler.java +++ b/src/main/java/ch/eitchnet/fileserver/FileHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/fileserver/FilePart.java b/src/main/java/ch/eitchnet/fileserver/FilePart.java index dfdc19615..cfd2f5ce3 100644 --- a/src/main/java/ch/eitchnet/fileserver/FilePart.java +++ b/src/main/java/ch/eitchnet/fileserver/FilePart.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java b/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java index 40dec4201..1071eeee2 100644 --- a/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java +++ b/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the ch.eitchnet.utils. - * - * ch.eitchnet.utils 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. - * - * ch.eitchnet.utils 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 ch.eitchnet.utils. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.exceptions; diff --git a/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java b/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java index 74a22a8cf..fdb837ec4 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.helper; diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java index 7a0513c43..8a3437a49 100644 --- a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java +++ b/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the ch.eitchnet.java.utils. - * - * ch.eitchnet.java.utils 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. - * - * ch.eitchnet.java.utils 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 ch.eitchnet.java.utils. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.helper; diff --git a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java index 9fc47cded..b9d4170c3 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the ch.eitchnet.java.utils. - * - * ch.eitchnet.java.utils 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. - * - * ch.eitchnet.java.utils 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 ch.eitchnet.java.utils. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.helper; diff --git a/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java b/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java index bcd1d496d..c25fece89 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.helper; import java.text.MessageFormat; diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 389572504..5db00ea85 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/helper/MathHelper.java b/src/main/java/ch/eitchnet/utils/helper/MathHelper.java index 6f46850b2..2b4b1f34b 100644 --- a/src/main/java/ch/eitchnet/utils/helper/MathHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/MathHelper.java @@ -1,15 +1,18 @@ /* - * Created on 05.01.2004 - */ - -/* - * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 - * Olten + * Copyright 2013 Martin Smock * - * All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index b2c974b48..6c8b908f0 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java b/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java index 1c4fcf31a..4e0056492 100644 --- a/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.helper; diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index df8f094f4..b322d57cc 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java index 57dad260d..f0fa37734 100644 --- a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java index d866c002a..bf618b412 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.utils. - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java index 45022a48b..edbb7f646 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java @@ -1,11 +1,18 @@ /* - * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 - * Olten + * Copyright 2013 Martin Smock * - * All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java index d7d7c6f00..411c70d9c 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java @@ -1,11 +1,18 @@ /* - * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 - * Olten + * Copyright 2013 Martin Smock * - * All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; /** diff --git a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java index e92175a02..931d1c720 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.iso8601; import java.util.Date; diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java index 4788a9de0..2c9f1de68 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.iso8601; import java.text.DecimalFormat; diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java index 810b7b34d..c319c719e 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java @@ -1,9 +1,17 @@ /* - * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 - * Olten + * Copyright 2013 Martin Smock * - * All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java index 93775a8b8..682ad6bbd 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.iso8601; import java.util.Date; diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java index ffd0c6c08..81f96cfa5 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java @@ -1,9 +1,17 @@ /* - * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 - * Olten + * Copyright 2013 Martin Smock * - * All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java index 058a5f7c3..48fbf65b3 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java @@ -1,11 +1,18 @@ /* - * Copyright (c) 2006 - 2011 Apixxo AG Hauptgasse 25 4600 - * Olten + * Copyright 2013 Martin Smock * - * All rights reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; /** diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java index 382baffc8..1a9bf9fde 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Michael Gatto * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index cc0f9123c..b2282e9ca 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Michael Gatto * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java b/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java index 1dce6362a..a54197fe4 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Michael Gatto * - * This file is part of ch.eitchnet.java.utils - * - * ch.eitchnet.java.utils is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.utils 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.utils. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java index fee6c668e..7444a0ef6 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.helper; diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java index 7bca92e75..15a62eb32 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the ch.eitchnet.java.utils. - * - * ch.eitchnet.java.utils 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. - * - * ch.eitchnet.java.utils 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 ch.eitchnet.java.utils. If not, see - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.helper; diff --git a/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java b/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java index 3e22f4331..562368b75 100644 --- a/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java +++ b/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.helper; diff --git a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java index ea0b7e6f9..f7a7c99df 100644 --- a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java +++ b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . + * Copyright 2013 Robert von Burg * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.utils.objectfilter; From 6bff06669f617b85cb3a110cfdc2ed0ea4e18acb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Dec 2013 13:38:36 +0100 Subject: [PATCH 222/457] [Project] Changed all licence references to Apache License 2.0 --- COPYING | 674 ------------------ COPYING.LESSER | 165 ----- LICENSE | 202 ++++++ .../ch/eitchnet/xmlpers/api/DomParser.java | 32 +- .../java/ch/eitchnet/xmlpers/api/FileDao.java | 32 +- .../java/ch/eitchnet/xmlpers/api/FileIo.java | 32 +- .../java/ch/eitchnet/xmlpers/api/IoMode.java | 32 +- .../ch/eitchnet/xmlpers/api/IoOperation.java | 15 + .../ch/eitchnet/xmlpers/api/MetadataDao.java | 32 +- .../xmlpers/api/ModificationResult.java | 15 + .../ch/eitchnet/xmlpers/api/ObjectDao.java | 32 +- .../eitchnet/xmlpers/api/ParserFactory.java | 32 +- .../xmlpers/api/PersistenceConstants.java | 32 +- .../xmlpers/api/PersistenceContext.java | 32 +- .../api/PersistenceContextFactory.java | 15 + .../PersistenceContextFactoryDelegator.java | 32 +- .../xmlpers/api/PersistenceManager.java | 32 +- .../xmlpers/api/PersistenceManagerLoader.java | 32 +- .../xmlpers/api/PersistenceRealm.java | 15 + .../xmlpers/api/PersistenceTransaction.java | 32 +- .../ch/eitchnet/xmlpers/api/SaxParser.java | 32 +- .../xmlpers/api/TransactionCloseStrategy.java | 15 + .../xmlpers/api/TransactionResult.java | 15 + .../xmlpers/api/TransactionState.java | 15 + .../xmlpers/api/XmlPersistenceException.java | 26 +- .../api/XmlPersistenceStreamWriter.java | 32 +- .../impl/DefaultPersistenceManager.java | 32 +- .../xmlpers/impl/DefaultPersistenceRealm.java | 15 + .../impl/DefaultPersistenceTransaction.java | 32 +- .../ch/eitchnet/xmlpers/impl/PathBuilder.java | 26 +- .../xmlpers/objref/IdOfSubTypeRef.java | 15 + .../eitchnet/xmlpers/objref/IdOfTypeRef.java | 15 + .../xmlpers/objref/LockableObject.java | 15 + .../ch/eitchnet/xmlpers/objref/ObjectRef.java | 15 + .../xmlpers/objref/ObjectReferenceCache.java | 15 + .../xmlpers/objref/RefNameCreator.java | 15 + .../ch/eitchnet/xmlpers/objref/RootRef.java | 15 + .../eitchnet/xmlpers/objref/SubTypeRef.java | 15 + .../ch/eitchnet/xmlpers/objref/TypeRef.java | 15 + .../eitchnet/xmlpers/util/AssertionUtil.java | 15 + .../ch/eitchnet/xmlpers/util/DomUtil.java | 32 +- .../xmlpers/util/FilenameUtility.java | 15 + .../xmlpers/test/AbstractPersistenceTest.java | 15 + .../ch/eitchnet/xmlpers/test/FileDaoTest.java | 32 +- .../ch/eitchnet/xmlpers/test/LockingTest.java | 32 +- .../xmlpers/test/ObjectDaoBookTest.java | 32 +- .../xmlpers/test/ObjectDaoResourceTest.java | 32 +- .../ch/eitchnet/xmlpers/test/RealmTest.java | 15 + .../xmlpers/test/TransactionResultTest.java | 32 +- .../ch/eitchnet/xmlpers/test/XmlTestMain.java | 32 +- .../xmlpers/test/impl/BookContextFactory.java | 15 + .../xmlpers/test/impl/BookDomParser.java | 32 +- .../xmlpers/test/impl/BookParserFactory.java | 32 +- .../xmlpers/test/impl/BookSaxParser.java | 32 +- .../test/impl/ResourceContextFactory.java | 15 + .../xmlpers/test/impl/ResourceDomParser.java | 32 +- .../test/impl/ResourceParserFactory.java | 32 +- .../xmlpers/test/impl/ResourceSaxParser.java | 32 +- .../xmlpers/test/impl/TestConstants.java | 32 +- .../ch/eitchnet/xmlpers/test/model/Book.java | 32 +- .../xmlpers/test/model/ModelBuilder.java | 32 +- .../xmlpers/test/model/Parameter.java | 32 +- .../eitchnet/xmlpers/test/model/Resource.java | 32 +- 63 files changed, 1024 insertions(+), 1534 deletions(-) delete mode 100644 COPYING delete mode 100644 COPYING.LESSER create mode 100644 LICENSE diff --git a/COPYING b/COPYING deleted file mode 100644 index 94a9ed024..000000000 --- a/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program 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. - - This program 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 this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/COPYING.LESSER b/COPYING.LESSER deleted file mode 100644 index 65c5ca88a..000000000 --- a/COPYING.LESSER +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java b/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java index 1c78ff5d3..3168364cd 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java index ffd55b2e9..2a4eeb3aa 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java b/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java index 0623b1ef5..f05aac811 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java b/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java index 2b80e64b0..7e5c7ee52 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java b/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java index f60211abe..ff4fd47ed 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.api; public enum IoOperation { diff --git a/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java b/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java index c2f80da0d..5459532dd 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java b/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java index a67ad0b65..761513488 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.api; import java.util.ArrayList; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index aba2fc7b5..a7c27d40b 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java index 5a4d8a2ec..b1ba612cf 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java index 482389e60..40fd39738 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java index 9655d5e35..0faa8688b 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java index 2735d4aeb..3feb45dd9 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.api; import ch.eitchnet.xmlpers.objref.ObjectRef; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java index da0a8056e..8be71335d 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java index 1d4d93fab..2790a394c 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java index 16fb0d667..ecbf0ba6d 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java index a83eeb014..4cc3e82fa 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.api; import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java index a507a5204..cd3c50de8 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java b/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java index f6e660175..82ea59631 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java index 7f93e02a9..987a8ffbc 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.api; public enum TransactionCloseStrategy { diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java index 02dc986cf..6ecddc843 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.api; import java.util.Date; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java index 15b6a3ced..42900e5a4 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.api; public enum TransactionState { diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java index d56835be8..258118918 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java index 7a758c901..182ad89c9 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.api; diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java index 21f2ee04b..32eff87f7 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.impl; diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java index b072576aa..547ffc262 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.impl; import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index c74ce27d1..90e5fdd6a 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.impl; diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java index a0f593c07..0dd3c231a 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 + * Copyright 2013 Robert von Burg * - * This file is part of ch.eitchnet.java.xmlpers - * - * ch.eitchnet.java.xmlpers is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ch.eitchnet.java.xmlpers 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ch.eitchnet.java.xmlpers. If not, see . + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.xmlpers.impl; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java index 7c7060e8a..bb4d206c1 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import java.io.File; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java index dec93808e..a110df9b1 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import java.io.File; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java index e810af235..caa0316ee 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import java.text.MessageFormat; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java index 356bdff4e..bc6810914 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import java.io.File; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java index 7a2a64412..67fd55562 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import java.util.HashMap; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java b/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java index b47e87d2a..d408c758d 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import ch.eitchnet.utils.helper.StringHelper; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java index 65640ab24..3776544c9 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import java.io.File; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java index fefcba6ef..f77e274ef 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import java.io.File; diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java index be34fff63..738a2cff7 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.objref; import java.io.File; diff --git a/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java b/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java index cdd7d24be..0a0fe4a9e 100644 --- a/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java +++ b/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.util; import java.text.MessageFormat; diff --git a/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java b/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java index f3e06cf60..fa2e9c9bf 100644 --- a/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java +++ b/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.util; diff --git a/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java b/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java index 2632531c9..bd67391b5 100644 --- a/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java +++ b/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.util; import java.text.MessageFormat; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java index 9b6086e84..eaea56e6a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.test; import java.io.File; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java index 150b18f36..c50e125cc 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java index 61b366b5f..ce8574cb6 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java index 591dcc542..840d95e63 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java index 69b1f2edf..7923a8ff1 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java b/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java index aeef634ca..534e18183 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.test; import static org.junit.Assert.assertNotNull; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java b/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java index 8aa381dc7..537dc5746 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java index decef2036..8ea42fa45 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java index c092b735d..ccb808507 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.test.impl; import ch.eitchnet.xmlpers.api.PersistenceContext; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java index 291a09250..a07e19be8 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java index 8ca161052..9de53c54e 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java index 37acd83a0..2a9338683 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java index 977ca09f3..03d67d7fc 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java @@ -1,3 +1,18 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.xmlpers.test.impl; import ch.eitchnet.xmlpers.api.PersistenceContext; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java index 9b86472e8..f9b4ecea4 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java index 002b83ad8..72787b11a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java index f3cdf82d4..7a87057f7 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java index 18ec8cafd..cfa62b831 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.impl; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java index 21318298c..536be946c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.model; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java index 1f6dfc432..8d9576b40 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.model; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java index 1f6ee3b25..d3b422ec0 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.model; diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java index b647b9d7c..5567f3e47 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.xmlpers.test.model; From a43be2cb8868a549c48c0a7f1292133140f68131 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 18 Dec 2013 17:46:04 +0100 Subject: [PATCH 223/457] [New] Added DBC class for helping in assertions --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/dbc/DBC.java diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java new file mode 100644 index 000000000..3ea0bb26e --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.dbc; + +import java.text.MessageFormat; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public enum DBC { + + PRE { + public void assertNotEmpty(String msg, String value) { + if (StringHelper.isEmpty(value)) { + String ex = "Illegal empty value: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + @Override + public void assertNotNull(String msg, Object value) { + if (value == null) { + String ex = "Illegal null value: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + }; + + public abstract void assertNotEmpty(String msg, String value); + public abstract void assertNotNull(String msg, Object value); + + public class DbcException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public DbcException(String message) { + super(message); + } + } +} From da835fcb7828af30c1f05d8e69937e7b90c2ee08 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 18 Dec 2013 17:46:26 +0100 Subject: [PATCH 224/457] [New] added FileHelper.readStreamToString() --- .../ch/eitchnet/utils/helper/FileHelper.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 5db00ea85..862598d9b 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -27,6 +27,7 @@ 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; @@ -101,12 +102,40 @@ public class FileHelper { return sb.toString(); } catch (FileNotFoundException e) { - throw new RuntimeException("Filed does not exist " + file.getAbsolutePath()); //$NON-NLS-1$ + 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 * From 6305bdc69fec99a60d58592e795ecffecf88e44e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 22 Dec 2013 23:03:14 +0100 Subject: [PATCH 225/457] [Minor] caught an NPE when the Transaction fails but no cause is set --- .../eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index 90e5fdd6a..650df2614 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -283,7 +283,9 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { if (this.txResult.getState() == TransactionState.FAILED) { String msg = "Failed to commit TX due to underlying exception: {0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, this.txResult.getFailCause().getMessage()); + String causeMsg = this.txResult.getFailCause() == null ? null : this.txResult.getFailCause() + .getMessage(); + msg = MessageFormat.format(msg, causeMsg); throw new XmlPersistenceException(msg, this.txResult.getFailCause()); } } From ae5bba3b4004d9ded08dbff24bbacf13d9fb5dbe Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 22 Dec 2013 23:05:06 +0100 Subject: [PATCH 226/457] [New] Added DomUtil --- .../ch/eitchnet/utils/helper/DomUtil.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/helper/DomUtil.java diff --git a/src/main/java/ch/eitchnet/utils/helper/DomUtil.java b/src/main/java/ch/eitchnet/utils/helper/DomUtil.java new file mode 100644 index 000000000..20851de57 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/DomUtil.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.helper; + +import java.text.MessageFormat; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** + * @author Robert von Burg + */ +public class DomUtil { + + public static DocumentBuilder createDocumentBuilder() { + try { + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + return docBuilder; + } catch (ParserConfigurationException e) { + String msg = "No Xml Parser could be loaded: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, e.getMessage()); + throw new RuntimeException(msg, e); + } + } +} From 5820265d1ef3706514bcf72b09db4d5b9aee64b3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 23 Dec 2013 01:21:42 +0100 Subject: [PATCH 227/457] [Minor] minor code cleanup --- .../java/ch/eitchnet/xmlpers/api/TransactionResult.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java index 6ecddc843..68d05d6ec 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java @@ -16,6 +16,7 @@ package ch.eitchnet.xmlpers.api; import java.util.Date; +import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -32,6 +33,11 @@ public class TransactionResult { private long closeDuration; private Map modificationByKey; + + public TransactionResult() { + this.state = TransactionState.OPEN; + this.modificationByKey = new HashMap<>(); + } /** * @return the realm From 24a62bb01dec38ce76bdd2e2261ebf321b7fcbe6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 24 Dec 2013 01:47:11 +0100 Subject: [PATCH 228/457] [Bugfix] fixed a bug where queryTypeSet() failed on MetadataDao --- src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java b/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java index 5459532dd..fd6f117b4 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java @@ -152,10 +152,9 @@ public class MetadataDao { Set keySet = new HashSet(); File[] subTypeFiles = queryPath.listFiles(); for (File subTypeFile : subTypeFiles) { - if (subTypeFile.isFile()) { - String filename = subTypeFile.getName(); - String id = FilenameUtility.getId(filename); - keySet.add(id); + if (subTypeFile.isDirectory()) { + String type = subTypeFile.getName(); + keySet.add(type); } } From 744920a62be08eac7d96c21f4b54425b173b26ee Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 24 Dec 2013 02:42:29 +0100 Subject: [PATCH 229/457] [Minor] removed use of deprecated JUnit classes --- .../privilege/test/PrivilegeTest.java | 31 +++++++------- .../ch/eitchnet/privilege/test/XmlTest.java | 42 +++++++++---------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index c01a404c1..6d3b1b3ea 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -15,13 +15,15 @@ */ package ch.eitchnet.privilege.test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import junit.framework.Assert; - import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -53,6 +55,7 @@ import ch.eitchnet.utils.helper.FileHelper; * * @author Robert von Burg */ +@SuppressWarnings("nls") public class PrivilegeTest { private static final String ADMIN = "admin"; @@ -80,7 +83,7 @@ public class PrivilegeTest { public static void init() throws Exception { try { destroy(); - + // copy configuration to tmp String pwd = System.getProperty("user.dir"); @@ -146,7 +149,7 @@ public class PrivilegeTest { private void login(String username, byte[] password) { Certificate certificate = privilegeHandler.authenticate(username, password); - Assert.assertTrue("Certificate is null!", certificate != null); + assertTrue("Certificate is null!", certificate != null); PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); PrivilegeContext.set(privilegeContext); } @@ -350,10 +353,10 @@ public class PrivilegeTest { // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); PrivilegeContext.get().validateAction(restrictable); - Assert.fail("Should fail as bob does not have role app"); + fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; - Assert.assertEquals(msg, e.getLocalizedMessage()); + assertEquals(msg, e.getLocalizedMessage()); } finally { logout(); } @@ -397,10 +400,10 @@ public class PrivilegeTest { // testFailAuthAsTedNoPass // Will fail because user ted has no password login(TED, ArraysHelper.copyOf(PASS_TED)); - org.junit.Assert.fail("User Ted may not authenticate because the user has no password!"); + fail("User Ted may not authenticate because the user has no password!"); } catch (PrivilegeException e) { String msg = "User ted has no password and may not login!"; - Assert.assertEquals(msg, e.getMessage()); + assertEquals(msg, e.getMessage()); } finally { logout(); } @@ -451,10 +454,10 @@ public class PrivilegeTest { new HashMap()); certificate = PrivilegeContext.get().getCertificate(); privilegeHandler.addOrReplaceUser(certificate, userRep, null); - Assert.fail("User bob may not add a user as bob does not have admin rights!"); + fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate; - Assert.assertEquals(msg, e.getMessage()); + assertEquals(msg, e.getMessage()); } finally { logout(); } @@ -501,10 +504,10 @@ public class PrivilegeTest { // testFailAuthUserBob // Will fail as user bob has no role privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); - org.junit.Assert.fail("User Bob may not authenticate because the user has no role"); + fail("User Bob may not authenticate because the user has no role"); } catch (PrivilegeException e) { String msg = "User bob does not have any roles defined!"; - Assert.assertEquals(msg, e.getMessage()); + assertEquals(msg, e.getMessage()); } finally { logout(); } @@ -527,10 +530,10 @@ public class PrivilegeTest { // testFailAuthAsBob // Will fail because user bob is not yet enabled privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); - org.junit.Assert.fail("User Bob may not authenticate because the user is not yet enabled!"); + fail("User Bob may not authenticate because the user is not yet enabled!"); } catch (PrivilegeException e) { String msg = "User bob does not have state ENABLED and can not login!"; - Assert.assertEquals(msg, e.getMessage()); + assertEquals(msg, e.getMessage()); } finally { logout(); } diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 719e271d9..7506a933b 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -15,6 +15,9 @@ */ package ch.eitchnet.privilege.test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -25,8 +28,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import junit.framework.Assert; - import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -51,13 +52,10 @@ import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg - * */ +@SuppressWarnings("nls") public class XmlTest { - /** - * - */ private static final String TARGET_TEST = "./target/test"; private static final Logger logger = LoggerFactory.getLogger(XmlTest.class); @@ -109,17 +107,17 @@ public class XmlTest { logger.info(containerModel.toString()); // assert all objects read - Assert.assertNotNull(containerModel.getParameterMap()); - Assert.assertNotNull(containerModel.getPolicies()); - Assert.assertNotNull(containerModel.getEncryptionHandlerClassName()); - Assert.assertNotNull(containerModel.getEncryptionHandlerParameterMap()); - Assert.assertNotNull(containerModel.getPersistenceHandlerClassName()); - Assert.assertNotNull(containerModel.getPersistenceHandlerParameterMap()); + assertNotNull(containerModel.getParameterMap()); + assertNotNull(containerModel.getPolicies()); + assertNotNull(containerModel.getEncryptionHandlerClassName()); + assertNotNull(containerModel.getEncryptionHandlerParameterMap()); + assertNotNull(containerModel.getPersistenceHandlerClassName()); + assertNotNull(containerModel.getPersistenceHandlerParameterMap()); - Assert.assertEquals(1, containerModel.getParameterMap().size()); - Assert.assertEquals(1, containerModel.getPolicies().size()); - Assert.assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); - Assert.assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); + assertEquals(1, containerModel.getParameterMap().size()); + assertEquals(1, containerModel.getPolicies().size()); + assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); + assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); // TODO extend assertions to actual model } @@ -150,7 +148,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - Assert.assertEquals("2ABD3442EEC8BCEC5BEE365AAB6DB2FD4E1789325425CB1E017E900582525685", fileHash); + assertEquals("2ABD3442EEC8BCEC5BEE365AAB6DB2FD4E1789325425CB1E017E900582525685", fileHash); } @Test @@ -161,12 +159,12 @@ public class XmlTest { XmlHelper.parseDocument(xmlFile, xmlHandler); List users = xmlHandler.getUsers(); - Assert.assertNotNull(users); + assertNotNull(users); List roles = xmlHandler.getRoles(); - Assert.assertNotNull(roles); + assertNotNull(roles); - Assert.assertEquals(2, users.size()); - Assert.assertEquals(4, roles.size()); + assertEquals(2, users.size()); + assertEquals(4, roles.size()); // TODO extend assertions to actual model } @@ -212,6 +210,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - Assert.assertEquals("A2127D20A61E00BCDBB61569CD2B200C4F0F111C972BAC3B1E54DF3B2FCDC8BE", fileHash); + assertEquals("A2127D20A61E00BCDBB61569CD2B200C4F0F111C972BAC3B1E54DF3B2FCDC8BE", fileHash); } } From ff52be1c071a87591c18f1df55e4454c93b9da5f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 24 Dec 2013 02:42:33 +0100 Subject: [PATCH 230/457] [Minor] removed use of deprecated JUnit classes --- .../utils/helper/BaseDecodingTest.java | 77 +++++++++---------- .../utils/helper/BaseEncodingTest.java | 76 +++++++++--------- 2 files changed, 76 insertions(+), 77 deletions(-) diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java index 7444a0ef6..a77e5ee5c 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java @@ -21,17 +21,16 @@ import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32Dmedia; import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32Hex; import static ch.eitchnet.utils.helper.BaseEncoding.fromBase64; import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; -import junit.framework.Assert; +import static org.junit.Assert.assertEquals; import org.junit.Test; -import org.junit.runners.JUnit4; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Robert von Burg - * */ +@SuppressWarnings("nls") public class BaseDecodingTest { public static final String PROP_RUN_PERF_TESTS = "ch.eitchnet.utils.test.runPerfTests"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(BaseDecodingTest.class); @@ -45,59 +44,59 @@ public class BaseDecodingTest { @Test public void testBase64() { - Assert.assertEquals("", fromBase64("")); - Assert.assertEquals("f", fromBase64("Zg==")); - Assert.assertEquals("fo", fromBase64("Zm8=")); - Assert.assertEquals("foo", fromBase64("Zm9v")); - Assert.assertEquals("foob", fromBase64("Zm9vYg==")); - Assert.assertEquals("fooba", fromBase64("Zm9vYmE=")); - Assert.assertEquals("foobar", fromBase64("Zm9vYmFy")); + assertEquals("", fromBase64("")); + assertEquals("f", fromBase64("Zg==")); + assertEquals("fo", fromBase64("Zm8=")); + assertEquals("foo", fromBase64("Zm9v")); + assertEquals("foob", fromBase64("Zm9vYg==")); + assertEquals("fooba", fromBase64("Zm9vYmE=")); + assertEquals("foobar", fromBase64("Zm9vYmFy")); } @Test public void testBase32() { - Assert.assertEquals("", fromBase32("")); - Assert.assertEquals("f", fromBase32("MY======")); - Assert.assertEquals("fo", fromBase32("MZXQ====")); - Assert.assertEquals("foo", fromBase32("MZXW6===")); - Assert.assertEquals("foob", fromBase32("MZXW6YQ=")); - Assert.assertEquals("fooba", fromBase32("MZXW6YTB")); - Assert.assertEquals("foobar", fromBase32("MZXW6YTBOI======")); + assertEquals("", fromBase32("")); + assertEquals("f", fromBase32("MY======")); + assertEquals("fo", fromBase32("MZXQ====")); + assertEquals("foo", fromBase32("MZXW6===")); + assertEquals("foob", fromBase32("MZXW6YQ=")); + assertEquals("fooba", fromBase32("MZXW6YTB")); + assertEquals("foobar", fromBase32("MZXW6YTBOI======")); } @Test public void testBase32Hex() { - Assert.assertEquals("", fromBase32Hex("")); - Assert.assertEquals("f", fromBase32Hex("CO======")); - Assert.assertEquals("fo", fromBase32Hex("CPNG====")); - Assert.assertEquals("foo", fromBase32Hex("CPNMU===")); - Assert.assertEquals("foob", fromBase32Hex("CPNMUOG=")); - Assert.assertEquals("fooba", fromBase32Hex("CPNMUOJ1")); - Assert.assertEquals("foobar", fromBase32Hex("CPNMUOJ1E8======")); + assertEquals("", fromBase32Hex("")); + assertEquals("f", fromBase32Hex("CO======")); + assertEquals("fo", fromBase32Hex("CPNG====")); + assertEquals("foo", fromBase32Hex("CPNMU===")); + assertEquals("foob", fromBase32Hex("CPNMUOG=")); + assertEquals("fooba", fromBase32Hex("CPNMUOJ1")); + assertEquals("foobar", fromBase32Hex("CPNMUOJ1E8======")); } @Test public void testBase32Dmedia() { - Assert.assertEquals("", fromBase32Dmedia("")); - Assert.assertEquals("binary foo", fromBase32Dmedia("FCNPVRELI7J9FUUI")); - Assert.assertEquals("f", fromBase32Dmedia("FR======")); - Assert.assertEquals("fo", fromBase32Dmedia("FSQJ====")); - Assert.assertEquals("foo", fromBase32Dmedia("FSQPX===")); - Assert.assertEquals("foob", fromBase32Dmedia("FSQPXRJ=")); - Assert.assertEquals("fooba", fromBase32Dmedia("FSQPXRM4")); - Assert.assertEquals("foobar", fromBase32Dmedia("FSQPXRM4HB======")); + assertEquals("", fromBase32Dmedia("")); + assertEquals("binary foo", fromBase32Dmedia("FCNPVRELI7J9FUUI")); + assertEquals("f", fromBase32Dmedia("FR======")); + assertEquals("fo", fromBase32Dmedia("FSQJ====")); + assertEquals("foo", fromBase32Dmedia("FSQPX===")); + assertEquals("foob", fromBase32Dmedia("FSQPXRJ=")); + assertEquals("fooba", fromBase32Dmedia("FSQPXRM4")); + assertEquals("foobar", fromBase32Dmedia("FSQPXRM4HB======")); } @Test public void testBase16() { - Assert.assertEquals("", fromBase16("")); - Assert.assertEquals("f", fromBase16("66")); - Assert.assertEquals("fo", fromBase16("666F")); - Assert.assertEquals("foo", fromBase16("666F6F")); - Assert.assertEquals("foob", fromBase16("666F6F62")); - Assert.assertEquals("fooba", fromBase16("666F6F6261")); - Assert.assertEquals("foobar", fromBase16("666F6F626172")); + assertEquals("", fromBase16("")); + assertEquals("f", fromBase16("66")); + assertEquals("fo", fromBase16("666F")); + assertEquals("foo", fromBase16("666F6F")); + assertEquals("foob", fromBase16("666F6F62")); + assertEquals("fooba", fromBase16("666F6F6261")); + assertEquals("foobar", fromBase16("666F6F626172")); } @Test diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java index 15a62eb32..4c2da50d7 100644 --- a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java @@ -22,7 +22,7 @@ import static ch.eitchnet.utils.helper.BaseEncoding.toBase32; import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Dmedia; import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; import static ch.eitchnet.utils.helper.BaseEncoding.toBase64; -import junit.framework.Assert; +import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; @@ -30,66 +30,66 @@ import org.slf4j.LoggerFactory; /** * @author Robert von Burg - * */ +@SuppressWarnings("nls") public class BaseEncodingTest { private static final Logger logger = LoggerFactory.getLogger(BaseEncodingTest.class); @Test public void testBase64() { - Assert.assertEquals("", toBase64("")); - Assert.assertEquals("Zg==", toBase64("f")); - Assert.assertEquals("Zm8=", toBase64("fo")); - Assert.assertEquals("Zm9v", toBase64("foo")); - Assert.assertEquals("Zm9vYg==", toBase64("foob")); - Assert.assertEquals("Zm9vYmE=", toBase64("fooba")); - Assert.assertEquals("Zm9vYmFy", toBase64("foobar")); + assertEquals("", toBase64("")); + assertEquals("Zg==", toBase64("f")); + assertEquals("Zm8=", toBase64("fo")); + assertEquals("Zm9v", toBase64("foo")); + assertEquals("Zm9vYg==", toBase64("foob")); + assertEquals("Zm9vYmE=", toBase64("fooba")); + assertEquals("Zm9vYmFy", toBase64("foobar")); } @Test public void testBase32() { - Assert.assertEquals("", toBase32("")); - Assert.assertEquals("MY======", toBase32("f")); - Assert.assertEquals("MZXQ====", toBase32("fo")); - Assert.assertEquals("MZXW6===", toBase32("foo")); - Assert.assertEquals("MZXW6YQ=", toBase32("foob")); - Assert.assertEquals("MZXW6YTB", toBase32("fooba")); - Assert.assertEquals("MZXW6YTBOI======", toBase32("foobar")); + assertEquals("", toBase32("")); + assertEquals("MY======", toBase32("f")); + assertEquals("MZXQ====", toBase32("fo")); + assertEquals("MZXW6===", toBase32("foo")); + assertEquals("MZXW6YQ=", toBase32("foob")); + assertEquals("MZXW6YTB", toBase32("fooba")); + assertEquals("MZXW6YTBOI======", toBase32("foobar")); } @Test public void testBase32Hex() { - Assert.assertEquals("", toBase32Hex("")); - Assert.assertEquals("CO======", toBase32Hex("f")); - Assert.assertEquals("CPNG====", toBase32Hex("fo")); - Assert.assertEquals("CPNMU===", toBase32Hex("foo")); - Assert.assertEquals("CPNMUOG=", toBase32Hex("foob")); - Assert.assertEquals("CPNMUOJ1", toBase32Hex("fooba")); - Assert.assertEquals("CPNMUOJ1E8======", toBase32Hex("foobar")); + assertEquals("", toBase32Hex("")); + assertEquals("CO======", toBase32Hex("f")); + assertEquals("CPNG====", toBase32Hex("fo")); + assertEquals("CPNMU===", toBase32Hex("foo")); + assertEquals("CPNMUOG=", toBase32Hex("foob")); + assertEquals("CPNMUOJ1", toBase32Hex("fooba")); + assertEquals("CPNMUOJ1E8======", toBase32Hex("foobar")); } @Test public void testBase32Dmedia() { - Assert.assertEquals("", toBase32Dmedia("")); - Assert.assertEquals("FCNPVRELI7J9FUUI", toBase32Dmedia("binary foo")); - Assert.assertEquals("FR======", toBase32Dmedia("f")); - Assert.assertEquals("FSQJ====", toBase32Dmedia("fo")); - Assert.assertEquals("FSQPX===", toBase32Dmedia("foo")); - Assert.assertEquals("FSQPXRJ=", toBase32Dmedia("foob")); - Assert.assertEquals("FSQPXRM4", toBase32Dmedia("fooba")); - Assert.assertEquals("FSQPXRM4HB======", toBase32Dmedia("foobar")); + assertEquals("", toBase32Dmedia("")); + assertEquals("FCNPVRELI7J9FUUI", toBase32Dmedia("binary foo")); + assertEquals("FR======", toBase32Dmedia("f")); + assertEquals("FSQJ====", toBase32Dmedia("fo")); + assertEquals("FSQPX===", toBase32Dmedia("foo")); + assertEquals("FSQPXRJ=", toBase32Dmedia("foob")); + assertEquals("FSQPXRM4", toBase32Dmedia("fooba")); + assertEquals("FSQPXRM4HB======", toBase32Dmedia("foobar")); } @Test public void testBase16() { - Assert.assertEquals("", toBase16("")); - Assert.assertEquals("66", toBase16("f")); - Assert.assertEquals("666F", toBase16("fo")); - Assert.assertEquals("666F6F", toBase16("foo")); - Assert.assertEquals("666F6F62", toBase16("foob")); - Assert.assertEquals("666F6F6261", toBase16("fooba")); - Assert.assertEquals("666F6F626172", toBase16("foobar")); + assertEquals("", toBase16("")); + assertEquals("66", toBase16("f")); + assertEquals("666F", toBase16("fo")); + assertEquals("666F6F", toBase16("foo")); + assertEquals("666F6F62", toBase16("foob")); + assertEquals("666F6F6261", toBase16("fooba")); + assertEquals("666F6F626172", toBase16("foobar")); } @Test From d35baa5a48c03f7a0f00cdfc08c8d0d6ed7e5455 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 24 Dec 2013 02:42:49 +0100 Subject: [PATCH 231/457] [Minor] set some loggers to debug --- .../java/ch/eitchnet/xmlpers/api/FileIo.java | 26 ++++++++++++------- .../xmlpers/objref/LockableObject.java | 6 +++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java b/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java index f05aac811..23f917e4f 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java @@ -95,8 +95,10 @@ public class FileIo { throw new XmlException(msg, e); } - String msg = "Wrote SAX to {0}"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); + if (logger.isDebugEnabled()) { + String msg = "Wrote SAX to {0}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); + } } public void readSax(PersistenceContext ctx) { @@ -110,8 +112,10 @@ public class FileIo { DefaultHandler defaultHandler = saxParser.getDefaultHandler(); sp.parse(this.path, defaultHandler); - String msg = "SAX parsed file {0}"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); + if (logger.isDebugEnabled()) { + String msg = "SAX parsed file {0}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.path.getAbsolutePath())); + } ctx.setObject(saxParser.getObject()); @@ -157,8 +161,10 @@ public class FileIo { Source xmlSource = new DOMSource(document); transformer.transform(xmlSource, result); - String msg = MessageFormat.format("Wrote DOM to {0}", this.path.getAbsolutePath()); //$NON-NLS-1$ - logger.info(msg); + if (logger.isDebugEnabled()) { + String msg = MessageFormat.format("Wrote DOM to {0}", this.path.getAbsolutePath()); //$NON-NLS-1$ + logger.info(msg); + } } catch (TransformerFactoryConfigurationError | TransformerException e) { @@ -183,9 +189,11 @@ public class FileIo { DomParser domParser = ctx.getParserFactor().getDomParser(); domParser.fromDom(document); - String msg = "DOM parsed file {0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, this.path.getAbsolutePath()); - logger.info(msg); + if (logger.isDebugEnabled()) { + String msg = "DOM parsed file {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.path.getAbsolutePath()); + logger.info(msg); + } ctx.setObject(domParser.getObject()); diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java index caa0316ee..760b8234f 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java @@ -51,7 +51,8 @@ public class LockableObject { msg = MessageFormat.format(msg, StringHelper.formatMillisecondsDuration(tryLockTime), this.toString()); throw new XmlPersistenceException(msg); } - logger.info("locked " + this.toString()); //$NON-NLS-1$ + if (logger.isDebugEnabled()) + logger.debug("locked " + this.toString()); //$NON-NLS-1$ } catch (InterruptedException e) { throw new XmlPersistenceException("Thread interrupted: " + e.getMessage(), e); //$NON-NLS-1$ } @@ -62,6 +63,7 @@ public class LockableObject { */ public void unlock() { this.lock.unlock(); - logger.info("unlocking " + this.toString()); //$NON-NLS-1$ + if (logger.isDebugEnabled()) + logger.debug("unlocking " + this.toString()); //$NON-NLS-1$ } } From 85db07a002adf1c70982e69af743e2da3124718b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 11:55:55 +0100 Subject: [PATCH 232/457] [Minor] fixed issue where eclipse couldn't validate log4j.xml --- src/test/resources/log4j.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml index a35a3c351..0a2a73d06 100644 --- a/src/test/resources/log4j.xml +++ b/src/test/resources/log4j.xml @@ -1,5 +1,6 @@ - + From 4df40f28853f5d64ebe6a3194d5c1ee463c21ad4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 11:55:59 +0100 Subject: [PATCH 233/457] [Minor] fixed issue where eclipse couldn't validate log4j.xml --- src/test/resources/log4j.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml index a35a3c351..0a2a73d06 100644 --- a/src/test/resources/log4j.xml +++ b/src/test/resources/log4j.xml @@ -1,5 +1,6 @@ - + From 54fd73942b166437a9925e46d72b515c8cbb3005 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 11:56:02 +0100 Subject: [PATCH 234/457] [Minor] fixed issue where eclipse couldn't validate log4j.xml --- src/test/resources/log4j.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml index 2f49b0dca..f223bc5cb 100644 --- a/src/test/resources/log4j.xml +++ b/src/test/resources/log4j.xml @@ -1,5 +1,6 @@ - + From 15a245d94e50bbb443a4feb284ae1a4620efb2a3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 13:10:58 +0100 Subject: [PATCH 235/457] [Minor] added new initialization to PrivilegeInitializationHelper Configuration can now be parsed and then passed to an initialization method --- .../helper/PrivilegeInitializationHelper.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index 435f4faec..ba51d5556 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -84,6 +84,20 @@ public class PrivilegeInitializationHelper { PrivilegeConfigSaxReader xmlHandler = new PrivilegeConfigSaxReader(containerModel); XmlHelper.parseDocument(privilegeConfigInputStream, xmlHandler); + return initializeFromXml(containerModel); + } + + /** + * Initializes the {@link PrivilegeHandler} by initializing from the given {@link PrivilegeContainerModel} + * + * @param containerModel + * the configuration for the {@link PrivilegeHandler} + * + * @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and + * {@link PersistenceHandler} are set and initialized as well + */ + public static PrivilegeHandler initializeFromXml(PrivilegeContainerModel containerModel) { + // initialize encryption handler String encryptionHandlerClassName = containerModel.getEncryptionHandlerClassName(); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); From 2e2263128f43d57d242277508a02f7e312ce2761 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 14:35:30 +0100 Subject: [PATCH 236/457] [New] code cleanup and new hashing methods - Cleaned up all i18n compiler warnings in all classes - Added new public hashing methods to pass the hashAlgorithm - Important additional change: HASH table is now lower case. --- .../ch/eitchnet/utils/helper/ByteHelper.java | 28 ++-- .../ch/eitchnet/utils/helper/ClassHelper.java | 81 ++++++++- .../eitchnet/utils/helper/ProcessHelper.java | 154 ++++++++++-------- .../utils/helper/PropertiesHelper.java | 9 +- .../eitchnet/utils/helper/StringHelper.java | 148 ++++++++++++----- .../eitchnet/utils/helper/SystemHelper.java | 45 ++--- .../utils/objectfilter/ObjectCache.java | 26 ++- .../GenerateReverseBaseEncodingAlphabets.java | 3 +- .../utils/objectfilter/ObjectFilterTest.java | 18 +- 9 files changed, 340 insertions(+), 172 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java index b9d4170c3..c9657a51d 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java @@ -33,7 +33,7 @@ public class ByteHelper { public static long toLong(byte[] bytes) { if (bytes.length != 8) - throw new IllegalArgumentException("The input byte array for a long must have 8 values"); + 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) // @@ -57,7 +57,7 @@ public class ByteHelper { public static int toInt(byte[] bytes) { if (bytes.length != 4) - throw new IllegalArgumentException("The input byte array for a long must have 4 values"); + 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) // @@ -92,7 +92,7 @@ public class ByteHelper { /** * Formats the given byte array to a binary string, separating each byte by a space * - * @param b + * @param bytes * the byte to format to a binary string * * @return the binary string @@ -102,7 +102,7 @@ public class ByteHelper { for (byte b : bytes) { sb.append(asBinary(b)); - sb.append(" "); + sb.append(StringHelper.SPACE); } return sb.toString(); @@ -129,7 +129,7 @@ public class ByteHelper { sb.append(((i >>> 25) & 1)); sb.append(((i >>> 24) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 23) & 1)); sb.append(((i >>> 22) & 1)); @@ -140,7 +140,7 @@ public class ByteHelper { sb.append(((i >>> 17) & 1)); sb.append(((i >>> 16) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 15) & 1)); sb.append(((i >>> 14) & 1)); @@ -151,7 +151,7 @@ public class ByteHelper { sb.append(((i >>> 9) & 1)); sb.append(((i >>> 8) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 7) & 1)); sb.append(((i >>> 6) & 1)); @@ -186,7 +186,7 @@ public class ByteHelper { sb.append(((i >>> 57) & 1)); sb.append(((i >>> 56) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 55) & 1)); sb.append(((i >>> 54) & 1)); @@ -197,7 +197,7 @@ public class ByteHelper { sb.append(((i >>> 49) & 1)); sb.append(((i >>> 48) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 47) & 1)); sb.append(((i >>> 46) & 1)); @@ -208,7 +208,7 @@ public class ByteHelper { sb.append(((i >>> 41) & 1)); sb.append(((i >>> 40) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 39) & 1)); sb.append(((i >>> 38) & 1)); @@ -219,7 +219,7 @@ public class ByteHelper { sb.append(((i >>> 33) & 1)); sb.append(((i >>> 32) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 31) & 1)); sb.append(((i >>> 30) & 1)); @@ -230,7 +230,7 @@ public class ByteHelper { sb.append(((i >>> 25) & 1)); sb.append(((i >>> 24) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 23) & 1)); sb.append(((i >>> 22) & 1)); @@ -241,7 +241,7 @@ public class ByteHelper { sb.append(((i >>> 17) & 1)); sb.append(((i >>> 16) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 15) & 1)); sb.append(((i >>> 14) & 1)); @@ -252,7 +252,7 @@ public class ByteHelper { sb.append(((i >>> 9) & 1)); sb.append(((i >>> 8) & 1)); - sb.append(" "); + sb.append(StringHelper.SPACE); sb.append(((i >>> 7) & 1)); sb.append(((i >>> 6) & 1)); diff --git a/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java b/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java index c25fece89..b24d3cd9f 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java @@ -24,16 +24,81 @@ import java.text.MessageFormat; */ public class ClassHelper { + /** + * Returns an instance of the class' name given by instantiating the class through an empty arguments constructor + * + * @param + * the type of the class to return + * @param className + * the name of a class to instantiate through an empty arguments constructor + * + * @return the newly instantiated object from the given class name + * + * @throws IllegalArgumentException + * if the class could not be instantiated + */ @SuppressWarnings("unchecked") - public static T instantiateClass(String className) { - + public static T instantiateClass(String className) throws IllegalArgumentException { try { - Class clazz = Class.forName(className); - return (T) clazz.newInstance(); + + Class clazz = (Class) Class.forName(className); + + return clazz.getConstructor().newInstance(); + } catch (Exception e) { - String msg = "Failed to load class {0} due to error: {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, className, e.getMessage()); - throw new IllegalArgumentException(msg); + String msg = MessageFormat.format("The class {0} could not be instantiated: ", className); //$NON-NLS-1$ + throw new IllegalArgumentException(msg, e); } } -} + + /** + * Instantiates an object for the given {@link Class} using an empty arguments constructor + * + * @param + * the type of the class to return + * @param clazz + * the {@link Class} from which a new object is to be instantiated using an empty arguments constructor + * + * @return the newly instantiated object from the given {@link Class} + * + * @throws IllegalArgumentException + * if the {@link Class} could not be instantiated + */ + public static T instantiateClass(Class clazz) throws IllegalArgumentException { + try { + + return clazz.getConstructor().newInstance(); + + } catch (Exception e) { + String msg = MessageFormat.format("The class {0} could not be instantiated: ", clazz.getName()); //$NON-NLS-1$ + throw new IllegalArgumentException(msg, e); + } + } + + /** + * Loads the {@link Class} object for the given class name + * + * @param + * the type of {@link Class} to return + * @param className + * the name of the {@link Class} to load and return + * + * @return the {@link Class} object for the given class name + * + * @throws IllegalArgumentException + * if the class could not be instantiated + */ + @SuppressWarnings("unchecked") + public static Class loadClass(String className) throws IllegalArgumentException { + try { + + Class clazz = (Class) Class.forName(className); + + return clazz; + + } catch (Exception e) { + String msg = MessageFormat.format("The class {0} could not be instantiated: ", className); //$NON-NLS-1$ + throw new IllegalArgumentException(msg, e); + } + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index 6c8b908f0..a4e3f3294 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -19,6 +19,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.text.MessageFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,55 +33,62 @@ public class ProcessHelper { public static ProcessResult runCommand(String command) { final StringBuffer sb = new StringBuffer(); - sb.append("=====================================\n"); + sb.append("=====================================\n"); //$NON-NLS-1$ try { final Process process = Runtime.getRuntime().exec(command); + final int[] returnValue = new int[1]; - final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); - Thread errorIn = new Thread("errorIn") { - @Override - public void run() { - ProcessHelper.readStream(sb, "[ERROR] ", errorStream); - } - }; - errorIn.start(); + try (final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); + final BufferedReader inputStream = new BufferedReader(new InputStreamReader( + process.getInputStream()));) { - final BufferedReader inputStream = new BufferedReader(new InputStreamReader(process.getInputStream())); - Thread infoIn = new Thread("infoIn") { - @Override - public void run() { - ProcessHelper.readStream(sb, "[INFO] ", inputStream); - } - }; - infoIn.start(); + Thread errorIn = new Thread("errorIn") { //$NON-NLS-1$ + @Override + public void run() { + readStream(sb, "[ERROR] ", errorStream); //$NON-NLS-1$ + } + }; + errorIn.start(); - int returnValue = process.waitFor(); + Thread infoIn = new Thread("infoIn") { //$NON-NLS-1$ + @Override + public void run() { + readStream(sb, "[INFO] ", inputStream); //$NON-NLS-1$ + } + }; + infoIn.start(); - errorIn.join(100l); - infoIn.join(100l); - sb.append("=====================================\n"); + returnValue[0] = process.waitFor(); - return new ProcessResult(returnValue, sb.toString(), null); + errorIn.join(100l); + infoIn.join(100l); + sb.append("=====================================\n"); //$NON-NLS-1$ + } + return new ProcessResult(returnValue[0], sb.toString(), null); } catch (IOException e) { - throw new RuntimeException("Failed to perform command: " + e.getLocalizedMessage(), e); + String msg = MessageFormat.format("Failed to perform command: {0}", e.getMessage()); //$NON-NLS-1$ + throw new RuntimeException(msg, e); } catch (InterruptedException e) { - ProcessHelper.logger.error("Interrupted!"); - sb.append("[FATAL] Interrupted"); + logger.error("Interrupted!"); //$NON-NLS-1$ + sb.append("[FATAL] Interrupted"); //$NON-NLS-1$ return new ProcessResult(-1, sb.toString(), e); } } public static ProcessResult runCommand(File workingDirectory, String... commandAndArgs) { - if (!workingDirectory.exists()) - throw new RuntimeException("Working directory does not exist at " + workingDirectory.getAbsolutePath()); + if (!workingDirectory.exists()) { + String msg = "Working directory does not exist 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!"); + throw new RuntimeException("No command passed!"); //$NON-NLS-1$ final StringBuffer sb = new StringBuffer(); - sb.append("=====================================\n"); + sb.append("=====================================\n"); //$NON-NLS-1$ try { ProcessBuilder processBuilder = new ProcessBuilder(commandAndArgs); @@ -88,38 +96,42 @@ public class ProcessHelper { processBuilder.directory(workingDirectory); final Process process = processBuilder.start(); + int[] returnValue = new int[1]; - final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); - Thread errorIn = new Thread("errorIn") { - @Override - public void run() { - ProcessHelper.readStream(sb, "[ERROR] ", errorStream); - } - }; - errorIn.start(); + try (final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); + final BufferedReader inputStream = new BufferedReader(new InputStreamReader( + process.getInputStream()));) { - final BufferedReader inputStream = new BufferedReader(new InputStreamReader(process.getInputStream())); - Thread infoIn = new Thread("infoIn") { - @Override - public void run() { - ProcessHelper.readStream(sb, "[INFO] ", inputStream); - } - }; - infoIn.start(); + Thread errorIn = new Thread("errorIn") { //$NON-NLS-1$ + @Override + public void run() { + readStream(sb, "[ERROR] ", errorStream); //$NON-NLS-1$ + } + }; + errorIn.start(); - int returnValue = process.waitFor(); + Thread infoIn = new Thread("infoIn") { //$NON-NLS-1$ + @Override + public void run() { + readStream(sb, "[INFO] ", inputStream); //$NON-NLS-1$ + } + }; + infoIn.start(); - errorIn.join(100l); - infoIn.join(100l); - sb.append("=====================================\n"); + returnValue[0] = process.waitFor(); - return new ProcessResult(returnValue, sb.toString(), null); + errorIn.join(100l); + infoIn.join(100l); + sb.append("=====================================\n"); //$NON-NLS-1$ + } + + return new ProcessResult(returnValue[0], sb.toString(), null); } catch (IOException e) { - throw new RuntimeException("Failed to perform command: " + e.getLocalizedMessage(), e); + throw new RuntimeException("Failed to perform command: " + e.getLocalizedMessage(), e); //$NON-NLS-1$ } catch (InterruptedException e) { - ProcessHelper.logger.error("Interrupted!"); - sb.append("[FATAL] Interrupted"); + logger.error("Interrupted!"); //$NON-NLS-1$ + sb.append("[FATAL] Interrupted"); //$NON-NLS-1$ return new ProcessResult(-1, sb.toString(), e); } } @@ -127,24 +139,27 @@ public class ProcessHelper { public static class ProcessResult { public final int returnValue; public final String processOutput; - public final Throwable t; + public final Throwable throwable; public ProcessResult(int returnValue, String processOutput, Throwable t) { this.returnValue = returnValue; this.processOutput = processOutput; - this.t = t; + this.throwable = t; } } - private static void readStream(StringBuffer sb, String prefix, BufferedReader bufferedReader) { + static void readStream(StringBuffer sb, String prefix, BufferedReader bufferedReader) { String line; try { while ((line = bufferedReader.readLine()) != null) { - sb.append(prefix + line + "\n"); + sb.append(prefix + line + StringHelper.NEW_LINE); } } catch (IOException e) { - String msg = "Faild to read from " + prefix + " stream: " + e.getLocalizedMessage(); - sb.append("[FATAL] " + msg + "\n"); + 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); } } @@ -152,31 +167,32 @@ public class ProcessHelper { ProcessResult processResult; if (SystemHelper.isLinux()) { - processResult = ProcessHelper.runCommand("xdg-open " + pdfPath.getAbsolutePath()); + processResult = runCommand("xdg-open " + pdfPath.getAbsolutePath()); //$NON-NLS-1$ } else if (SystemHelper.isMacOS()) { - processResult = ProcessHelper.runCommand("open " + pdfPath.getAbsolutePath()); + 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 = ProcessHelper.runCommand("rundll32 url.dll,FileProtocolHandler " + pdfFile); + processResult = runCommand("rundll32 url.dll,FileProtocolHandler " + pdfFile); //$NON-NLS-1$ } else { - throw new UnsupportedOperationException("Unexpected OS: " + SystemHelper.osName); + String msg = MessageFormat.format("Unexpected OS: {0}", SystemHelper.osName); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); } - ProcessHelper.logProcessResult(processResult); + logProcessResult(processResult); } public static void logProcessResult(ProcessResult processResult) { if (processResult.returnValue == 0) { - ProcessHelper.logger.info("Process executed successfully"); + logger.info("Process executed successfully"); //$NON-NLS-1$ } else if (processResult.returnValue == -1) { - ProcessHelper.logger.error("Process execution failed:\n" + processResult.processOutput); - ProcessHelper.logger.error(processResult.t.getMessage(), processResult.t); + logger.error("Process execution failed:\n" + processResult.processOutput); //$NON-NLS-1$ + logger.error(processResult.throwable.getMessage(), processResult.throwable); } else { - ProcessHelper.logger.info("Process execution was not successful with return value:" - + processResult.returnValue + "\n" + processResult.processOutput); + String msg = "Process execution was not successful with return value:{0}\n{1}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, processResult.returnValue, processResult.processOutput)); } } } diff --git a/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java b/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java index 4e0056492..f09f66e6c 100644 --- a/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java @@ -15,11 +15,11 @@ */ package ch.eitchnet.utils.helper; +import java.text.MessageFormat; import java.util.Properties; /** * @author Robert von Burg - * */ public class PropertiesHelper { @@ -45,8 +45,11 @@ public class PropertiesHelper { public static String getProperty(Properties properties, String context, String key, String def) throws RuntimeException { String property = properties.getProperty(key, def); - if (property == null) - throw new RuntimeException("[" + context + "] Property " + key + " is not set, and no default was given!"); + 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; } diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index b322d57cc..a7eca17de 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -18,6 +18,7 @@ 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; @@ -32,15 +33,17 @@ 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$ private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); /** - * Hex char table for fast calculating of hex value + * Hex char table for fast calculating of hex values */ - private static final byte[] HEX_CHAR_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', - 'D', 'E', 'F' }; + 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 @@ -59,14 +62,15 @@ public class StringHelper { for (byte b : raw) { int v = b & 0xFF; - hex[index++] = StringHelper.HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = StringHelper.HEX_CHAR_TABLE[v & 0xF]; + hex[index++] = HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = HEX_CHAR_TABLE[v & 0xF]; } - return new String(hex, "ASCII"); + return new String(hex, "ASCII"); //$NON-NLS-1$ } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Something went wrong while converting to HEX: " + e.getLocalizedMessage(), e); + String msg = MessageFormat.format("Something went wrong while converting to HEX: {0}", e.getMessage()); //$NON-NLS-1$ + throw new RuntimeException(msg, e); } } @@ -80,7 +84,7 @@ public class StringHelper { */ public static byte[] fromHexString(String encoded) { if ((encoded.length() % 2) != 0) - throw new IllegalArgumentException("Input string must contain an even number of characters."); + 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(); @@ -102,7 +106,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static String hashMd5AsHex(String string) { - return getHexString(StringHelper.hashMd5(string.getBytes())); + return getHexString(hashMd5(string.getBytes())); } /** @@ -115,7 +119,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashMd5(String string) { - return StringHelper.hashMd5(string.getBytes()); + return hashMd5(string.getBytes()); } /** @@ -128,7 +132,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashMd5(byte[] bytes) { - return StringHelper.hash("MD5", bytes); + return hash("MD5", bytes); //$NON-NLS-1$ } /** @@ -140,7 +144,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static String hashSha1AsHex(String string) { - return getHexString(StringHelper.hashSha1(string.getBytes())); + return getHexString(hashSha1(string.getBytes())); } /** @@ -153,7 +157,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashSha1(String string) { - return StringHelper.hashSha1(string.getBytes()); + return hashSha1(string.getBytes()); } /** @@ -166,7 +170,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashSha1(byte[] bytes) { - return StringHelper.hash("SHA-1", bytes); + return hash("SHA-1", bytes); //$NON-NLS-1$ } /** @@ -178,7 +182,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static String hashSha256AsHex(String string) { - return getHexString(StringHelper.hashSha256(string.getBytes())); + return getHexString(hashSha256(string.getBytes())); } /** @@ -191,7 +195,7 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashSha256(String string) { - return StringHelper.hashSha256(string.getBytes()); + return hashSha256(string.getBytes()); } /** @@ -204,7 +208,59 @@ public class StringHelper { * @return the hash or null, if an exception was thrown */ public static byte[] hashSha256(byte[] bytes) { - return StringHelper.hash("SHA-256", 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)); } /** @@ -226,7 +282,8 @@ public class StringHelper { return hashArray; } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Algorithm " + algorithm + " does not exist!", e); + String msg = MessageFormat.format("Algorithm {0} does not exist!", algorithm); //$NON-NLS-1$ + throw new RuntimeException(msg, e); } } @@ -245,7 +302,7 @@ public class StringHelper { * @return the new string */ public static String normalizeLength(String value, int length, boolean beginning, char c) { - return StringHelper.normalizeLength(value, length, beginning, false, c); + return normalizeLength(value, length, beginning, false, c); } /** @@ -284,8 +341,8 @@ public class StringHelper { } else if (shorten) { - StringHelper.logger.warn("Shortening length of value: " + value); - StringHelper.logger.warn("Length is: " + value.length() + " max: " + length); + 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); } @@ -300,7 +357,7 @@ public class StringHelper { * returned */ public static String replaceSystemPropertiesIn(String value) { - return StringHelper.replacePropertiesIn(System.getProperties(), value); + return replacePropertiesIn(System.getProperties(), value); } /** @@ -315,10 +372,10 @@ public class StringHelper { * * @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 alue) { + public static String replacePropertiesIn(Properties properties, String value) { // get a copy of the value - String tmpValue = alue; + String tmpValue = value; // get first occurrence of $ character int pos = -1; @@ -337,8 +394,8 @@ public class StringHelper { // if no stop found, then break as another sequence should be able to start if (stop == -1) { - StringHelper.logger.error("Sequence starts at offset " + pos + " but does not end!"); - tmpValue = alue; + logger.error(MessageFormat.format("Sequence starts at offset {0} but does not end!", pos)); //$NON-NLS-1$ + tmpValue = value; break; } @@ -346,15 +403,16 @@ public class StringHelper { String sequence = tmpValue.substring(pos + 2, stop); // make sure sequence doesn't contain $ { } characters - if (sequence.contains("$") || sequence.contains("{") || sequence.contains("}")) { - StringHelper.logger.error("Enclosed sequence in offsets " + pos + " - " + stop - + " contains one of the illegal chars: $ { }: " + sequence); - tmpValue = alue; + if (sequence.contains("$") || 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}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, pos, stop, sequence); + logger.error(msg); + tmpValue = value; break; } // sequence is good, so see if we have a property for it - String property = properties.getProperty(sequence, ""); + String property = properties.getProperty(sequence, StringHelper.EMPTY); // if no property exists, then log and continue if (property.isEmpty()) { @@ -363,7 +421,7 @@ public class StringHelper { } // property exists, so replace in value - tmpValue = tmpValue.replace("${" + sequence + "}", property); + tmpValue = tmpValue.replace("${" + sequence + "}", property); //$NON-NLS-1$ //$NON-NLS-2$ } return tmpValue; @@ -377,7 +435,7 @@ public class StringHelper { * the properties in which the values must have any ${...} replaced by values of the respective key */ public static void replaceProperties(Properties properties) { - StringHelper.replaceProperties(properties, null); + replaceProperties(properties, null); } /** @@ -394,7 +452,7 @@ public class StringHelper { for (Object keyObj : properties.keySet()) { String key = (String) keyObj; String property = properties.getProperty(key); - String newProperty = StringHelper.replacePropertiesIn(properties, property); + String newProperty = replacePropertiesIn(properties, property); // try first properties if (!property.equals(newProperty)) { @@ -403,7 +461,7 @@ public class StringHelper { } else if (altProperties != null) { // try alternative properties - newProperty = StringHelper.replacePropertiesIn(altProperties, property); + 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); @@ -443,10 +501,15 @@ public class StringHelper { 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 + ". Showing " + maxContext - + " extra characters and start and end:\n"); - sb.append("context s1: " + s1.substring(start, end) + "\n"); - sb.append("context s2: " + s2.substring(start, end) + "\n"); + 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(); } @@ -519,15 +582,16 @@ public class StringHelper { */ 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"); + 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 { - throw new RuntimeException("Value " + value - + " can not be parsed to boolean! Expected case insensitive true or false"); + 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); } } } diff --git a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java index f0fa37734..3b72d38d1 100644 --- a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java @@ -32,11 +32,11 @@ public class SystemHelper { return SystemHelper.instance; } - public static final String osName = System.getProperty("os.name"); - public static final String osArch = System.getProperty("os.arch"); - public static final String osVersion = System.getProperty("os.version"); - public static final String javaVendor = System.getProperty("java.vendor"); - public static final String javaVersion = System.getProperty("java.version"); + 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 @@ -59,40 +59,41 @@ public class SystemHelper { public static String asString() { StringBuilder sb = new StringBuilder(); sb.append(SystemHelper.osName); - sb.append(" "); + sb.append(StringHelper.EMPTY); sb.append(SystemHelper.osArch); - sb.append(" "); + sb.append(StringHelper.EMPTY); sb.append(SystemHelper.osVersion); - sb.append(" "); - sb.append("on Java " + SystemHelper.javaVendor); - sb.append(" version "); + sb.append(StringHelper.EMPTY); + 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"); + return System.getProperty("user.dir"); //$NON-NLS-1$ } public static boolean isMacOS() { - return SystemHelper.osName.startsWith("Mac"); + return SystemHelper.osName.startsWith("Mac"); //$NON-NLS-1$ } public static boolean isWindows() { - return SystemHelper.osName.startsWith("Win"); + return SystemHelper.osName.startsWith("Win"); //$NON-NLS-1$ } public static boolean isLinux() { - return SystemHelper.osName.startsWith("Lin"); + return SystemHelper.osName.startsWith("Lin"); //$NON-NLS-1$ } public static boolean is32bit() { - return SystemHelper.osArch.equals("x86") || SystemHelper.osArch.equals("i386") - || SystemHelper.osArch.equals("i686"); + 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"); + return SystemHelper.osArch.equals("x86_64") || SystemHelper.osArch.equals("amd64"); //$NON-NLS-1$ //$NON-NLS-2$ } public static String getMaxMemory() { @@ -108,7 +109,13 @@ public class SystemHelper { } public static String getMemorySummary() { - return "Memory available " + SystemHelper.getMaxMemory() + " / Used: " + SystemHelper.getUsedMemory() - + " / Free:" + SystemHelper.getFreeMemory(); + StringBuilder sb = new StringBuilder(); + sb.append("Memory available "); //$NON-NLS-1$ + sb.append(SystemHelper.getMaxMemory()); + sb.append(" / Used: "); //$NON-NLS-1$ + sb.append(SystemHelper.getUsedMemory()); + sb.append(" / Free:"); //$NON-NLS-1$ + sb.append(SystemHelper.getFreeMemory()); + return sb.toString(); } } diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java index 1a9bf9fde..8739e86d1 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java @@ -15,6 +15,8 @@ */ package ch.eitchnet.utils.objectfilter; +import java.text.MessageFormat; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,6 +71,7 @@ public class ObjectCache { * @param object * @param operation */ + @SuppressWarnings("nls") public ObjectCache(long id, String key, Object object, Operation operation) { this.id = id; @@ -77,8 +80,16 @@ public class ObjectCache { this.operation = operation; if (logger.isDebugEnabled()) { - logger.debug("Instanciated Cache: ID" + this.id + " / " + key + " OP: " + this.operation - + " / " + object.toString()); + 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()); } } @@ -89,7 +100,7 @@ public class ObjectCache { */ public void setObject(Object object) { if (logger.isDebugEnabled()) { - logger.debug("Updating ID " + this.id + " to value " + object.toString()); + logger.debug(MessageFormat.format("Updating ID {0} to value {1}", this.id, object.toString())); //$NON-NLS-1$ } this.object = object; } @@ -100,9 +111,10 @@ public class ObjectCache { * @param newOperation */ public void setOperation(Operation newOperation) { - if (ObjectCache.logger.isDebugEnabled()) { - ObjectCache.logger.debug("Updating Operation of ID " + this.id + " from " + this.operation + " to " - + 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; } @@ -120,7 +132,7 @@ public class ObjectCache { public String getKey() { return this.key; } - + /** * @return the operation */ diff --git a/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java b/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java index 562368b75..5a492a2d7 100644 --- a/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java +++ b/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java @@ -19,10 +19,11 @@ import java.util.HashMap; import java.util.Map; /** - * Simple helper class to generate the reverse alphabets for {@link BaseDecoding} + * Simple helper class to generate the reverse alphabets for {@link BaseEncoding} * * @author Robert von Burg */ +@SuppressWarnings("nls") public class GenerateReverseBaseEncodingAlphabets { public static void main(String[] args) { diff --git a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java index f7a7c99df..70b6c98f7 100644 --- a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java +++ b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java @@ -25,8 +25,8 @@ import org.junit.Test; /** * @author Robert von Burg - * */ +@SuppressWarnings("nls") public class ObjectFilterTest { @Test @@ -100,9 +100,9 @@ public class ObjectFilterTest { try { filter.add(myObj); - fail("Should have failed adding twice!"); + fail("Should have failed adding twice!"); } catch (RuntimeException e) { - assertEquals("Stale State exception: Invalid + after +", e.getMessage()); + assertEquals("Stale State exception: Invalid + after +", e.getMessage()); } testAssertions(filter, 1, 1, 1, 0, 0); @@ -118,9 +118,9 @@ public class ObjectFilterTest { try { filter.remove(myObj); - fail("Should have failed removing twice!"); + fail("Should have failed removing twice!"); } catch (RuntimeException e) { - assertEquals("Stale State exception: Invalid - after -", e.getMessage()); + assertEquals("Stale State exception: Invalid - after -", e.getMessage()); } testAssertions(filter, 1, 1, 0, 0, 1); @@ -158,9 +158,9 @@ public class ObjectFilterTest { try { filter.add(myObj); - fail("Should have failed add after modify"); + fail("Should have failed add after modify"); } catch (RuntimeException e) { - assertEquals("Stale State exception: Invalid + after +=", e.getMessage()); + assertEquals("Stale State exception: Invalid + after +=", e.getMessage()); } testAssertions(filter, 1, 1, 0, 1, 0); @@ -186,9 +186,9 @@ public class ObjectFilterTest { try { filter.update(myObj); - fail("Should have failed modify after remove"); + fail("Should have failed modify after remove"); } catch (RuntimeException e) { - assertEquals("Stale State exception: Invalid += after -", e.getMessage()); + assertEquals("Stale State exception: Invalid += after -", e.getMessage()); } testAssertions(filter, 1, 1, 0, 0, 1); From 3727d3545fc9d7793051431403f319212fa1c450 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 14:37:22 +0100 Subject: [PATCH 237/457] [Minor] cleaned up all compiler warnings --- .../handler/DefaultEncryptionHandler.java | 46 +++-- .../handler/DefaultPrivilegeHandler.java | 190 +++++++++++------- .../privilege/handler/PrivilegeHandler.java | 2 +- .../helper/BootstrapConfigurationHelper.java | 1 + .../privilege/helper/ClassHelper.java | 101 ---------- .../eitchnet/privilege/helper/HashHelper.java | 88 -------- .../privilege/helper/PasswordCreaterUI.java | 5 +- .../privilege/helper/PasswordCreator.java | 5 +- .../helper/PrivilegeInitializationHelper.java | 1 + .../privilege/helper/XmlConstants.java | 1 + .../eitchnet/privilege/model/Certificate.java | 9 +- .../privilege/model/PrivilegeContext.java | 8 +- .../privilege/model/PrivilegeRep.java | 9 +- .../ch/eitchnet/privilege/model/RoleRep.java | 3 +- .../ch/eitchnet/privilege/model/UserRep.java | 13 +- .../internal/PrivilegeContainerModel.java | 17 +- .../model/internal/PrivilegeImpl.java | 9 +- .../privilege/model/internal/Role.java | 9 +- .../privilege/model/internal/User.java | 11 +- .../privilege/policy/DefaultPrivilege.java | 4 +- .../xml/PrivilegeConfigSaxReader.java | 60 +++--- .../xml/PrivilegeModelSaxReader.java | 85 ++++---- .../ch/eitchnet/privilege/test/XmlTest.java | 4 +- 23 files changed, 275 insertions(+), 406 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/HashHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 792e6b9bb..5c083dbbe 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -19,14 +19,15 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.text.MessageFormat; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.helper.HashHelper; import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.utils.helper.StringHelper; /** *

      @@ -60,27 +61,25 @@ public class DefaultEncryptionHandler implements EncryptionHandler { @Override public String convertToHash(String string) { - try { - - return HashHelper.stringToHash(this.hashAlgorithm, string); - - } catch (NoSuchAlgorithmException e) { - throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); - } catch (UnsupportedEncodingException e) { - throw new PrivilegeException("Charset ASCII is not supported!", e); - } + return convertToHash(string.getBytes()); } @Override public String convertToHash(byte[] bytes) { try { - return HashHelper.stringToHash(this.hashAlgorithm, bytes); + return StringHelper.hashAsHex(this.hashAlgorithm, bytes); - } catch (NoSuchAlgorithmException e) { - throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); - } catch (UnsupportedEncodingException e) { - throw new PrivilegeException("Charset ASCII is not supported!", e); + } catch (RuntimeException e) { + if (e.getCause() == null) + throw e; + if (e.getCause().getClass().equals(NoSuchAlgorithmException.class)) + throw new PrivilegeException( + MessageFormat.format("Algorithm {0} was not found!", this.hashAlgorithm), e.getCause()); //$NON-NLS-1$ + if (e.getCause().getClass().equals(UnsupportedEncodingException.class)) + throw new PrivilegeException("Charset ASCII is not supported!", e.getCause()); //$NON-NLS-1$ + + throw e; } } @@ -100,18 +99,21 @@ public class DefaultEncryptionHandler implements EncryptionHandler { // get hash algorithm parameters this.hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); if (this.hashAlgorithm == null || this.hashAlgorithm.isEmpty()) { - throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid"); + String msg = "[{0}] Defined parameter {1} is invalid"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XmlConstants.XML_PARAM_HASH_ALGORITHM); + throw new PrivilegeException(msg); } // test hash algorithm try { - convertToHash("test"); - DefaultEncryptionHandler.logger.info("Using hashing algorithm " + this.hashAlgorithm); + convertToHash("test"); //$NON-NLS-1$ + DefaultEncryptionHandler.logger.info(MessageFormat + .format("Using hashing algorithm {0}", this.hashAlgorithm)); //$NON-NLS-1$ } catch (Exception e) { - throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid because of underlying exception: " - + e.getLocalizedMessage(), e); + String msg = "[{0}] Defined parameter {1} is invalid because of underlying exception: {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XmlConstants.XML_PARAM_HASH_ALGORITHM, + e.getLocalizedMessage()); + throw new PrivilegeException(msg, e); } } } diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index efdfd9f2d..66fdb4ef4 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -30,7 +30,6 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.PrivilegeContext; @@ -42,6 +41,7 @@ import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.ClassHelper; /** *

      @@ -66,7 +66,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * configuration parameter to define automatic persisting on password change */ - private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; + private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; //$NON-NLS-1$ /** * slf4j logger @@ -327,14 +327,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get role Role role = this.persistenceHandler.getRole(roleName); if (role == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); + String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); } // validate that policy exists if needed String policy = privilegeRep.getPolicy(); if (policy != null && !this.policyMap.containsKey(policy)) { - throw new PrivilegeException("Policy " + policy + " for Privilege " + privilegeRep.getName() - + " does not exist"); + String msg = "Policy {0} for Privilege {1} does not exist"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policy, privilegeRep.getName()); + throw new PrivilegeException(msg); } // create new role with the additional privilege @@ -364,19 +366,21 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get user User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // ignore if user already has role Set currentRoles = user.getRoles(); if (currentRoles.contains(roleName)) { - DefaultPrivilegeHandler.logger.error("User " + username + " already has role " + roleName); + String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); //$NON-NLS-1$ + DefaultPrivilegeHandler.logger.error(msg); return; } // validate that role exists if (getRole(roleName) == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); + String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); } // create new user @@ -399,12 +403,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get role Role role = this.persistenceHandler.getRole(roleName); if (role == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("Role {0} does not exist!", roleName)); //$NON-NLS-1$ } // ignore if role does not have privilege - if (!role.hasPrivilege(privilegeName)) - throw new PrivilegeException("Role " + roleName + " does not have Privilege " + privilegeName); + if (!role.hasPrivilege(privilegeName)) { + String msg = MessageFormat.format("Role {0} does not have Privilege {1}", roleName, privilegeName); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } // create new set of privileges with out the to removed privilege Set privilegeNames = role.getPrivilegeNames(); @@ -447,13 +453,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // ignore if user does not have role Set currentRoles = user.getRoles(); if (!currentRoles.contains(roleName)) { - DefaultPrivilegeHandler.logger.error("User " + user + " does not have role " + roleName); + String msg = MessageFormat.format("User {0} does not have role {1}", user, roleName); //$NON-NLS-1$ + logger.error(msg); return; } @@ -494,7 +501,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // create new user @@ -514,7 +521,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // create new user @@ -548,7 +555,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } String passwordHash = null; @@ -587,7 +594,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // create new user @@ -611,8 +618,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Certificate certificate; try { // username must be at least 2 characters in length - if (username == null || username.length() < 2) - throw new PrivilegeException("The given username '" + username + "' is shorter than 2 characters"); + if (username == null || username.length() < 2) { + String msg = MessageFormat.format("The given username ''{0}'' is shorter than 2 characters", username); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } // and validate the password validatePassword(password); @@ -623,26 +632,32 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get user object User user = this.persistenceHandler.getUser(username); // no user means no authentication - if (user == null) - throw new AccessDeniedException("There is no user defined with the username " + username); + if (user == null) { + String msg = MessageFormat.format("There is no user defined with the username {0}", username); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } // validate password String pwHash = user.getPassword(); if (pwHash == null) - throw new AccessDeniedException("User " + username + " has no password and may not login!"); + throw new AccessDeniedException(MessageFormat.format( + "User {0} has no password and may not login!", username)); //$NON-NLS-1$ if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username); + throw new AccessDeniedException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ // validate if user is allowed to login // this also capture the trying to login of SYSTEM user - if (user.getUserState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " does not have state " + UserState.ENABLED - + " and can not login!"); + if (user.getUserState() != UserState.ENABLED) { + String msg = "User {0} does not have state {1} and can not login!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username, UserState.ENABLED); + throw new AccessDeniedException(msg); + } // validate user has at least one role Set userRoles = user.getRoles(); if (userRoles.isEmpty()) { - throw new PrivilegeException("User " + username + " does not have any roles defined!"); + throw new PrivilegeException( + MessageFormat.format("User {0} does not have any roles defined!", username)); //$NON-NLS-1$ } // get 2 auth tokens @@ -660,11 +675,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.privilegeContextMap.put(sessionId, privilegeContext); // log - DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + certificate); + DefaultPrivilegeHandler.logger.info(MessageFormat.format( + "User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$ } catch (RuntimeException e) { - DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " - + e.getLocalizedMessage()); + String msg = "User {0} Failed to authenticate: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username, e.getMessage()); + DefaultPrivilegeHandler.logger.error(msg); throw e; } finally { clearPassword(password); @@ -700,8 +717,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { IPrivilege privilege = role.getPrivilege(privilegeName); if (privilege == null) { - throw new PrivilegeException(MessageFormat.format("The Privilege {0} does not exist for role {1}", - privilegeName, roleName)); + String msg = "The Privilege {0} does not exist for role {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, roleName); + throw new PrivilegeException(msg); } privileges.put(privilegeName, privilege); @@ -712,8 +730,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegePolicy policy = getPolicy(policyName); if (policy == null) { - throw new PrivilegeException(MessageFormat.format( - "The Policy {0} does not exist for Privilege {1}", policyName, privilegeName)); + String msg = "The Policy {0} does not exist for Privilege {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policyName, privilegeName); + throw new PrivilegeException(msg); } policies.put(policyName, policy); } @@ -736,9 +755,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // return true if object was really removed boolean loggedOut = privilegeContext != null; if (loggedOut) - DefaultPrivilegeHandler.logger.info("User " + certificate.getUsername() + " logged out."); + DefaultPrivilegeHandler.logger + .info(MessageFormat.format("User {0} logged out.", certificate.getUsername())); //$NON-NLS-1$ else - DefaultPrivilegeHandler.logger.warn("User already logged out!"); + DefaultPrivilegeHandler.logger.warn("User already logged out!"); //$NON-NLS-1$ return loggedOut; } @@ -747,25 +767,30 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // certificate must not be null if (certificate == null) - throw new PrivilegeException("Certificate may not be null!"); + throw new PrivilegeException("Certificate may not be null!"); //$NON-NLS-1$ // first see if a session exists for this certificate PrivilegeContext privilegeContext = this.privilegeContextMap.get(certificate.getSessionId()); - if (privilegeContext == null) - throw new AccessDeniedException("There is no session information for " + certificate.toString()); + if (privilegeContext == null) { + String msg = MessageFormat.format("There is no session information for {0}", certificate); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } // validate certificate has not been tampered with Certificate sessionCertificate = privilegeContext.getCertificate(); - if (!sessionCertificate.equals(certificate)) - throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); + if (!sessionCertificate.equals(certificate)) { + String msg = "Received illegal certificate for session id {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, certificate.getSessionId()); + throw new PrivilegeException(msg); + } // get user object User user = this.persistenceHandler.getUser(privilegeContext.getUsername()); // if user exists, then certificate is valid if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + String msg = "Oh boy, how did this happen: No User in user map although the certificate is valid!"; //$NON-NLS-1$ + throw new PrivilegeException(msg); } // everything is ok @@ -789,15 +814,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get user object User user = this.persistenceHandler.getUser(certificate.getUsername()); if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " - + certificate); + String msg = "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, certificate); + throw new PrivilegeException(msg); } // validate user has PrivilegeAdmin role if (!user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { - throw new AccessDeniedException("User does not have " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE - + " role! Certificate: " + certificate); + String msg = "User does not have {0} role! Certificate: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE, certificate); + throw new AccessDeniedException(msg); } } @@ -810,11 +836,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void validatePassword(byte[] password) throws PrivilegeException { if (password == null || password.length == 0) { - throw new PrivilegeException("A password may not be empty!"); + throw new PrivilegeException("A password may not be empty!"); //$NON-NLS-1$ } if (password.length < 3) { - throw new PrivilegeException("The given password is shorter than 3 characters"); + throw new PrivilegeException("The given password is shorter than 3 characters"); //$NON-NLS-1$ } } @@ -848,7 +874,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PersistenceHandler persistenceHandler, Map> policyMap) { if (this.initialized) - throw new PrivilegeException("Already initialized!"); + throw new PrivilegeException("Already initialized!"); //$NON-NLS-1$ this.policyMap = policyMap; this.encryptionHandler = encryptionHandler; @@ -859,10 +885,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.autoPersistOnPasswordChange = false; } else if (autoPersistS.equals(Boolean.TRUE.toString())) { this.autoPersistOnPasswordChange = true; - logger.info("Enabling automatic persistence on password change."); + logger.info("Enabling automatic persistence on password change."); //$NON-NLS-1$ } else { - logger.error("Parameter " + PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE + " has illegal value " + autoPersistS - + ". Overriding with " + Boolean.FALSE.toString()); + String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE, autoPersistS, Boolean.FALSE); + logger.error(msg); } // validate policies on privileges of Roles @@ -886,8 +913,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { IPrivilege privilege = role.getPrivilege(privilegeName); String policy = privilege.getPolicy(); if (policy != null && !this.policyMap.containsKey(policy)) { - throw new PrivilegeException("Policy " + policy + " for Privilege " + privilege.getName() - + " does not exist on role " + role); + String msg = "Policy {0} for Privilege {1} does not exist on role {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policy, privilege.getName(), role); + throw new PrivilegeException(msg); } } } @@ -918,18 +946,18 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { if (systemUsername == null) - throw new PrivilegeException("systemUsername may not be null!"); + throw new PrivilegeException("systemUsername may not be null!"); //$NON-NLS-1$ if (action == null) - throw new PrivilegeException("action may not be null!"); + throw new PrivilegeException("action may not be null!"); //$NON-NLS-1$ // get the system user User systemUser = this.persistenceHandler.getUser(systemUsername); if (systemUser == null) - throw new PrivilegeException("System user " + systemUsername + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("System user {0} does not exist!", systemUsername)); //$NON-NLS-1$ // validate this is a system user if (systemUser.getUserState() != UserState.SYSTEM) - throw new PrivilegeException("User " + systemUsername + " is not a System user!"); + throw new PrivilegeException(MessageFormat.format("User {0} is not a System user!", systemUsername)); //$NON-NLS-1$ // validate this system user may perform the given action String actionClassname = action.getClass().getName(); @@ -966,7 +994,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // default throw exception, as the user does not have the privilege - throw new PrivilegeException("User " + user.getUsername() + " does not have Privilege " + privilegeName); + String msg = MessageFormat.format("User {0} does not have Privilege {1}", user.getUsername(), privilegeName); //$NON-NLS-1$ + throw new PrivilegeException(msg); } /** @@ -986,24 +1015,33 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get user object User user = this.persistenceHandler.getUser(systemUsername); // no user means no authentication - if (user == null) - throw new AccessDeniedException("The system user with username " + systemUsername + " does not exist!"); + if (user == null) { + String msg = MessageFormat.format("The system user with username {0} does not exist!", systemUsername); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } // validate password String pwHash = user.getPassword(); - if (pwHash == null) - throw new AccessDeniedException("System user " + systemUsername + " has no password and may not login!"); - if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException("System user " + systemUsername + " has an incorrect password defined!"); + if (pwHash == null) { + String msg = MessageFormat.format("System user {0} has no password and may not login!", systemUsername); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } + if (!pwHash.equals(passwordHash)) { + String msg = MessageFormat.format("System user {0} has an incorrect password defined!", systemUsername); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } // validate user state is system - if (user.getUserState() != UserState.SYSTEM) - throw new PrivilegeException("The system " + systemUsername + " user does not have expected user state " - + UserState.SYSTEM); + if (user.getUserState() != UserState.SYSTEM) { + String msg = "The system {0} user does not have expected user state {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, systemUsername, UserState.SYSTEM); + throw new PrivilegeException(msg); + } // validate user has at least one role if (user.getRoles().isEmpty()) { - throw new PrivilegeException("The system user " + systemUsername + " does not have any roles defined!"); + String msg = MessageFormat.format("The system user {0} does not have any roles defined!", systemUsername); //$NON-NLS-1$ + throw new PrivilegeException(msg); } // get 2 auth tokens @@ -1021,8 +1059,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); // log - DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " - + systemUserCertificate); + String msg = "The system user {0} is logged in with session {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, systemUsername, systemUserCertificate); + DefaultPrivilegeHandler.logger.info(msg); return privilegeContext; } @@ -1055,8 +1094,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { try { policy = ClassHelper.instantiateClass(policyClazz); } catch (Exception e) { - throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" - + policyName, e); + String msg = "The class for the policy with the name {0} does not exist!{1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policyName, policyName); + throw new PrivilegeException(msg, e); } return policy; diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 52832c2c8..fafd54bdc 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -43,7 +43,7 @@ public interface PrivilegeHandler { /** * PRIVILEGE_ADMIN_ROLE = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects */ - public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; + public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; //$NON-NLS-1$ /** * Returns a {@link UserRep} for the given username diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index c6a052942..f9d3b158b 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -40,6 +40,7 @@ import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; * * @author Robert von Burg */ +@SuppressWarnings("nls") public class BootstrapConfigurationHelper { // private static final Logger logger = Loggerdoc.getLogger(BootstrapConfigurationHelper.class); diff --git a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java deleted file mode 100644 index cad6e5fb5..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.privilege.helper; - -import ch.eitchnet.privilege.base.PrivilegeException; - -/** - * The {@link ClassHelper} class is a helper to instantiate classes using reflection - * - * @author Robert von Burg - */ -public class ClassHelper { - - /** - * Returns an instance of the class' name given by instantiating the class through an empty arguments constructor - * - * @param - * the type of the class to return - * @param className - * the name of a class to instantiate through an empty arguments constructor - * - * @return the newly instantiated object from the given class name - * - * @throws PrivilegeException - * if the class could not be instantiated - */ - @SuppressWarnings("unchecked") - public static T instantiateClass(String className) throws PrivilegeException { - try { - - Class clazz = (Class) Class.forName(className); - - return clazz.getConstructor().newInstance(); - - } catch (Exception e) { - throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); - } - } - - /** - * Instantiates an object for the given {@link Class} using an empty arguments constructor - * - * @param - * the type of the class to return - * @param clazz - * the {@link Class} from which a new object is to be instantiated using an empty arguments constructor - * - * @return the newly instantiated object from the given {@link Class} - * - * @throws PrivilegeException - * if the {@link Class} could not be instantiated - */ - public static T instantiateClass(Class clazz) throws PrivilegeException { - try { - - return clazz.getConstructor().newInstance(); - - } catch (Exception e) { - throw new PrivilegeException("The class " + clazz.getName() + " could not be instantiated: ", e); - } - } - - /** - * Loads the {@link Class} object for the given class name - * - * @param - * the type of {@link Class} to return - * @param className - * the name of the {@link Class} to load and return - * - * @return the {@link Class} object for the given class name - * - * @throws PrivilegeException - * if the class could not be instantiated - */ - @SuppressWarnings("unchecked") - public static Class loadClass(String className) throws PrivilegeException { - try { - - Class clazz = (Class) Class.forName(className); - - return clazz; - - } catch (Exception e) { - throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); - } - } -} diff --git a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java deleted file mode 100644 index 01fef9e5f..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.privilege.helper; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Helper class to hash a String for a certain hash algorithm, using the Java {@link MessageDigest} classes - * - * @author Robert von Burg - */ -public class HashHelper { - - /** - * 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' }; - - /** - * Creates the hash of the given string using {@link MessageDigest} and the defined hash algorithm - * - * @param hashAlgorithm - * the algorithm to use for hashing - * @param string - * the string to hash - * - * @return a new string encrypted by the defined algorithm - * - * @throws NoSuchAlgorithmException - * if the algorithm is not found - * @throws UnsupportedEncodingException - * if something is wrong with the given string to hash - */ - public static String stringToHash(String hashAlgorithm, String string) throws NoSuchAlgorithmException, - UnsupportedEncodingException { - return HashHelper.stringToHash(hashAlgorithm, string.getBytes()); - } - - /** - * Creates the hash of the given string using {@link MessageDigest} and the defined hash algorithm - * - * @param hashAlgorithm - * the algorithm to use for hashing - * @param bytes - * the bytes to hash - * - * @return a new string encrypted by the defined algorithm - * - * @throws NoSuchAlgorithmException - * if the algorithm is not found - * @throws UnsupportedEncodingException - * if something is wrong with the given string to hash - */ - public static String stringToHash(String hashAlgorithm, byte[] bytes) throws NoSuchAlgorithmException, - UnsupportedEncodingException { - - MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); - byte[] hashArray = digest.digest(bytes); - - byte[] hex = new byte[2 * hashArray.length]; - int index = 0; - - for (byte b : hashArray) { - int v = b & 0xFF; - hex[index++] = HashHelper.HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = HashHelper.HEX_CHAR_TABLE[v & 0xF]; - } - - return new String(hex, "ASCII"); - } -} diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index fe58b86f5..d409a196e 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -31,11 +31,14 @@ import javax.swing.JPasswordField; import javax.swing.JTextField; import javax.swing.SwingConstants; +import ch.eitchnet.utils.helper.StringHelper; + /** * Simple Swing UI to create passwords * * @author Robert von Burg */ +@SuppressWarnings("nls") public class PasswordCreaterUI { /** @@ -91,7 +94,7 @@ public class PasswordCreaterUI { String digest = (String) digestCombo.getSelectedItem(); char[] passwordChar = passwordField.getPassword(); String password = new String(passwordChar); - String hash = HashHelper.stringToHash(digest, password); + String hash = StringHelper.hashAsHex(digest, password); hashField.setText(hash); } catch (Exception e1) { e1.printStackTrace(); diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java index 6285b4d5a..81e3bf567 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -19,6 +19,8 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.security.MessageDigest; +import ch.eitchnet.utils.helper.StringHelper; + /** *

      * Simple main class which can be used to create a hash from a password which the user must type in at the command line @@ -38,6 +40,7 @@ public class PasswordCreator { * @throws Exception * thrown if anything goes wrong */ + @SuppressWarnings("nls") public static void main(String[] args) throws Exception { BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); @@ -63,7 +66,7 @@ public class PasswordCreator { System.out.print("Password: "); String password = r.readLine().trim(); - System.out.print("Hash is: " + HashHelper.stringToHash(hashAlgorithm, password)); + System.out.print("Hash is: " + StringHelper.hashAsHex(hashAlgorithm, password)); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index ba51d5556..03bc8d21e 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -29,6 +29,7 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; import ch.eitchnet.privilege.policy.PrivilegePolicy; import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; +import ch.eitchnet.utils.helper.ClassHelper; import ch.eitchnet.utils.helper.XmlHelper; /** diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index 068fc01a7..079fe433d 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -20,6 +20,7 @@ package ch.eitchnet.privilege.helper; * * @author Robert von Burg */ +@SuppressWarnings("nls") public class XmlConstants { /** diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 28ea2420f..503f49515 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -72,16 +72,16 @@ public final class Certificate implements Serializable { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { - throw new PrivilegeException("sessionId is null!"); + throw new PrivilegeException("sessionId is null!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(username)) { - throw new PrivilegeException("username is null!"); + throw new PrivilegeException("username is null!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(authToken)) { - throw new PrivilegeException("authToken is null!"); + throw new PrivilegeException("authToken is null!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(authPassword)) { - throw new PrivilegeException("authPassword is null!"); + throw new PrivilegeException("authPassword is null!"); //$NON-NLS-1$ } this.sessionId = sessionId; @@ -162,6 +162,7 @@ public final class Certificate implements Serializable { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 3889bfe11..d7780b635 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -94,7 +94,7 @@ public class PrivilegeContext { String privilegeName = restrictable.getPrivilegeName(); IPrivilege privilege = this.privileges.get(privilegeName); if (privilege == null) { - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } @@ -103,7 +103,7 @@ public class PrivilegeContext { String policyName = privilege.getPolicy(); PrivilegePolicy policy = this.policies.get(policyName); if (policy == null) { - String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; + String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; //$NON-NLS-1$ throw new PrivilegeException(MessageFormat.format(msg, policyName)); } @@ -130,7 +130,7 @@ public class PrivilegeContext { public static PrivilegeContext get() throws PrivilegeException { PrivilegeContext privilegeContext = PrivilegeContext.threadLocal.get(); if (privilegeContext == null) { - throw new PrivilegeException("There is no PrivilegeContext currently bound to the ThreadLocal!"); + throw new PrivilegeException("There is no PrivilegeContext currently bound to the ThreadLocal!"); //$NON-NLS-1$ } return privilegeContext; } @@ -148,7 +148,7 @@ public class PrivilegeContext { public static void set(PrivilegeContext privilegeContext) throws PrivilegeException { PrivilegeContext currentContext = PrivilegeContext.threadLocal.get(); if (privilegeContext != null && currentContext != null) { - throw new PrivilegeException("There already is a PrivilegeContext bound to the ThreadLocal!"); + throw new PrivilegeException("There already is a PrivilegeContext bound to the ThreadLocal!"); //$NON-NLS-1$ } PrivilegeContext.threadLocal.set(privilegeContext); } diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index 10c20da2e..e2c1a3a39 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -72,18 +72,18 @@ public class PrivilegeRep implements Serializable { public void validate() { if (StringHelper.isEmpty(this.name)) { - throw new PrivilegeException("No name defined!"); + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(this.policy)) { - throw new PrivilegeException("policy is null!"); + throw new PrivilegeException("policy is null!"); //$NON-NLS-1$ } if (this.denyList == null) { - throw new PrivilegeException("denyList is null"); + throw new PrivilegeException("denyList is null"); //$NON-NLS-1$ } if (this.allowList == null) { - throw new PrivilegeException("allowList is null"); + throw new PrivilegeException("allowList is null"); //$NON-NLS-1$ } } @@ -167,6 +167,7 @@ public class PrivilegeRep implements Serializable { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index 9a4fe7548..c32a226bc 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -57,7 +57,7 @@ public class RoleRep implements Serializable { */ public void validate() { if (StringHelper.isEmpty(this.name)) - throw new PrivilegeException("name is null"); + throw new PrivilegeException("name is null"); //$NON-NLS-1$ } /** @@ -87,6 +87,7 @@ public class RoleRep implements Serializable { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 2dd875b40..26bdc141e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -85,22 +85,22 @@ public class UserRep implements Serializable { public void validate() { if (StringHelper.isEmpty(this.userId)) - throw new PrivilegeException("userId is null or empty"); + throw new PrivilegeException("userId is null or empty"); //$NON-NLS-1$ if (StringHelper.isEmpty(this.username)) - throw new PrivilegeException("username is null or empty"); + throw new PrivilegeException("username is null or empty"); //$NON-NLS-1$ if (StringHelper.isEmpty(this.firstname)) - throw new PrivilegeException("firstname is null or empty"); + throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ if (StringHelper.isEmpty(this.surname)) - throw new PrivilegeException("surname is null or empty"); + throw new PrivilegeException("surname is null or empty"); //$NON-NLS-1$ if (this.userState == null) - throw new PrivilegeException("userState is null"); + throw new PrivilegeException("userState is null"); //$NON-NLS-1$ if (this.roles == null) - throw new PrivilegeException("roles is null"); + throw new PrivilegeException("roles is null"); //$NON-NLS-1$ } /** @@ -247,6 +247,7 @@ public class UserRep implements Serializable { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index 39aabc5ae..efc2d7954 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -15,6 +15,7 @@ */ package ch.eitchnet.privilege.model.internal; +import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; @@ -139,14 +140,17 @@ public class PrivilegeContainerModel { this.policies.put(privilegeName, clazz); } catch (InstantiationException e) { - throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " - + policyClassName + " could not be instantiated.", e); + String msg = "Configured Privilege Policy {0} with class {1} could not be instantiated."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, policyClassName); + throw new PrivilegeException(msg, e); } catch (IllegalAccessException e) { - throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " - + policyClassName + " can not be accessed.", e); + String msg = "Configured Privilege Policy {0} with class {1} can not be accessed."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, policyClassName); + throw new PrivilegeException(msg, e); } catch (ClassNotFoundException e) { - throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " - + policyClassName + " does not exist.", e); + String msg = "Configured Privilege Policy {0} with class {1} does not exist."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, policyClassName); + throw new PrivilegeException(msg, e); } } @@ -162,6 +166,7 @@ public class PrivilegeContainerModel { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index 8b28c442d..e9d2b937c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -73,16 +73,16 @@ public final class PrivilegeImpl implements IPrivilege { public PrivilegeImpl(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { if (StringHelper.isEmpty(name)) { - throw new PrivilegeException("No name defined!"); + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(policy)) { - throw new PrivilegeException("Policy may not be empty!"); + throw new PrivilegeException("Policy may not be empty!"); //$NON-NLS-1$ } if (denyList == null) { - throw new PrivilegeException("denyList is null!"); + throw new PrivilegeException("denyList is null!"); //$NON-NLS-1$ } if (allowList == null) { - throw new PrivilegeException("allowList is null!"); + throw new PrivilegeException("allowList is null!"); //$NON-NLS-1$ } this.name = name; @@ -189,6 +189,7 @@ public final class PrivilegeImpl implements IPrivilege { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index 91ddbeb6a..b962095b7 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -55,10 +55,10 @@ public final class Role { public Role(String name, Map privilegeMap) { if (StringHelper.isEmpty(name)) { - throw new PrivilegeException("No name defined!"); + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (privilegeMap == null) { - throw new PrivilegeException("No privileges defined!"); + throw new PrivilegeException("No privileges defined!"); //$NON-NLS-1$ } this.name = name; @@ -75,11 +75,11 @@ public final class Role { String name = roleRep.getName(); if (StringHelper.isEmpty(name)) { - throw new PrivilegeException("No name defined!"); + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (roleRep.getPrivilegeMap() == null) { - throw new PrivilegeException("No privileges defined!"); + throw new PrivilegeException("No privileges defined!"); //$NON-NLS-1$ } // build privileges from reps @@ -145,6 +145,7 @@ public final class Role { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 7fe3a0ed2..7d1402234 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -82,19 +82,19 @@ public final class User { Set roles, Locale locale, Map propertyMap) { if (StringHelper.isEmpty(userId)) { - throw new PrivilegeException("No UserId defined!"); + throw new PrivilegeException("No UserId defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(username)) { - throw new PrivilegeException("No username defined!"); + throw new PrivilegeException("No username defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(firstname)) { - throw new PrivilegeException("No firstname defined!"); + throw new PrivilegeException("No firstname defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(surname)) { - throw new PrivilegeException("No surname defined!"); + throw new PrivilegeException("No surname defined!"); //$NON-NLS-1$ } if (userState == null) { - throw new PrivilegeException("No userState defined!"); + throw new PrivilegeException("No userState defined!"); //$NON-NLS-1$ } // password may be null, meaning not able to login @@ -240,6 +240,7 @@ public final class User { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 98c170bd6..2a10f3270 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -81,7 +81,7 @@ public class DefaultPrivilege implements PrivilegePolicy { // first check values not allowed if (privilege.isDenied(privilegeValue)) { // then throw access denied - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } @@ -91,7 +91,7 @@ public class DefaultPrivilege implements PrivilegePolicy { return; // default is not allowed - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index 74547ea9e..f7e09a822 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -23,11 +23,11 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; /** * @author Robert von Burg - * */ public class PrivilegeConfigSaxReader extends DefaultHandler { @@ -41,14 +41,18 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { this.containerModel = containerModel; } + public PrivilegeContainerModel getContainerModel() { + return this.containerModel; + } + @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Container")) { + if (qName.equals(XmlConstants.XML_CONTAINER)) { this.buildersStack.add(new ContainerParser()); - } else if (qName.equals("Parameters")) { + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { this.buildersStack.add(new ParametersParser()); - } else if (qName.equals("Policies")) { + } else if (qName.equals(XmlConstants.XML_POLICIES)) { this.buildersStack.add(new PoliciesParser()); } @@ -69,11 +73,11 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals("Container")) { + if (qName.equals(XmlConstants.XML_CONTAINER)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals("Parameters")) { + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals("Policies")) { + } else if (qName.equals(XmlConstants.XML_POLICIES)) { elementParser = this.buildersStack.pop(); } @@ -105,16 +109,16 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Container")) { + if (qName.equals(XmlConstants.XML_CONTAINER)) { this.currentElement = qName; - } else if (qName.equals("EncryptionHandler")) { + } else if (qName.equals(XmlConstants.XML_HANDLER_ENCRYPTION)) { this.currentElement = qName; - PrivilegeConfigSaxReader.this.containerModel - .setEncryptionHandlerClassName(attributes.getValue("class")); - } else if (qName.equals("PersistenceHandler")) { + String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); + getContainerModel().setEncryptionHandlerClassName(className); + } else if (qName.equals(XmlConstants.XML_HANDLER_PERSISTENCE)) { this.currentElement = qName; - PrivilegeConfigSaxReader.this.containerModel.setPersistenceHandlerClassName(attributes - .getValue("class")); + String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); + getContainerModel().setPersistenceHandlerClassName(className); } } @@ -125,14 +129,12 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { ParametersParser parametersChild = (ParametersParser) child; - if (this.currentElement.equals("Container")) { - PrivilegeConfigSaxReader.this.containerModel.setParameterMap(parametersChild.getParameterMap()); - } else if (this.currentElement.equals("EncryptionHandler")) { - PrivilegeConfigSaxReader.this.containerModel.setEncryptionHandlerParameterMap(parametersChild - .getParameterMap()); - } else if (this.currentElement.equals("PersistenceHandler")) { - PrivilegeConfigSaxReader.this.containerModel.setPersistenceHandlerParameterMap(parametersChild - .getParameterMap()); + if (this.currentElement.equals(XmlConstants.XML_CONTAINER)) { + getContainerModel().setParameterMap(parametersChild.getParameterMap()); + } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_ENCRYPTION)) { + getContainerModel().setEncryptionHandlerParameterMap(parametersChild.getParameterMap()); + } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_PERSISTENCE)) { + getContainerModel().setPersistenceHandlerParameterMap(parametersChild.getParameterMap()); } } } @@ -145,9 +147,9 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Parameter")) { - String key = attributes.getValue("name"); - String value = attributes.getValue("value"); + if (qName.equals(XmlConstants.XML_PARAMETER)) { + String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); this.parameterMap.put(key, value); } } @@ -166,11 +168,11 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Policy")) { - String policyName = attributes.getValue("name"); - String policyClassName = attributes.getValue("class"); + if (qName.equals(XmlConstants.XML_POLICY)) { + String policyName = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String policyClassName = attributes.getValue(XmlConstants.XML_ATTR_CLASS); - PrivilegeConfigSaxReader.this.containerModel.addPolicy(policyName, policyClassName); + getContainerModel().addPolicy(policyName, policyClassName); } } } diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index b3add0f1b..df0b05460 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -15,6 +15,7 @@ */ package ch.eitchnet.privilege.xml; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -30,6 +31,7 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.PrivilegeImpl; @@ -42,7 +44,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class PrivilegeModelSaxReader extends DefaultHandler { - private static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); + protected static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); private Stack buildersStack = new Stack(); @@ -73,12 +75,12 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Users")) { + if (qName.equals(XmlConstants.XML_USERS)) { this.buildersStack.add(new UserParser()); this.insideUser = true; - } else if (qName.equals("Properties")) { + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { this.buildersStack.add(new PropertyParser()); - } else if (qName.equals("Roles") && !this.insideUser) { + } else if (qName.equals(XmlConstants.XML_ROLES) && !this.insideUser) { this.buildersStack.add(new RoleParser()); } @@ -99,16 +101,16 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals("Users")) { + if (qName.equals(XmlConstants.XML_USERS)) { elementParser = this.buildersStack.pop(); this.insideUser = false; - PrivilegeModelSaxReader.logger.info("Popping for Users"); - } else if (qName.equals("Properties")) { + logger.info("Popping for Users"); //$NON-NLS-1$ + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { elementParser = this.buildersStack.pop(); - PrivilegeModelSaxReader.logger.info("Popping for Properties"); - } else if (qName.equals("Roles") && !this.insideUser) { + logger.info("Popping for Properties"); //$NON-NLS-1$ + } else if (qName.equals(XmlConstants.XML_ROLES) && !this.insideUser) { elementParser = this.buildersStack.pop(); - PrivilegeModelSaxReader.logger.info("Popping for Roles"); + logger.info("Popping for Roles"); //$NON-NLS-1$ } if (!this.buildersStack.isEmpty() && elementParser != null) @@ -142,16 +144,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { private Map privileges; - /** - * - */ public RoleParser() { init(); } - /** - * - */ private void init() { this.privileges = new HashMap(); @@ -170,11 +166,11 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.text = new StringBuilder(); - if (qName.equals("Role")) { - this.roleName = attributes.getValue("name"); - } else if (qName.equals("Privilege")) { - this.privilegeName = attributes.getValue("name"); - this.privilegePolicy = attributes.getValue("policy"); + if (qName.equals(XmlConstants.XML_ROLE)) { + this.roleName = attributes.getValue(XmlConstants.XML_ATTR_NAME); + } else if (qName.equals(XmlConstants.XML_PRIVILEGE)) { + this.privilegeName = attributes.getValue(XmlConstants.XML_ATTR_NAME); + this.privilegePolicy = attributes.getValue(XmlConstants.XML_ATTR_POLICY); } } @@ -187,24 +183,24 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void endElement(String uri, String localName, String qName) throws SAXException { - if (qName.equals("AllAllowed")) { + if (qName.equals(XmlConstants.XML_ALL_ALLOWED)) { this.allAllowed = StringHelper.parseBoolean(this.text.toString().trim()); - } else if (qName.equals("Allow")) { + } else if (qName.equals(XmlConstants.XML_ALLOW)) { this.allowList.add(this.text.toString().trim()); - } else if (qName.equals("Deny")) { + } else if (qName.equals(XmlConstants.XML_DENY)) { this.denyList.add(this.text.toString().trim()); - } else if (qName.equals("Privilege")) { + } else if (qName.equals(XmlConstants.XML_PRIVILEGE)) { IPrivilege privilege = new PrivilegeImpl(this.privilegeName, this.privilegePolicy, this.allAllowed, this.denyList, this.allowList); this.privileges.put(this.privilegeName, privilege); - } else if (qName.equals("Role")) { + } else if (qName.equals(XmlConstants.XML_ROLE)) { Role role = new Role(this.roleName, this.privileges); - PrivilegeModelSaxReader.this.roles.add(role); - PrivilegeModelSaxReader.logger.info("New Role: " + role); + getRoles().add(role); + logger.info(MessageFormat.format("New Role: {0}", role)); //$NON-NLS-1$ init(); } } @@ -248,10 +244,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.text = new StringBuilder(); - if (qName.equals("User")) { - this.userId = attributes.getValue("userId"); - this.username = attributes.getValue("username"); - this.password = attributes.getValue("password"); + if (qName.equals(XmlConstants.XML_USER)) { + this.userId = attributes.getValue(XmlConstants.XML_ATTR_USER_ID); + this.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME); + this.password = attributes.getValue(XmlConstants.XML_ATTR_PASSWORD); } } @@ -263,22 +259,22 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void endElement(String uri, String localName, String qName) throws SAXException { - if (qName.equals("Firstname")) { + if (qName.equals(XmlConstants.XML_FIRSTNAME)) { this.firstName = this.text.toString().trim(); - } else if (qName.equals("Surname")) { + } else if (qName.equals(XmlConstants.XML_SURNAME)) { this.surname = this.text.toString().trim(); - } else if (qName.equals("State")) { + } else if (qName.equals(XmlConstants.XML_STATE)) { this.userState = UserState.valueOf(this.text.toString().trim()); - } else if (qName.equals("Locale")) { + } else if (qName.equals(XmlConstants.XML_LOCALE)) { this.locale = Locale.forLanguageTag(this.text.toString().trim()); - } else if (qName.equals("Role")) { + } else if (qName.equals(XmlConstants.XML_ROLE)) { this.userRoles.add(this.text.toString().trim()); - } else if (qName.equals("User")) { + } else if (qName.equals(XmlConstants.XML_USER)) { User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, this.userState, this.userRoles, this.locale, this.parameters); - PrivilegeModelSaxReader.this.users.add(user); + getUsers().add(user); } } @@ -294,20 +290,17 @@ public class PrivilegeModelSaxReader extends DefaultHandler { // - private Map parameterMap = new HashMap(); + public Map parameterMap = new HashMap(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Property")) { - String key = attributes.getValue("name"); - String value = attributes.getValue("value"); + if (qName.equals(XmlConstants.XML_PROPERTY)) { + String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); this.parameterMap.put(key, value); } } - /** - * @return the parameterMap - */ public Map getParameterMap() { return this.parameterMap; } diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 7506a933b..cac5aef26 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -148,7 +148,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - assertEquals("2ABD3442EEC8BCEC5BEE365AAB6DB2FD4E1789325425CB1E017E900582525685", fileHash); + assertEquals("2abd3442eec8bcec5bee365aab6db2fd4e1789325425cb1e017e900582525685", fileHash); } @Test @@ -210,6 +210,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("A2127D20A61E00BCDBB61569CD2B200C4F0F111C972BAC3B1E54DF3B2FCDC8BE", fileHash); + assertEquals("a2127d20a61e00bcdbb61569cd2b200c4f0f111c972bac3b1e54df3b2fcdc8be", fileHash); } } From 5d59a52eeb2aad2860b52b5b628b2bed2e3b8c7a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Dec 2013 10:55:58 +0100 Subject: [PATCH 238/457] [Minor] Throwing explicit exception system user tries to login Further enforcing that a system user may not have a password --- config/PrivilegeModel.xml | 3 +-- .../handler/DefaultPrivilegeHandler.java | 18 +++++++++--------- .../privilege/model/internal/User.java | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 2d1659459..9f15dcc53 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -18,7 +18,7 @@ - + System User Administrator SYSTEM @@ -26,7 +26,6 @@ system_admin_privileges - diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 66fdb4ef4..62d260d95 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -637,6 +637,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new AccessDeniedException(msg); } + // make sure not a system user - they may not login in + if (user.getUserState() == UserState.SYSTEM) { + String msg = "User {0} is a system user and may no login!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username); + throw new AccessDeniedException(msg); + } + // validate password String pwHash = user.getPassword(); if (pwHash == null) @@ -1009,9 +1016,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private PrivilegeContext getSystemUserPrivilegeContext(String systemUsername) { - // we only work with hashed passwords - String passwordHash = this.encryptionHandler.convertToHash(systemUsername.getBytes()); - // get user object User user = this.persistenceHandler.getUser(systemUsername); // no user means no authentication @@ -1022,12 +1026,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate password String pwHash = user.getPassword(); - if (pwHash == null) { - String msg = MessageFormat.format("System user {0} has no password and may not login!", systemUsername); //$NON-NLS-1$ - throw new AccessDeniedException(msg); - } - if (!pwHash.equals(passwordHash)) { - String msg = MessageFormat.format("System user {0} has an incorrect password defined!", systemUsername); //$NON-NLS-1$ + if (pwHash != null) { + String msg = MessageFormat.format("System users must not have a password: {0}", systemUsername); //$NON-NLS-1$ throw new AccessDeniedException(msg); } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 7d1402234..0e260ccaf 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -105,7 +105,7 @@ public final class User { this.userId = userId; this.username = username; - this.password = password; + this.password = StringHelper.isEmpty(password) ? null : password; this.userState = userState; this.firstname = firstname; From bab1e4b119c1dd42c5bf3c2765707e7d1a52b69a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Dec 2013 12:16:48 +0100 Subject: [PATCH 239/457] [Bugfix] fixed a bug where the role were not properly read from XML Added tests for reading the model from XML --- .../handler/DefaultPrivilegeHandler.java | 11 +- .../xml/PrivilegeModelSaxReader.java | 15 +- .../ch/eitchnet/privilege/test/XmlTest.java | 130 +++++++++++++++++- 3 files changed, 143 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 62d260d95..295541f58 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -972,9 +972,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get certificate for this system user PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(systemUsername); - - // perform the action - action.execute(systemUserPrivilegeContext); + String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId(); + this.privilegeContextMap.put(sessionId, systemUserPrivilegeContext); + try { + // perform the action + action.execute(systemUserPrivilegeContext); + } finally { + this.privilegeContextMap.remove(sessionId); + } } /** diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index df0b05460..dd5b83278 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -75,12 +75,12 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals(XmlConstants.XML_USERS)) { + if (qName.equals(XmlConstants.XML_USER)) { this.buildersStack.add(new UserParser()); this.insideUser = true; } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { this.buildersStack.add(new PropertyParser()); - } else if (qName.equals(XmlConstants.XML_ROLES) && !this.insideUser) { + } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { this.buildersStack.add(new RoleParser()); } @@ -101,16 +101,13 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals(XmlConstants.XML_USERS)) { + if (qName.equals(XmlConstants.XML_USER)) { elementParser = this.buildersStack.pop(); this.insideUser = false; - logger.info("Popping for Users"); //$NON-NLS-1$ } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { elementParser = this.buildersStack.pop(); - logger.info("Popping for Properties"); //$NON-NLS-1$ - } else if (qName.equals(XmlConstants.XML_ROLES) && !this.insideUser) { + } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { elementParser = this.buildersStack.pop(); - logger.info("Popping for Roles"); //$NON-NLS-1$ } if (!this.buildersStack.isEmpty() && elementParser != null) @@ -266,14 +263,14 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } else if (qName.equals(XmlConstants.XML_STATE)) { this.userState = UserState.valueOf(this.text.toString().trim()); } else if (qName.equals(XmlConstants.XML_LOCALE)) { - this.locale = Locale.forLanguageTag(this.text.toString().trim()); + this.locale = new Locale(this.text.toString().trim()); } else if (qName.equals(XmlConstants.XML_ROLE)) { this.userRoles.add(this.text.toString().trim()); } else if (qName.equals(XmlConstants.XML_USER)) { User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, this.userState, this.userRoles, this.locale, this.parameters); - + logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$ getUsers().add(user); } } diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index cac5aef26..dbdc7bc6f 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -15,11 +15,16 @@ */ package ch.eitchnet.privilege.test; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -166,7 +171,130 @@ public class XmlTest { assertEquals(2, users.size()); assertEquals(4, roles.size()); - // TODO extend assertions to actual model + // assert model + + // + // users + // + + // admin + User admin = findUser("admin", users); + assertEquals("1", admin.getUserId()); + assertEquals("admin", admin.getUsername()); + assertEquals("8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", admin.getPassword()); + assertEquals("Application", admin.getFirstname()); + assertEquals("Administrator", admin.getSurname()); + assertEquals(UserState.ENABLED, admin.getUserState()); + assertEquals("en_gb", admin.getLocale().toString()); + assertThat(admin.getRoles(), containsInAnyOrder("PrivilegeAdmin", "AppUser")); + Map properties = admin.getProperties(); + assertEquals(new HashSet(Arrays.asList("organization", "organizationalUnit")), properties.keySet()); + assertEquals("eitchnet.ch", properties.get("organization")); + assertEquals("Development", properties.get("organizationalUnit")); + + // system_admin + User systemAdmin = findUser("system_admin", users); + assertEquals("2", systemAdmin.getUserId()); + assertEquals("system_admin", systemAdmin.getUsername()); + assertEquals(null, systemAdmin.getPassword()); + assertEquals("System User", systemAdmin.getFirstname()); + assertEquals("Administrator", systemAdmin.getSurname()); + assertEquals(UserState.SYSTEM, systemAdmin.getUserState()); + assertEquals("en_gb", systemAdmin.getLocale().toString()); + assertThat(systemAdmin.getRoles(), containsInAnyOrder("system_admin_privileges")); + assertTrue(systemAdmin.getProperties().isEmpty()); + + // + // roles + // + + // PrivilegeAdmin + Role privilegeAdmin = findRole("PrivilegeAdmin", roles); + assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); + assertTrue(privilegeAdmin.getPrivilegeNames().isEmpty()); + + // AppUser + Role appUser = findRole("AppUser", roles); + assertEquals("AppUser", appUser.getName()); + assertEquals(new HashSet(Arrays.asList("ch.eitchnet.privilege.test.model.TestRestrictable")), + appUser.getPrivilegeNames()); + + IPrivilege testRestrictable = appUser.getPrivilege("ch.eitchnet.privilege.test.model.TestRestrictable"); + assertEquals("ch.eitchnet.privilege.test.model.TestRestrictable", testRestrictable.getName()); + assertEquals("DefaultPrivilege", testRestrictable.getPolicy()); + assertTrue(testRestrictable.isAllAllowed()); + assertEquals(0, testRestrictable.getAllowList().size()); + assertEquals(0, testRestrictable.getDenyList().size()); + + // system_admin_privileges + Role systemAdminPrivileges = findRole("system_admin_privileges", roles); + assertEquals("system_admin_privileges", systemAdminPrivileges.getName()); + assertEquals(2, systemAdminPrivileges.getPrivilegeNames().size()); + assertThat( + systemAdminPrivileges.getPrivilegeNames(), + containsInAnyOrder("ch.eitchnet.privilege.test.model.TestSystemUserAction", + "ch.eitchnet.privilege.test.model.TestSystemRestrictable")); + + IPrivilege testSystemUserAction = systemAdminPrivileges + .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemUserAction"); + assertEquals("ch.eitchnet.privilege.test.model.TestSystemUserAction", testSystemUserAction.getName()); + assertEquals("DefaultPrivilege", testSystemUserAction.getPolicy()); + assertTrue(testSystemUserAction.isAllAllowed()); + assertEquals(0, testSystemUserAction.getAllowList().size()); + assertEquals(0, testSystemUserAction.getDenyList().size()); + + IPrivilege testSystemRestrictable = systemAdminPrivileges + .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemRestrictable"); + assertEquals("ch.eitchnet.privilege.test.model.TestSystemRestrictable", testSystemRestrictable.getName()); + assertEquals("DefaultPrivilege", testSystemRestrictable.getPolicy()); + assertTrue(testSystemRestrictable.isAllAllowed()); + assertEquals(0, testSystemRestrictable.getAllowList().size()); + assertEquals(0, testSystemRestrictable.getDenyList().size()); + + // restrictedRole + Role restrictedRole = findRole("restrictedRole", roles); + assertEquals("restrictedRole", restrictedRole.getName()); + assertEquals(1, restrictedRole.getPrivilegeNames().size()); + assertThat(restrictedRole.getPrivilegeNames(), + containsInAnyOrder("ch.eitchnet.privilege.test.model.TestSystemUserAction")); + + IPrivilege testSystemUserAction2 = restrictedRole + .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemUserAction"); + assertEquals("ch.eitchnet.privilege.test.model.TestSystemUserAction", testSystemUserAction2.getName()); + assertEquals("DefaultPrivilege", testSystemUserAction2.getPolicy()); + assertFalse(testSystemUserAction2.isAllAllowed()); + assertEquals(1, testSystemUserAction2.getAllowList().size()); + assertEquals(1, testSystemUserAction2.getDenyList().size()); + assertThat(testSystemUserAction2.getAllowList(), containsInAnyOrder("hello")); + assertThat(testSystemUserAction2.getDenyList(), containsInAnyOrder("goodbye")); + } + + /** + * @param username + * @param users + * @return + */ + private User findUser(String username, List users) { + for (User user : users) { + if (user.getUsername().equals(username)) + return user; + } + + throw new RuntimeException("No user exists with username " + username); + } + + /** + * @param name + * @param roles + * @return + */ + private Role findRole(String name, List roles) { + for (Role role : roles) { + if (role.getName().equals(name)) + return role; + } + + throw new RuntimeException("No role exists with name " + name); } @Test From 11f53cb2721d466280e628abfdf1d6d6b5e7bbdb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 29 Dec 2013 23:12:37 +0100 Subject: [PATCH 240/457] [Minor] removed the use of the authPass in the certificate --- .../handler/DefaultPrivilegeHandler.java | 10 +++---- .../eitchnet/privilege/model/Certificate.java | 28 +++---------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 295541f58..0d2e284b8 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -668,15 +668,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get 2 auth tokens - String authToken = this.encryptionHandler.nextToken(); - String authPassword = this.encryptionHandler.nextToken(); + String authToken = this.encryptionHandler.convertToHash(this.encryptionHandler.nextToken()); // get next session id String sessionId = nextSessionId(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, System.currentTimeMillis(), username, authToken, authPassword, - user.getLocale(), new HashMap(user.getProperties())); + certificate = new Certificate(sessionId, System.currentTimeMillis(), username, authToken, user.getLocale(), + new HashMap(user.getProperties())); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); @@ -1051,14 +1050,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get 2 auth tokens String authToken = this.encryptionHandler.nextToken(); - String authPassword = this.encryptionHandler.nextToken(); // get next session id String sessionId = nextSessionId(); // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - authToken, authPassword, user.getLocale(), new HashMap(user.getProperties())); + authToken, user.getLocale(), new HashMap(user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 503f49515..2d417f86c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -38,7 +38,6 @@ public final class Certificate implements Serializable { private final long loginTime; private final String username; private final String authToken; - private final String authPassword; private Locale locale; @@ -58,17 +57,14 @@ public final class Certificate implements Serializable { * the users login name * @param authToken * the authentication token defining the users unique session and is a private field of this certificate. - * @param authPassword - * the password to access the authentication token, this is not known to the client but set by the - * {@link PrivilegeHandler} on authentication. * @param locale * the users {@link Locale} * @param propertyMap * a {@link Map} containing string value pairs of properties for the logged in user. These properties can * be edited and can be used for the user to change settings of this session */ - public Certificate(String sessionId, long loginTime, String username, String authToken, String authPassword, - Locale locale, Map propertyMap) { + public Certificate(String sessionId, long loginTime, String username, String authToken, Locale locale, + Map propertyMap) { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { @@ -80,15 +76,11 @@ public final class Certificate implements Serializable { if (StringHelper.isEmpty(authToken)) { throw new PrivilegeException("authToken is null!"); //$NON-NLS-1$ } - if (StringHelper.isEmpty(authPassword)) { - throw new PrivilegeException("authPassword is null!"); //$NON-NLS-1$ - } this.sessionId = sessionId; this.loginTime = loginTime; this.username = username; this.authToken = authToken; - this.authPassword = authPassword; // if no locale is given, set default if (locale == null) @@ -145,16 +137,10 @@ public final class Certificate implements Serializable { /** * Returns the authToken if the given authPassword is correct, null otherwise * - * @param authPassword - * the authentication password with which this certificate was created - * * @return the authToken if the given authPassword is correct, null otherwise */ - public String getAuthToken(String authPassword) { - if (this.authPassword.equals(authPassword)) - return this.authToken; - - return null; + public String getAuthToken() { + return this.authToken; } /** @@ -180,7 +166,6 @@ public final class Certificate implements Serializable { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((this.authPassword == null) ? 0 : this.authPassword.hashCode()); result = prime * result + ((this.authToken == null) ? 0 : this.authToken.hashCode()); result = prime * result + ((this.locale == null) ? 0 : this.locale.hashCode()); result = prime * result + ((this.sessionId == null) ? 0 : this.sessionId.hashCode()); @@ -197,11 +182,6 @@ public final class Certificate implements Serializable { if (!(obj instanceof Certificate)) return false; Certificate other = (Certificate) obj; - if (this.authPassword == null) { - if (other.authPassword != null) - return false; - } else if (!this.authPassword.equals(other.authPassword)) - return false; if (this.authToken == null) { if (other.authToken != null) return false; From d677249cf6b67eb43052c670031bc12ed09c2a64 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 31 Dec 2013 19:56:45 +0100 Subject: [PATCH 241/457] [Minor] added StringHelper.DASH constant --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index a7eca17de..45849de3c 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -35,6 +35,7 @@ public class StringHelper { 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 = "dash"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); From 7202545f6f8ddcba27387c6aa21e19ce207026a1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 2 Jan 2014 16:29:02 +0100 Subject: [PATCH 242/457] [Bugfix] fixed wrong constant value --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 45849de3c..d9eea196d 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -35,7 +35,7 @@ public class StringHelper { 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 = "dash"; //$NON-NLS-1$ + public static final String DASH = "-"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); From 48e2defc9eaef6709d092754be53d7280422d19f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 10 Jan 2014 23:13:41 +0100 Subject: [PATCH 243/457] [Bugfix] fixed a bug where FileHelper.copy() didn't recursively copy --- .../java/ch/eitchnet/utils/helper/FileHelper.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 862598d9b..eb0725308 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -234,8 +234,9 @@ public class FileHelper { } /** - * Copy a given list of {@link File Files}. The renameTo method does not allow action across NFS mounted filesystems - * this method is the workaround + *

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

      * * @param srcFiles * The source files to copy @@ -255,8 +256,14 @@ public class FileHelper { for (File srcFile : srcFiles) { File dstFile = new File(dstDirectory, srcFile.getName()); - if (!copy(srcFile, dstFile, checksum)) - return false; + if (srcFile.isDirectory()) { + dstFile.mkdir(); + if (!copy(srcFile.listFiles(), dstFile, checksum)) + return false; + } else { + if (!copy(srcFile, dstFile, checksum)) + return false; + } } return true; From 40eec73bb197d3ab46a09a3b47156cb53cc37db0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 10 Jan 2014 23:14:17 +0100 Subject: [PATCH 244/457] [Bugfix] fixed missing exception on transaction result on error case --- .../ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index 650df2614..7d6bc8fe4 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -253,6 +253,7 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { this.txResult.clear(); this.txResult.setState(TransactionState.FAILED); this.txResult.setModificationByKey(Collections. emptyMap()); + this.txResult.setFailCause(e); } finally { From aad2b447b96aed6525e6fdb88c081bdec3d3d68d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 11 Jan 2014 17:35:19 +0100 Subject: [PATCH 245/457] [Minor] added some JavaDoc to TransactionResult --- .../ch/eitchnet/xmlpers/api/TransactionResult.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java index 68d05d6ec..11267944b 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java @@ -33,7 +33,7 @@ public class TransactionResult { private long closeDuration; private Map modificationByKey; - + public TransactionResult() { this.state = TransactionState.OPEN; this.modificationByKey = new HashMap<>(); @@ -70,6 +70,8 @@ public class TransactionResult { } /** + * The internal exception why the transaction failed + * * @return the failCause */ public Exception getFailCause() { @@ -85,6 +87,8 @@ public class TransactionResult { } /** + * Start time of the transaction + * * @return the startTime */ public Date getStartTime() { @@ -100,6 +104,8 @@ public class TransactionResult { } /** + * The duration the transaction was open in nanoseconds + * * @return the txDuration */ public long getTxDuration() { @@ -115,6 +121,8 @@ public class TransactionResult { } /** + * The duration the transaction took to close in nanoseconds + * * @return the closeDuration */ public long getCloseDuration() { From 4b179f8bb44f3ef4eb03ec91c2e242244c81df7b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 13 Jan 2014 21:09:28 +0100 Subject: [PATCH 246/457] [Minor] small error handling change in FileHelper --- .../ch/eitchnet/utils/helper/FileHelper.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index eb0725308..a1ad8cdbd 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -244,7 +244,7 @@ public class FileHelper { * The destination where to copy the files * @param checksum * if true, then a MD5 checksum is made to validate copying - * @return true if and only if the renaming succeeded; false otherwise + * @return true if and only if the copying succeeded; false otherwise */ public final static boolean copy(File[] srcFiles, File dstDirectory, boolean checksum) { @@ -258,11 +258,16 @@ public class FileHelper { File dstFile = new File(dstDirectory, srcFile.getName()); if (srcFile.isDirectory()) { dstFile.mkdir(); - if (!copy(srcFile.listFiles(), dstFile, checksum)) + if (!copy(srcFile.listFiles(), dstFile, checksum)) { + String msg = "Failed to copy contents of {0} to {1}"; + msg = MessageFormat.format(msg, srcFile.getAbsolutePath(), dstFile.getAbsolutePath()); + logger.error(msg); return false; + } } else { - if (!copy(srcFile, dstFile, checksum)) + if (!copy(srcFile, dstFile, checksum)) { return false; + } } } @@ -310,8 +315,8 @@ public class FileHelper { // cleanup if files are not the same length if (fromFile.length() != toFile.length()) { - String msg = MessageFormat.format("Copying failed, as new files are not the same length: {0} / {1}", //$NON-NLS-1$ - fromFile.length(), toFile.length()); + String msg = "Copying failed, as new files are not the same length: {0} / {1}"; + msg = MessageFormat.format(msg, fromFile.length(), toFile.length()); FileHelper.logger.error(msg); toFile.delete(); @@ -319,8 +324,8 @@ public class FileHelper { } } catch (Exception e) { - - FileHelper.logger.error(e.getMessage(), e); + String msg = MessageFormat.format("Failed to copy path from {0} to + {1} due to:", fromFile, toFile); + FileHelper.logger.error(msg, e); return false; } From adc9e3598c9e2b4eee7f76b18a8cada37031436b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 15 Jan 2014 22:02:21 +0100 Subject: [PATCH 247/457] [New] new DBC.PRE.assertNull() method --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 3ea0bb26e..9f83f31f8 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -41,11 +41,24 @@ public enum DBC { throw new DbcException(ex); } } + + @Override + public void assertNull(String msg, Object value) { + if (value != null) { + String ex = "Illegal situation as value is not null: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + }; public abstract void assertNotEmpty(String msg, String value); + public abstract void assertNotNull(String msg, Object value); + public abstract void assertNull(String msg, Object value); + public class DbcException extends RuntimeException { private static final long serialVersionUID = 1L; From c969e25a858cc026da5259a1fd68b4d84f550bf4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 21 Jan 2014 07:40:27 +0100 Subject: [PATCH 248/457] [New] Added StringHelper.formatException(Throwable) --- .../ch/eitchnet/utils/helper/StringHelper.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index d9eea196d..db43eb557 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -15,6 +15,8 @@ */ package ch.eitchnet.utils.helper; +import java.io.PrintWriter; +import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -551,6 +553,21 @@ public class StringHelper { } } + /** + * 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(); + } + /** * Simply returns true if the value is null, or empty * From bfe08c9dc9dc4dda48d3b5cb6410002a04eee994 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 23 Jan 2014 22:52:25 +0100 Subject: [PATCH 249/457] [Project] added Jenkins build badge to README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ed357451..7b9074980 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ -ch.eitchnet.java.utils +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 From e6e184c16cdfbd2fb76f6d2622a490dfe11b590d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 23 Jan 2014 22:52:28 +0100 Subject: [PATCH 250/457] [Project] added Jenkins build badge to README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2496f583d..ec5c13c8c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ ch.eitchnet.java.xmlpers ======================== + +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=ch.eitchnet.xmlpers)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.xmlpers/) + Generic Java XML persistence layer. Implemented to be light-weight and simple to use Dependencies From 1f28237091cc580a4d2f1db057fb9bc42f5baf0f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 23 Jan 2014 22:59:42 +0100 Subject: [PATCH 251/457] [Project] added Jenkins build badge to README.md --- README => README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) rename README => README.md (83%) diff --git a/README b/README.md similarity index 83% rename from README rename to README.md index 73ec1b402..ba63eaf11 100644 --- a/README +++ b/README.md @@ -1,8 +1,10 @@ +ch.eitchnet.privilege +================== -Privilege README file +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=ch.eitchnet.privilege)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.privilege/) Overview -################################ +======================================================================= Privilege is a light weight library to secure access or grant privileges to users in an application. Privilege allows a developer to secure the application @@ -23,7 +25,7 @@ The main developer is Robert von Burg who also maintains the Github repository. He is available for all questions regarding Privilege Motivation -################################ +======================================================================= In some cases a developer might want to restrict access to an application depending on the role which an authenticated user has. In other cases the developer would need a more finely grained control by restricting access to a @@ -39,7 +41,7 @@ provide an API which suited our needs or which were not easily implemented, thus leading to the design of Privilege. Design Goals -################################ +======================================================================= When a developer needs to implement access restriction an application there are different questions which the developer will ask: - Does the user have a specific role? @@ -64,13 +66,13 @@ depending on the application. For example it should be able to store the data in XML files, in a LDAP directory and so forth. Documentation -################################ +======================================================================= The current documentation, though a bit outdated, can be found in the docs/ directory of the Repository Compiling -################################ +======================================================================= Privilege is a Maven3 project and can be built by simply performing the following command: @@ -78,7 +80,7 @@ following command: $ mvn compile Using -################################ +======================================================================= To use Privilege see the ch.eitchnet.privilege.test.PrivilegeTest.java class which contains a few test cases including showing how to load Privilege. From 4627f59a808749225d0771d8031d2d0336b4057f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 28 Jan 2014 22:07:04 +0100 Subject: [PATCH 252/457] [Minor] properties are read-only on Certificate --- .../ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 2 +- src/main/java/ch/eitchnet/privilege/model/Certificate.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 0d2e284b8..fe9f2a175 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -639,7 +639,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // make sure not a system user - they may not login in if (user.getUserState() == UserState.SYSTEM) { - String msg = "User {0} is a system user and may no login!"; //$NON-NLS-1$ + String msg = "User {0} is a system user and may not login!"; //$NON-NLS-1$ msg = MessageFormat.format(msg, username); throw new AccessDeniedException(msg); } diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 2d417f86c..1bac8c70e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -16,6 +16,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +import java.util.Collections; import java.util.Locale; import java.util.Map; @@ -88,7 +89,7 @@ public final class Certificate implements Serializable { else this.locale = locale; - this.propertyMap = propertyMap; + this.propertyMap = Collections.unmodifiableMap(propertyMap); } /** From 5e6423c44319fa1b1cfb8382adda0d753cd1cf83 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 28 Jan 2014 22:19:15 +0100 Subject: [PATCH 253/457] [Bugfix] fixed NPE when Certificate is created with null properties --- src/main/java/ch/eitchnet/privilege/model/Certificate.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 1bac8c70e..ba2f74bc1 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -89,7 +89,10 @@ public final class Certificate implements Serializable { else this.locale = locale; - this.propertyMap = Collections.unmodifiableMap(propertyMap); + if (propertyMap == null) + this.propertyMap = Collections.emptyMap(); + else + this.propertyMap = Collections.unmodifiableMap(propertyMap); } /** From dfc9d7ab178b8d95f3d4d9a08133b2dadaf00aa9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 30 Jan 2014 00:20:27 +0100 Subject: [PATCH 254/457] [New] added DBC.PRE.assert*Empty(File) --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 9f83f31f8..9a84ec46c 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -15,6 +15,7 @@ */ package ch.eitchnet.utils.dbc; +import java.io.File; import java.text.MessageFormat; import ch.eitchnet.utils.helper.StringHelper; @@ -51,6 +52,23 @@ public enum DBC { } } + @Override + public void assertNotExists(String msg, File file) { + if (file.exists()) { + String ex = "Illegal situation as file (" + file + ") exists: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + @Override + public void assertExists(String msg, File file) { + if (!file.exists()) { + String ex = "Illegal situation as file (" + file + ") does not exist: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } }; public abstract void assertNotEmpty(String msg, String value); @@ -59,6 +77,10 @@ public enum DBC { public abstract void assertNull(String msg, Object value); + public abstract void assertNotExists(String msg, File file); + + public abstract void assertExists(String msg, File file); + public class DbcException extends RuntimeException { private static final long serialVersionUID = 1L; From a79c9d88ce625289cc8aab4202eca5dfaaafa68d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 31 Jan 2014 15:56:16 +0100 Subject: [PATCH 255/457] [New] Added StringHelper.UNDERLINE and COMMA --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index db43eb557..4a8bc83aa 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -38,6 +38,8 @@ public class StringHelper { 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$ private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); From dda9ed8423d5e9c354ca57a75f4915ee47084913 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 31 Jan 2014 18:57:04 +0100 Subject: [PATCH 256/457] [New] added method StringHelper.isNotEmpty() --- .../ch/eitchnet/utils/helper/StringHelper.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 4a8bc83aa..34b3416f1 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -40,6 +40,8 @@ public class StringHelper { 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 SEMICOLON = ";"; //$NON-NLS-1$ + public static final String COLON = ":"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(StringHelper.class); @@ -582,6 +584,18 @@ public class StringHelper { return value == null || value.isEmpty(); } + /** + * Simply returns true if the value is neither null nor empty + * + * @param value + * the value to check + * + * @return true if the value is neither null nor empty + */ + public static boolean isNotEmpty(String value) { + return value != null && !value.isEmpty(); + } + /** *

      * Parses the given string value to a boolean. This extends the default {@link Boolean#parseBoolean(String)} as it From 7f2b7435a966bf4d5448767c30afb4c5f4e60c78 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 31 Jan 2014 18:58:47 +0100 Subject: [PATCH 257/457] [Minor] code cleanup --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 9a84ec46c..662c2fe0f 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -55,7 +55,7 @@ public enum DBC { @Override public void assertNotExists(String msg, File file) { if (file.exists()) { - String ex = "Illegal situation as file (" + file + ") exists: {0}"; //$NON-NLS-1$ + String ex = MessageFormat.format("Illegal situation as file ({0}) exists: {0}", file); //$NON-NLS-1$ ex = MessageFormat.format(ex, msg); throw new DbcException(ex); } @@ -64,7 +64,7 @@ public enum DBC { @Override public void assertExists(String msg, File file) { if (!file.exists()) { - String ex = "Illegal situation as file (" + file + ") does not exist: {0}"; //$NON-NLS-1$ + String ex = MessageFormat.format("Illegal situation as file ({0}) does not exist: {0}", file); //$NON-NLS-1$ ex = MessageFormat.format(ex, msg); throw new DbcException(ex); } From da45d3238e842e57bf5eab4570ae9e5365c13f11 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 31 Jan 2014 18:59:34 +0100 Subject: [PATCH 258/457] [Minor] switched to StringHelper.isNotEmpty() --- src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java b/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java index 0dd3c231a..8b0d582a3 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java @@ -80,15 +80,15 @@ public class PathBuilder { String getPathAsString(String type, String subType, String id) { StringBuilder sb = new StringBuilder(this.basePath); - if (!StringHelper.isEmpty(type)) { + if (StringHelper.isNotEmpty(type)) { sb.append(File.separatorChar); sb.append(type); } - if (!StringHelper.isEmpty(subType)) { + if (StringHelper.isNotEmpty(subType)) { sb.append(File.separatorChar); sb.append(subType); } - if (!StringHelper.isEmpty(id)) { + if (StringHelper.isNotEmpty(id)) { sb.append(File.separatorChar); sb.append(getFilename(id)); } From 7ec21839d97f12ac854f1b2f105624d5fc94bf1b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 31 Jan 2014 19:58:24 +0100 Subject: [PATCH 259/457] [Minor] code cleanup --- src/main/java/ch/eitchnet/utils/helper/FileHelper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index a1ad8cdbd..7d73800ec 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -259,7 +259,7 @@ public class FileHelper { if (srcFile.isDirectory()) { dstFile.mkdir(); if (!copy(srcFile.listFiles(), dstFile, checksum)) { - String msg = "Failed to copy contents of {0} to {1}"; + 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; @@ -315,7 +315,7 @@ public class FileHelper { // 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}"; + 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(); @@ -324,7 +324,7 @@ public class FileHelper { } } catch (Exception e) { - String msg = MessageFormat.format("Failed to copy path from {0} to + {1} due to:", fromFile, toFile); + 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; } From a4b459b11d2c441acf8e5e8a051e2f06987c9a7e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Feb 2014 13:14:29 +0100 Subject: [PATCH 260/457] [Minor] extended DBC with assertTrue and assertFalse --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 662c2fe0f..2e783f2d0 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -26,6 +26,26 @@ import ch.eitchnet.utils.helper.StringHelper; public enum DBC { PRE { + + @Override + 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); + } + } + + @Override + 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); + } + } + + @Override public void assertNotEmpty(String msg, String value) { if (StringHelper.isEmpty(value)) { String ex = "Illegal empty value: {0}"; //$NON-NLS-1$ @@ -71,6 +91,10 @@ public enum DBC { } }; + public abstract void assertTrue(String msg, boolean value); + + public abstract void assertFalse(String msg, boolean value); + public abstract void assertNotEmpty(String msg, String value); public abstract void assertNotNull(String msg, Object value); From 58b742d5d9b057732741a97fc9c76ee30269b148 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Feb 2014 13:47:04 +0100 Subject: [PATCH 261/457] [Minor] using Deque instead of Stack --- .../privilege/xml/PrivilegeConfigSaxReader.java | 11 ++++++----- .../privilege/xml/PrivilegeModelSaxReader.java | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index f7e09a822..c1b31c5a2 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -15,9 +15,10 @@ */ package ch.eitchnet.privilege.xml; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; import java.util.Map; -import java.util.Stack; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -33,7 +34,7 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { // private static final Logger logger = LoggerFactory.getLogger(PrivilegeConfigSaxReader.class); - private Stack buildersStack = new Stack(); + private Deque buildersStack = new ArrayDeque(); private PrivilegeContainerModel containerModel; @@ -49,11 +50,11 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals(XmlConstants.XML_CONTAINER)) { - this.buildersStack.add(new ContainerParser()); + this.buildersStack.push(new ContainerParser()); } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { - this.buildersStack.add(new ParametersParser()); + this.buildersStack.push(new ParametersParser()); } else if (qName.equals(XmlConstants.XML_POLICIES)) { - this.buildersStack.add(new PoliciesParser()); + this.buildersStack.push(new PoliciesParser()); } if (!this.buildersStack.isEmpty()) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index dd5b83278..1ac89649e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -16,14 +16,15 @@ package ch.eitchnet.privilege.xml; import java.text.MessageFormat; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.Stack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +47,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { protected static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); - private Stack buildersStack = new Stack(); + private Deque buildersStack = new ArrayDeque(); private List users; private List roles; @@ -76,12 +77,12 @@ public class PrivilegeModelSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals(XmlConstants.XML_USER)) { - this.buildersStack.add(new UserParser()); + this.buildersStack.push(new UserParser()); this.insideUser = true; } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { - this.buildersStack.add(new PropertyParser()); + this.buildersStack.push(new PropertyParser()); } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { - this.buildersStack.add(new RoleParser()); + this.buildersStack.push(new RoleParser()); } if (!this.buildersStack.isEmpty()) From a8264aca3727c64a629718ce26d4c53781ff62be Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 2 Feb 2014 20:31:04 +0100 Subject: [PATCH 262/457] [New] added DBC.assertEquals() including generated tests --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 22 +- .../java/ch/eitchnet/utils/dbc/DBCTest.java | 380 ++++++++++++++++++ 2 files changed, 400 insertions(+), 2 deletions(-) create mode 100644 src/test/java/ch/eitchnet/utils/dbc/DBCTest.java diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 2e783f2d0..199b2bd29 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -27,6 +27,22 @@ public enum DBC { PRE { + @Override + public void assertEquals(String msg, Object value1, Object value2) { + if (value1 == null && value2 == null) + return; + + if (value1 != null && value1.equals(value2)) + return; + + if (value2 != null && value2.equals(value1)) + return; + + String ex = "Values are not equal: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + @Override public void assertTrue(String msg, boolean value) { if (!value) { @@ -75,7 +91,7 @@ public enum DBC { @Override public void assertNotExists(String msg, File file) { if (file.exists()) { - String ex = MessageFormat.format("Illegal situation as file ({0}) exists: {0}", file); //$NON-NLS-1$ + 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); } @@ -84,13 +100,15 @@ public enum DBC { @Override public void assertExists(String msg, File file) { if (!file.exists()) { - String ex = MessageFormat.format("Illegal situation as file ({0}) does not exist: {0}", file); //$NON-NLS-1$ + 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 abstract void assertEquals(String msg, Object value1, Object value2); + public abstract void assertTrue(String msg, boolean value); public abstract void assertFalse(String msg, boolean value); diff --git a/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java b/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java new file mode 100644 index 000000000..f0ed48526 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java @@ -0,0 +1,380 @@ +package ch.eitchnet.utils.dbc; + +import java.io.File; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import ch.eitchnet.utils.dbc.DBC.DbcException; + +/** + * The class DBCTest contains tests for the class {@link DBC}. + * + * @generatedBy CodePro at 2/2/14 8:13 PM + * @author eitch + * @version $Revision: 1.0 $ + */ +@SuppressWarnings("nls") +public class DBCTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_1() throws Exception { + String msg = ""; + Object value1 = null; + Object value2 = null; + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_2() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Values are not equal:"); + String msg = ""; + Object value1 = new Object(); + Object value2 = new Object(); + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + // An unexpected exception was thrown in user code while executing this test: + // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Values are not equal: + // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_3() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Values are not equal:"); + + String msg = ""; + Object value1 = null; + Object value2 = new Object(); + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_4() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Values are not equal:"); + + String msg = ""; + Object value1 = new Object(); + Object value2 = null; + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertEquals_5() throws Exception { + String msg = ""; + Object value1 = "bla"; + Object value2 = "bla"; + + DBC.PRE.assertEquals(msg, value1, value2); + + // add additional test code here + // An unexpected exception was thrown in user code while executing this test: + // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Values are not equal: + // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) + } + + /** + * Run the void assertExists(String,File) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertExists_1() throws Exception { + String msg = ""; + File file = new File("src"); + + DBC.PRE.assertExists(msg, file); + } + + /** + * Run the void assertExists(String,File) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertExists_2() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Illegal situation as file (srcc) does not exist:"); + + String msg = ""; + File file = new File("srcc"); + + DBC.PRE.assertExists(msg, file); + + // add additional test code here + // An unexpected exception was thrown in user code while executing this test: + // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Illegal situation as file () does not exist: + // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertExists(DBC.PRE.java:95) + } + + /** + * Run the void assertFalse(String,boolean) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertFalse_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Expected false, but was true: "); + + String msg = ""; + boolean value = true; + + DBC.PRE.assertFalse(msg, value); + } + + /** + * Run the void assertFalse(String,boolean) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertFalse_2() throws Exception { + String msg = ""; + boolean value = false; + + DBC.PRE.assertFalse(msg, value); + + // add additional test code here + } + + /** + * Run the void assertNotEmpty(String,String) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEmpty_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Illegal empty value: "); + + String msg = "Illegal empty value: "; + String value = ""; + + DBC.PRE.assertNotEmpty(msg, value); + } + + /** + * Run the void assertNotEmpty(String,String) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEmpty_2() throws Exception { + String msg = ""; + String value = "a"; + + DBC.PRE.assertNotEmpty(msg, value); + } + + /** + * Run the void assertNotExists(String,File) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotExists_1() throws Exception { + String msg = ""; + File file = new File("srcc"); + + DBC.PRE.assertNotExists(msg, file); + + // add additional test code here + } + + /** + * Run the void assertNotExists(String,File) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotExists_2() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Illegal situation as file (src) exists: "); + + String msg = ""; + File file = new File("src"); + + DBC.PRE.assertNotExists(msg, file); + + // add additional test code here + } + + /** + * Run the void assertNotNull(String,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotNull_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Illegal null value:"); + + String msg = ""; + Object value = null; + + DBC.PRE.assertNotNull(msg, value); + } + + /** + * Run the void assertNotNull(String,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotNull_2() throws Exception { + String msg = ""; + Object value = new Object(); + + DBC.PRE.assertNotNull(msg, value); + + // add additional test code here + } + + /** + * Run the void assertNull(String,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNull_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Illegal situation as value is not null:"); + + String msg = ""; + Object value = new Object(); + + DBC.PRE.assertNull(msg, value); + } + + /** + * Run the void assertNull(String,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNull_2() throws Exception { + String msg = ""; + Object value = null; + + DBC.PRE.assertNull(msg, value); + + // add additional test code here + } + + /** + * Run the void assertTrue(String,boolean) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertTrue_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Expected true, but was false: "); + + String msg = ""; + boolean value = false; + + DBC.PRE.assertTrue(msg, value); + + // add additional test code here + // An unexpected exception was thrown in user code while executing this test: + // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Expected true, but was false: + // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertTrue(DBC.PRE.java:47) + } + + /** + * Run the void assertTrue(String,boolean) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertTrue_2() throws Exception { + String msg = ""; + boolean value = true; + + DBC.PRE.assertTrue(msg, value); + + // add additional test code here + } +} \ No newline at end of file From 09444817e2094d9a281ed07983c1ab8aaa267db3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 2 Feb 2014 20:42:27 +0100 Subject: [PATCH 263/457] [New] added FileProgressListener with ProgressableFileInputStream --- .../utils/io/FileProgressListener.java | 41 +++++ .../utils/io/FileStreamProgressWatcher.java | 81 ++++++++++ .../utils/io/LoggingFileProgressListener.java | 43 ++++++ .../utils/io/ProgressableFileInputStream.java | 146 ++++++++++++++++++ 4 files changed, 311 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/io/FileProgressListener.java create mode 100644 src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java create mode 100644 src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java create mode 100644 src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java diff --git a/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java b/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java new file mode 100644 index 000000000..b22649b00 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java @@ -0,0 +1,41 @@ +package ch.eitchnet.utils.io; + +/** + *

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

      + * + * @author Robert von Burg + */ +public interface FileProgressListener { + + /** + * Notify the listener that the progress has begun + * + * @param size + * the size of the job which is to be accomplished + */ + public void begin(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(long)} but in case of errors it can be different + */ + public void end(int percent, long position); +} diff --git a/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java b/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java new file mode 100644 index 000000000..9ffc13a46 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java @@ -0,0 +1,81 @@ +package ch.eitchnet.utils.io; + +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * File stream progress monitoring thread + * + * @author Robert von Burg + */ +public class FileStreamProgressWatcher implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(FileStreamProgressWatcher.class); + private ProgressableFileInputStream inputStream; + private boolean run = false; + private FileProgressListener progressListener; + private long millis; + + /** + * @param millis + * @param progressListener + * @param inputStream + */ + public FileStreamProgressWatcher(long millis, FileProgressListener progressListener, + ProgressableFileInputStream inputStream) { + this.millis = millis; + this.progressListener = progressListener; + this.inputStream = inputStream; + } + + @Override + public void run() { + this.run = true; + + this.progressListener.begin(this.inputStream.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) { + + logger.info(MessageFormat.format("Work stopped: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + this.run = false; + int percentComplete = this.inputStream.getPercentComplete(); + this.progressListener.end(percentComplete, this.inputStream.getBytesRead()); + + } catch (Exception e) { + + logger.error(e.getMessage(), e); + this.run = false; + int percentComplete = this.inputStream.getPercentComplete(); + this.progressListener.end(percentComplete, Long.MAX_VALUE); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java b/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java new file mode 100644 index 000000000..4cafa25fd --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java @@ -0,0 +1,43 @@ +package ch.eitchnet.utils.io; + +import java.text.MessageFormat; + +import org.slf4j.Logger; + +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +public class LoggingFileProgressListener implements FileProgressListener { + + private final Logger logger; + private final String name; + + /** + * @param logger + * @param name + */ + public LoggingFileProgressListener(Logger logger, String name) { + this.logger = logger; + this.name = name; + } + + @Override + public void begin(long size) { + String msg = "Starting to read {0} with a size of {1}"; //$NON-NLS-1$ + this.logger.info(MessageFormat.format(msg, this.name, FileHelper.humanizeFileSize(size))); + } + + @Override + public void progress(int percent, long position) { + String msg = "Read {0}% of {1} at position {2}"; //$NON-NLS-1$ + this.logger.info(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$ + this.logger.info(MessageFormat.format(msg, this.name, percent, FileHelper.humanizeFileSize(position))); + } +} diff --git a/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java b/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java new file mode 100644 index 000000000..94890cc72 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java @@ -0,0 +1,146 @@ +package ch.eitchnet.utils.io; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + *

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

      + * + * @author Robert von Burg + */ +public class ProgressableFileInputStream extends FileInputStream { + + private long fileSize; + private long bytesRead; + private 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) read; + } + + /** + * Returns true if {@link #close()} was called, false otherwise + * + * @return the closed + */ + public boolean isClosed() { + return this.closed; + } +} From 50077bb2055054b0e9bb807f9ac645de6fb56f7c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 3 Feb 2014 13:52:59 +0100 Subject: [PATCH 264/457] Update README.md Updated changed dependencies JRE 6 to 7 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7b9074980..46c7a0bcd 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ 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 6 -* JUnit 4.10 (only during tests) +* the Java Runtime Environment 7 +* JUnit 4.11 (test scope) * slf4j 1.7.2 -* slf4j-log4j bindings (only during tests) +* slf4j-log4j bindings (test scope) Features ---------------------- From 4ab52576c0010c0c35cbc41784ba09ec52703729 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 3 Feb 2014 17:54:15 +0100 Subject: [PATCH 265/457] [New] implemented FileProgressListener and ProgressableFileInputStream --- .../utils/io/FileProgressListener.java | 6 +++++- .../utils/io/FileStreamProgressWatcher.java | 21 +++++-------------- .../utils/io/LoggingFileProgressListener.java | 15 ++++++++----- .../utils/io/ProgressableFileInputStream.java | 12 +++++------ 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java b/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java index b22649b00..e7bcd4eeb 100644 --- a/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java +++ b/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java @@ -13,10 +13,14 @@ 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(long size); + public void begin(int percent, long position, long size); /** * Notifies the listener of incremental progress diff --git a/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java b/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java index 9ffc13a46..b6105a200 100644 --- a/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java +++ b/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java @@ -33,44 +33,33 @@ public class FileStreamProgressWatcher implements Runnable { @Override public void run() { this.run = true; - - this.progressListener.begin(this.inputStream.getFileSize()); + 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) { + if (this.run) Thread.sleep(this.millis); - } } catch (InterruptedException e) { - - logger.info(MessageFormat.format("Work stopped: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ 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(); diff --git a/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java b/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java index 4cafa25fd..d5d6faf5f 100644 --- a/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java +++ b/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java @@ -24,20 +24,25 @@ public class LoggingFileProgressListener implements FileProgressListener { } @Override - public void begin(long size) { - String msg = "Starting to read {0} with a size of {1}"; //$NON-NLS-1$ - this.logger.info(MessageFormat.format(msg, this.name, FileHelper.humanizeFileSize(size))); + 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$ - this.logger.info(MessageFormat.format(msg, percent, this.name, FileHelper.humanizeFileSize(position))); + 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$ - this.logger.info(MessageFormat.format(msg, this.name, percent, FileHelper.humanizeFileSize(position))); + log(MessageFormat.format(msg, this.name, FileHelper.humanizeFileSize(position), percent)); } + + private void log(String msg) { + this.logger.info(msg); + } + } diff --git a/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java b/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java index 94890cc72..dbd1514ef 100644 --- a/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java +++ b/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java @@ -8,17 +8,17 @@ import java.io.IOException; /** *

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

      * * @author Robert von Burg */ public class ProgressableFileInputStream extends FileInputStream { - private long fileSize; - private long bytesRead; - private boolean closed; + private volatile long fileSize; + private volatile long bytesRead; + private volatile boolean closed; /** * Constructs a normal {@link FileInputStream} with the given {@link File} @@ -132,7 +132,7 @@ public class ProgressableFileInputStream extends FileInputStream { } double read = (100.0d / this.fileSize * currentRead); - return (int) read; + return (int) Math.ceil(read); } /** From 221a307c884cf696b372b1ce7fbd83b54b72c22a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 11 Feb 2014 08:34:23 +0100 Subject: [PATCH 266/457] [New] implemented a MapOfMaps to easily store map of maps --- .../eitchnet/utils/collections/MapOfMaps.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java new file mode 100644 index 000000000..4a6f6ed15 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java @@ -0,0 +1,128 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.collections; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + *

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

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

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

      + * + *
      + * MapOfMaps<String, String, MyObject> mapOfMaps = new MapOfMaps<>();
      + * 
      + * + * + * @author Robert von Burg + * + * @param + * The key to a map with U as the key and V as the value + * @param + * The key to get a value (leaf) + * @param + * The value stored in the tree (leaf) + */ +public class MapOfMaps { + + private Map> mapOfMaps; + + public MapOfMaps() { + this.mapOfMaps = new HashMap<>(); + } + + public Set keySet() { + return this.mapOfMaps.keySet(); + } + + public Map getMap(T t) { + return this.mapOfMaps.get(t); + } + + public V getElement(T t, U u) { + Map map = this.mapOfMaps.get(t); + if (map == null) + return null; + return map.get(u); + } + + public V addElement(T t, U u, V v) { + Map map = this.mapOfMaps.get(t); + if (map == null) { + map = new HashMap<>(); + this.mapOfMaps.put(t, map); + } + return map.put(u, v); + } + + public void addMap(T t, Map u) { + Map map = this.mapOfMaps.get(t); + if (map == null) { + map = new HashMap<>(); + this.mapOfMaps.put(t, map); + } + map.putAll(u); + } + + public V removeElement(T t, U u) { + Map map = this.mapOfMaps.get(t); + if (map == null) { + return null; + } + V v = map.remove(u); + if (map.isEmpty()) { + this.mapOfMaps.remove(t); + } + + return v; + } + + public Map removeMap(T t) { + return this.mapOfMaps.remove(t); + } + + public void clear() { + Set>> entrySet = this.mapOfMaps.entrySet(); + Iterator>> iter = entrySet.iterator(); + while (iter.hasNext()) { + iter.next().getValue().clear(); + iter.remove(); + } + } + + public boolean containsMap(T t) { + return this.mapOfMaps.containsKey(t); + } + + public boolean containsElement(T t, U u) { + Map map = this.mapOfMaps.get(t); + if (map == null) + return false; + return map.containsKey(u); + } +} From 0a6af4a50ee2065c9dd4a405853bef8ca0f34459 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 14 Feb 2014 20:56:06 +0100 Subject: [PATCH 267/457] [Minor] DBC now also has POST and INTERIM --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 131 ++++++++----------- 1 file changed, 53 insertions(+), 78 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 199b2bd29..8aca39191 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -25,103 +25,78 @@ import ch.eitchnet.utils.helper.StringHelper; */ public enum DBC { - PRE { + PRE, INTERIM, POST; - @Override - public void assertEquals(String msg, Object value1, Object value2) { - if (value1 == null && value2 == null) - return; + public void assertEquals(String msg, Object value1, Object value2) { + if (value1 == null && value2 == null) + return; - if (value1 != null && value1.equals(value2)) - return; + if (value1 != null && value1.equals(value2)) + return; - if (value2 != null && value2.equals(value1)) - return; + if (value2 != null && value2.equals(value1)) + return; - String ex = "Values are not equal: {0}"; //$NON-NLS-1$ + String ex = "Values are not equal: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + 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); } + } - @Override - 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); } + } - @Override - 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 assertNotEmpty(String msg, String value) { + if (StringHelper.isEmpty(value)) { + String ex = "Illegal empty value: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); } + } - @Override - public void assertNotEmpty(String msg, String value) { - if (StringHelper.isEmpty(value)) { - String ex = "Illegal empty value: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); - throw new DbcException(ex); - } + public void assertNotNull(String msg, Object value) { + if (value == null) { + String ex = "Illegal null value: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); } + } - @Override - public void assertNotNull(String msg, Object value) { - if (value == null) { - String ex = "Illegal null value: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); - throw new DbcException(ex); - } + public void assertNull(String msg, Object value) { + if (value != null) { + String ex = "Illegal situation as value is not null: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); } + } - @Override - public void assertNull(String msg, Object value) { - if (value != null) { - String ex = "Illegal situation as value is not null: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); - 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); } + } - @Override - 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); } - - @Override - 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 abstract void assertEquals(String msg, Object value1, Object value2); - - public abstract void assertTrue(String msg, boolean value); - - public abstract void assertFalse(String msg, boolean value); - - public abstract void assertNotEmpty(String msg, String value); - - public abstract void assertNotNull(String msg, Object value); - - public abstract void assertNull(String msg, Object value); - - public abstract void assertNotExists(String msg, File file); - - public abstract void assertExists(String msg, File file); + } public class DbcException extends RuntimeException { private static final long serialVersionUID = 1L; From 5dab5a23ffd0e4b95222a6b01938ad4f2340a2c4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 15 Feb 2014 19:31:47 +0100 Subject: [PATCH 268/457] [Minor] added StringHelper.DOT --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 34b3416f1..61cc47aca 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -40,6 +40,7 @@ public class StringHelper { 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$ From 2647115f6f5e9bf39b44bf8284d7295d1186c232 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 19 Feb 2014 00:07:55 +0100 Subject: [PATCH 269/457] [Minor] added realm name to log message on TX commit --- .../ch/eitchnet/xmlpers/api/TransactionResult.java | 12 +++++++----- .../eitchnet/xmlpers/test/TransactionResultTest.java | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java index 11267944b..dbd72c324 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java @@ -171,21 +171,23 @@ public class TransactionResult { } StringBuilder sb = new StringBuilder(); + sb.append("TX for realm "); + sb.append(getRealm()); switch (this.state) { case OPEN: - sb.append("TX is still open after "); + sb.append(" is still open after "); break; case COMMITTED: - sb.append("TX was completed after "); + sb.append(" was completed after "); break; case ROLLED_BACK: - sb.append("TX was rolled back after "); + sb.append(" was rolled back after "); break; case FAILED: - sb.append("TX has failed after "); + sb.append(" has failed after "); break; default: - sb.append("TX is in unhandled state "); + sb.append(" is in unhandled state "); sb.append(this.state); sb.append(" after "); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java b/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java index 537dc5746..576cc1486 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java @@ -72,7 +72,7 @@ public class TransactionResultTest extends AbstractPersistenceTest { performChanges(txResult); String logMessage = txResult.getLogMessage(); logger.info(logMessage); - assertThat(logMessage, containsString("TX was completed after")); //$NON-NLS-1$ + assertThat(logMessage, containsString("TX for realm defaultRealm was completed after")); //$NON-NLS-1$ assertThat(logMessage, containsString("30 objects in 2 types were modified")); //$NON-NLS-1$ assertThat(txResult.getKeys(), containsInAnyOrder("Resource", "Book")); //$NON-NLS-1$ //$NON-NLS-2$ From a4c82e6704cd14804e578fd1d1477761a925c489 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 24 Feb 2014 21:45:50 +0100 Subject: [PATCH 270/457] [New] added a DBC.PRE.assertNotEquals() --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 12 +++ .../java/ch/eitchnet/utils/dbc/DBCTest.java | 86 +++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 8aca39191..ac9b08313 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -42,6 +42,18 @@ public enum DBC { throw new DbcException(ex); } + public void assertNotEquals(String msg, Object value1, Object value2) { + if (value1 != null && !value1.equals(value2)) + return; + + if (value2 != null && !value2.equals(value1)) + return; + + String ex = "Values are equal: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + public void assertTrue(String msg, boolean value) { if (!value) { String ex = "Expected true, but was false: {0}"; //$NON-NLS-1$ diff --git a/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java b/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java index f0ed48526..2f73cdb45 100644 --- a/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java +++ b/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java @@ -125,6 +125,92 @@ public class DBCTest { // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) } + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_1() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Values are equal:"); + + String msg = ""; + Object value1 = null; + Object value2 = null; + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_2() throws Exception { + String msg = ""; + Object value1 = new Object(); + Object value2 = new Object(); + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_3() throws Exception { + String msg = ""; + Object value1 = null; + Object value2 = new Object(); + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_4() throws Exception { + String msg = ""; + Object value1 = new Object(); + Object value2 = null; + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + + /** + * Run the void assertEquals(String,Object,Object) method test. + * + * @throws Exception + * + * @generatedBy CodePro at 2/2/14 8:13 PM + */ + @Test + public void testAssertNotEquals_5() throws Exception { + this.exception.expect(DbcException.class); + this.exception.expectMessage("Values are equal:"); + + String msg = ""; + Object value1 = "bla"; + Object value2 = "bla"; + + DBC.PRE.assertNotEquals(msg, value1, value2); + } + /** * Run the void assertExists(String,File) method test. * From 127ab93ea74de9fdaf6e3b1d99a9ffd815f59495 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 25 Feb 2014 21:27:53 +0100 Subject: [PATCH 271/457] [Minor] changed DBC assertNotEquals and assertEquals to take args T --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index ac9b08313..a12cf85f3 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -27,7 +27,7 @@ public enum DBC { PRE, INTERIM, POST; - public void assertEquals(String msg, Object value1, Object value2) { + public void assertEquals(String msg, T value1, T value2) { if (value1 == null && value2 == null) return; @@ -42,7 +42,7 @@ public enum DBC { throw new DbcException(ex); } - public void assertNotEquals(String msg, Object value1, Object value2) { + public void assertNotEquals(String msg, T value1, T value2) { if (value1 != null && !value1.equals(value2)) return; From 77f631a2dcd5ec6d854f59fb3f9a3b1659bf5c20 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 14 Mar 2014 14:36:07 +0100 Subject: [PATCH 272/457] [Project] fixed urls of projects --- README.md | 2 +- pom.xml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ba63eaf11..e5eeeb487 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ a LDAP repository with only the Privilege JAR. Privilege is distributed under the GNU Lesser General Public License on Github.com and can be downloaded at - https://github.com/eitch/ch.eitchnet.privilege + https://github.com/eitchnet/ch.eitchnet.privilege The main developer is Robert von Burg who also maintains the Github repository. He is available for all questions regarding Privilege diff --git a/pom.xml b/pom.xml index 110c03a32..0f2ef0408 100644 --- a/pom.xml +++ b/pom.xml @@ -13,18 +13,18 @@ jar 0.2.0-SNAPSHOT ch.eitchnet.privilege - https://github.com/eitch/ch.eitchnet.privilege + https://github.com/eitchnet/ch.eitchnet.privilege 2011 Github Issues - https://github.com/eitch/ch.eitchnet.privilege/issues + https://github.com/eitchnet/ch.eitchnet.privilege/issues - scm:git:https://github.com/eitch/ch.eitchnet.privilege.git - scm:git:git@github.com:eitch/ch.eitchnet.privilege.git - https://github.com/eitch/ch.eitchnet.privilege + scm:git:https://github.com/eitchnet/ch.eitchnet.privilege.git + scm:git:git@github.com:eitchnet/ch.eitchnet.privilege.git + https://github.com/eitchnet/ch.eitchnet.privilege From 6d15ea715bff24e535bd191e5cf18ba65e90e6db Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 14 Mar 2014 14:36:12 +0100 Subject: [PATCH 273/457] [Project] fixed urls of projects --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 143e056af..609b01d1a 100644 --- a/pom.xml +++ b/pom.xml @@ -14,18 +14,18 @@ 0.2.0-SNAPSHOT ch.eitchnet.utils These utils contain project independent helper classes and utilities for reuse - https://github.com/eitch/ch.eitchnet.utils + https://github.com/eitchnet/ch.eitchnet.utils 2011 Github Issues - https://github.com/eitch/ch.eitchnet.utils/issues + https://github.com/eitchnet/ch.eitchnet.utils/issues - scm:git:https://github.com/eitch/ch.eitchnet.utils.git - scm:git:git@github.com:eitch/ch.eitchnet.utils.git - https://github.com/eitch/ch.eitchnet.utils + scm:git:https://github.com/eitchnet/ch.eitchnet.utils.git + scm:git:git@github.com:eitchnet/ch.eitchnet.utils.git + https://github.com/eitchnet/ch.eitchnet.utils From 44613163cb2feab14b418619e4af8d09cebf0272 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 14 Mar 2014 14:36:16 +0100 Subject: [PATCH 274/457] [Project] fixed urls of projects --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index cb6be517c..5bd34d729 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ jar 0.3.0-SNAPSHOT ch.eitchnet.xmlpers - https://github.com/eitch/ch.eitchnet.xmlpers + https://github.com/eitchnet/ch.eitchnet.xmlpers UTF-8 @@ -25,13 +25,13 @@ Github Issues - https://github.com/eitch/ch.eitchnet.xmlpers/issues + https://github.com/eitchnet/ch.eitchnet.xmlpers/issues - scm:git:https://github.com/eitch/ch.eitchnet.xmlpers.git - scm:git:git@github.com:eitch/ch.eitchnet.xmlpers.git - https://github.com/eitch/ch.eitchnet.xmlpers + scm:git:https://github.com/eitchnet/ch.eitchnet.xmlpers.git + scm:git:git@github.com:eitchnet/ch.eitchnet.xmlpers.git + https://github.com/eitchnet/ch.eitchnet.xmlpers From 58f57f36586655468aebcbf1080fb7e72203563d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 27 Mar 2014 18:49:54 +0100 Subject: [PATCH 275/457] [Minor] StringHelper.formatNanos() now understands up to hours --- .../eitchnet/utils/helper/StringHelper.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 61cc47aca..1999200f6 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -523,36 +523,36 @@ public class StringHelper { } /** - * Formats the given number of milliseconds to a time like 0.000s/ms + * Formats the given number of milliseconds to a time like 0.000h/m/s/ms/us/ns * * @param millis * the number of milliseconds * - * @return format the given number of milliseconds to a time like 0.000s/ms + * @return format the given number of milliseconds to a time like 0.000h/m/s/ms/us/ns */ public static String formatMillisecondsDuration(final long millis) { - if (millis > 1000) { - return String.format("%.3fs", (((double) millis) / 1000)); //$NON-NLS-1$ - } - - return millis + "ms"; //$NON-NLS-1$ + return formatNanoDuration(millis * 1000000L); } /** - * Formats the given number of nanoseconds to a time like 0.000s/ms/us/ns + * Formats the given number of nanoseconds to a time like 0.000h/m/s/ms/us/ns * * @param nanos * the number of nanoseconds * - * @return format the given number of nanoseconds to a time like 0.000s/ms/us/ns + * @return format the given number of nanoseconds to a time like 0.000h/m/s/ms/us/ns */ public static String formatNanoDuration(final long nanos) { - if (nanos > 1000000000) { - return String.format("%.3fs", (((double) nanos) / 1000000000)); //$NON-NLS-1$ - } else if (nanos > 1000000) { - return String.format("%.3fms", (((double) nanos) / 1000000)); //$NON-NLS-1$ - } else if (nanos > 1000) { - return String.format("%.3fus", (((double) nanos) / 1000)); //$NON-NLS-1$ + if (nanos > 3600000000000L) { + return String.format("%.3fh", (nanos / 3600000000000.0D)); //$NON-NLS-1$ + } else if (nanos > 60000000000L) { + return String.format("%.3fm", (nanos / 60000000000.0D)); //$NON-NLS-1$ + } else if (nanos > 1000000000L) { + return String.format("%.3fs", (nanos / 1000000000.0D)); //$NON-NLS-1$ + } else if (nanos > 1000000L) { + return String.format("%.3fms", (nanos / 1000000.0D)); //$NON-NLS-1$ + } else if (nanos > 1000L) { + return String.format("%.3fus", (nanos / 1000.0D)); //$NON-NLS-1$ } else { return nanos + "ns"; //$NON-NLS-1$ } From 2adf809515f0219f9a5f3ad838ab7a3547813d72 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 27 Mar 2014 18:50:14 +0100 Subject: [PATCH 276/457] [Minor] code formatting --- .../ch/eitchnet/xmlpers/test/model/Resource.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java b/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java index 5567f3e47..a9dd1cca7 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java @@ -28,7 +28,7 @@ public class Resource { private Map parameters = new HashMap(); /** - * + * */ public Resource() { // empty constructor @@ -73,8 +73,7 @@ public class Resource { } /** - * @param id - * the id to set + * @param id the id to set */ public void setId(String id) { this.id = id; @@ -88,8 +87,7 @@ public class Resource { } /** - * @param name - * the name to set + * @param name the name to set */ public void setName(String name) { this.name = name; @@ -103,8 +101,7 @@ public class Resource { } /** - * @param type - * the type to set + * @param type the type to set */ public void setType(String type) { this.type = type; @@ -121,4 +118,4 @@ public class Resource { public Parameter getParameterBy(String id) { return this.parameters.get(id); } -} \ No newline at end of file +} From 468c79baf057dbd4968d5c941fdee07c79f22221 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 28 Mar 2014 15:22:52 +0100 Subject: [PATCH 277/457] [Minor] StringHelper.formatNanos() does not use decimals anymore --- .../eitchnet/utils/helper/StringHelper.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 1999200f6..b7f1b6302 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -523,36 +523,36 @@ public class StringHelper { } /** - * Formats the given number of milliseconds to a time like 0.000h/m/s/ms/us/ns + * 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 0.000h/m/s/ms/us/ns + * @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 0.000h/m/s/ms/us/ns + * 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 0.000h/m/s/ms/us/ns + * @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("%.3fh", (nanos / 3600000000000.0D)); //$NON-NLS-1$ - } else if (nanos > 60000000000L) { - return String.format("%.3fm", (nanos / 60000000000.0D)); //$NON-NLS-1$ - } else if (nanos > 1000000000L) { - return String.format("%.3fs", (nanos / 1000000000.0D)); //$NON-NLS-1$ - } else if (nanos > 1000000L) { - return String.format("%.3fms", (nanos / 1000000.0D)); //$NON-NLS-1$ - } else if (nanos > 1000L) { - return String.format("%.3fus", (nanos / 1000.0D)); //$NON-NLS-1$ + 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$ } From 1b1eb982c2ce3db61e9b1363f6487593cec7163a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 28 Mar 2014 19:05:34 +0100 Subject: [PATCH 278/457] [Minor] Added MapOfMaps.size() and .size(T) methods --- .../ch/eitchnet/utils/collections/MapOfMaps.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java index 4a6f6ed15..887999eaf 100644 --- a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java +++ b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java @@ -125,4 +125,18 @@ public class MapOfMaps { return false; return map.containsKey(u); } + + public int size() { + int size = 0; + Set>> entrySet = this.mapOfMaps.entrySet(); + Iterator>> iter = entrySet.iterator(); + while (iter.hasNext()) { + size += iter.next().getValue().size(); + } + return size; + } + + public int size(T t) { + return this.mapOfMaps.get(t).size(); + } } From 2e1412de9364138b38dee1f7d00fad20b48ba077 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 15 Apr 2014 19:18:11 +0200 Subject: [PATCH 279/457] [Major] Removed the use of a ThreadLocal for the PrivilegeContext ThreadLocals are bad idea when ClassLoaders come into play, so removing the need makes Privilege better usable in different contexts. --- config/PrivilegeModel.xml | 17 ++++ .../handler/DefaultPrivilegeHandler.java | 4 +- .../privilege/model/PrivilegeContext.java | 44 +-------- .../privilege/policy/DefaultPrivilege.java | 6 +- .../privilege/policy/PrivilegePolicy.java | 6 +- .../privilege/test/PrivilegeTest.java | 91 +++++++++++++------ .../ch/eitchnet/privilege/test/XmlTest.java | 4 +- .../test/model/TestSystemUserAction.java | 2 - 8 files changed, 92 insertions(+), 82 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 9f15dcc53..488ae379a 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -28,6 +28,17 @@
      + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + system_admin_privileges2 + + +
      @@ -49,6 +60,12 @@
      + + + true + + + hello diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index fe9f2a175..f48a10964 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -41,7 +41,6 @@ import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; -import ch.eitchnet.utils.helper.ClassHelper; /** *

      @@ -1095,7 +1094,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // instantiate the policy PrivilegePolicy policy; try { - policy = ClassHelper.instantiateClass(policyClazz); + + policy = policyClazz.newInstance(); } catch (Exception e) { String msg = "The class for the policy with the name {0} does not exist!{1}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, policyName, policyName); diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index d7780b635..8cd33eef1 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -108,48 +108,6 @@ public class PrivilegeContext { } // delegate to the policy - policy.validateAction(privilege, restrictable); - } - - // - // ThreadLocal binding - // - - private static final ThreadLocal threadLocal = new ThreadLocal(); - - /** - * Returns the currently {@link ThreadLocal} bound {@link PrivilegeContext} or throws a {@link PrivilegeException} - * if none is set - * - * @return the currently {@link ThreadLocal} bound {@link PrivilegeContext} or throws a {@link PrivilegeException} - * if none is set - * - * @throws PrivilegeException - * if no {@link PrivilegeContext} is set - */ - public static PrivilegeContext get() throws PrivilegeException { - PrivilegeContext privilegeContext = PrivilegeContext.threadLocal.get(); - if (privilegeContext == null) { - throw new PrivilegeException("There is no PrivilegeContext currently bound to the ThreadLocal!"); //$NON-NLS-1$ - } - return privilegeContext; - } - - /** - * Bind a {@link PrivilegeContext} to the {@link ThreadLocal} or throws a {@link PrivilegeException} if one is - * already bound - * - * @param privilegeContext - * the {@link PrivilegeContext} to bind - * - * @throws PrivilegeException - * if e {@link PrivilegeContext} is already bound - */ - public static void set(PrivilegeContext privilegeContext) throws PrivilegeException { - PrivilegeContext currentContext = PrivilegeContext.threadLocal.get(); - if (privilegeContext != null && currentContext != null) { - throw new PrivilegeException("There already is a PrivilegeContext bound to the ThreadLocal!"); //$NON-NLS-1$ - } - PrivilegeContext.threadLocal.set(privilegeContext); + policy.validateAction(this, privilege, restrictable); } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 2a10f3270..32375ee3b 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -40,7 +40,7 @@ public class DefaultPrivilege implements PrivilegePolicy { * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(IPrivilege, Restrictable) */ @Override - public void validateAction(IPrivilege privilege, Restrictable restrictable) { + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { if (privilege == null) throw new PrivilegeException(PrivilegeMessages.getString("Privilege.privilegeNull")); //$NON-NLS-1$ @@ -82,7 +82,7 @@ public class DefaultPrivilege implements PrivilegePolicy { if (privilege.isDenied(privilegeValue)) { // then throw access denied String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ - PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); + ctx.getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } @@ -92,7 +92,7 @@ public class DefaultPrivilege implements PrivilegePolicy { // default is not allowed String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ - PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); + ctx.getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index a585ef589..ba698b174 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -17,6 +17,7 @@ package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; @@ -38,6 +39,8 @@ public interface PrivilegePolicy { /** * Checks if the given {@link Role} and the given {@link IPrivilege} has access to the given {@link Restrictable} * + * @param context + * the privilege context * @param privilege * the {@link IPrivilege} containing the permissions * @param restrictable @@ -46,5 +49,6 @@ public interface PrivilegePolicy { * @throws AccessDeniedException * if action not allowed */ - public void validateAction(IPrivilege privilege, Restrictable restrictable) throws AccessDeniedException; + public abstract void validateAction(PrivilegeContext context, IPrivilege privilege, Restrictable restrictable) + throws AccessDeniedException; } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 6d3b1b3ea..3968c6e25 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -27,7 +27,9 @@ import java.util.Map; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,6 +65,7 @@ public class PrivilegeTest { private static final String BOB = "bob"; private static final String TED = "ted"; private static final String SYSTEM_USER_ADMIN = "system_admin"; + private static final String SYSTEM_USER_ADMIN2 = "system_admin2"; private static final byte[] PASS_BOB = "admin1".getBytes(); private static final String ROLE_APP_USER = "AppUser"; private static final String ROLE_TEMP = "temp"; @@ -73,7 +76,11 @@ public class PrivilegeTest { private static final Logger logger = LoggerFactory.getLogger(PrivilegeTest.class); + @Rule + public ExpectedException exception = ExpectedException.none(); + private static PrivilegeHandler privilegeHandler; + private PrivilegeContext ctx; /** * @throws Exception @@ -151,19 +158,20 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(username, password); assertTrue("Certificate is null!", certificate != null); PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); - PrivilegeContext.set(privilegeContext); + this.ctx = privilegeContext; } private void logout() { - try { - PrivilegeContext privilegeContext = PrivilegeContext.get(); - privilegeHandler.invalidateSession(privilegeContext.getCertificate()); - } catch (PrivilegeException e) { - String msg = "There is no PrivilegeContext currently bound to the ThreadLocal!"; - if (!e.getMessage().equals(msg)) - throw e; - } finally { - PrivilegeContext.set(null); + if (this.ctx != null) { + try { + PrivilegeContext privilegeContext = this.ctx; + this.ctx = null; + privilegeHandler.invalidateSession(privilegeContext.getCertificate()); + } catch (PrivilegeException e) { + String msg = "There is no PrivilegeContext currently bound to the ThreadLocal!"; + if (!e.getMessage().equals(msg)) + throw e; + } } } @@ -176,8 +184,9 @@ public class PrivilegeTest { } } - @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { + exception.expect(AccessDeniedException.class); + exception.expectMessage("blabla"); try { login(ADMIN, ArraysHelper.copyOf(PASS_BAD)); } finally { @@ -185,8 +194,9 @@ public class PrivilegeTest { } } - @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { + exception.expect(PrivilegeException.class); + exception.expectMessage("blabla"); try { login(ADMIN, null); } finally { @@ -202,7 +212,7 @@ public class PrivilegeTest { Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(ROLE_TEMP, privilegeMap); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceRole(certificate, roleRep); privilegeHandler.persist(certificate); } finally { @@ -217,7 +227,7 @@ public class PrivilegeTest { // see if admin can perform restrictable Restrictable restrictable = new TestRestrictable(); - PrivilegeContext.get().validateAction(restrictable); + this.ctx.validateAction(restrictable); } finally { logout(); @@ -238,8 +248,11 @@ public class PrivilegeTest { /** * Checks that the system user can not perform a valid action, but illegal privilege */ - @Test(expected = PrivilegeException.class) + @Test public void testPerformSystemRestrictableFailPrivilege() throws Exception { + exception.expect(PrivilegeException.class); + exception + .expectMessage("User system_admin does not have Privilege ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -251,11 +264,31 @@ public class PrivilegeTest { } } + /** + * Checks that the system user can not perform a valid action, but illegal privilege + */ + @Test + public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { + exception.expect(PrivilegeException.class); + exception.expectMessage("User system_admin2 does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable"); + try { + // create the action to be performed as a system user + TestSystemUserActionDeny action = new TestSystemUserActionDeny(); + + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN2, action); + } finally { + logout(); + } + } + /** * System user may not login */ - @Test(expected = AccessDeniedException.class) + @Test public void testLoginSystemUser() throws Exception { + exception.expect(AccessDeniedException.class); + exception.expectMessage("User system_admin is a system user and may not login!"); try { login(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); } finally { @@ -268,7 +301,7 @@ public class PrivilegeTest { try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Restrictable restrictable = new TestRestrictable(); - PrivilegeContext.get().validateAction(restrictable); + this.ctx.validateAction(restrictable); } finally { logout(); } @@ -325,7 +358,7 @@ public class PrivilegeTest { login(BOB, ArraysHelper.copyOf(PASS_BOB)); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); - PrivilegeContext.get().validateAction(restrictable); + this.ctx.validateAction(restrictable); } finally { logout(); } @@ -335,7 +368,7 @@ public class PrivilegeTest { try { // testAddAppRoleToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); logger.info("Added " + ROLE_APP_USER + " to " + BOB); privilegeHandler.persist(certificate); @@ -352,7 +385,7 @@ public class PrivilegeTest { login(BOB, ArraysHelper.copyOf(PASS_BOB)); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); - PrivilegeContext.get().validateAction(restrictable); + this.ctx.validateAction(restrictable); fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; @@ -375,7 +408,7 @@ public class PrivilegeTest { try { // testTedChangesOwnPwd login(TED, ArraysHelper.copyOf(PASS_DEF)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_TED)); } finally { logout(); @@ -387,7 +420,7 @@ public class PrivilegeTest { // testSetTedPwdAsBob login(BOB, ArraysHelper.copyOf(PASS_BOB)); // set ted's password to default - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_DEF)); privilegeHandler.persist(certificate); } finally { @@ -419,7 +452,7 @@ public class PrivilegeTest { roles.add(ROLE_USER); userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); privilegeHandler.persist(certificate); @@ -432,7 +465,7 @@ public class PrivilegeTest { try { // testAddAdminRoleToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); privilegeHandler.persist(certificate); @@ -452,7 +485,7 @@ public class PrivilegeTest { // let's add a new user Ted userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, new HashMap()); - certificate = PrivilegeContext.get().getCertificate(); + certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceUser(certificate, userRep, null); fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { @@ -476,7 +509,7 @@ public class PrivilegeTest { try { // testAddRoleUserToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); privilegeHandler.persist(certificate); logout(); @@ -491,7 +524,7 @@ public class PrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceRole(certificate, roleRep); privilegeHandler.persist(certificate); } finally { @@ -517,7 +550,7 @@ public class PrivilegeTest { try { // testEnableUserBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); privilegeHandler.persist(certificate); } finally { @@ -546,7 +579,7 @@ public class PrivilegeTest { // let's add a new user bob UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, new HashMap()); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + BOB); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index dbdc7bc6f..a120c4d6b 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -168,8 +168,8 @@ public class XmlTest { List roles = xmlHandler.getRoles(); assertNotNull(roles); - assertEquals(2, users.size()); - assertEquals(4, roles.size()); + assertEquals(3, users.size()); + assertEquals(5, roles.size()); // assert model diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index 86c1613e2..5b64a5421 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -27,8 +27,6 @@ public class TestSystemUserAction implements SystemUserAction { @Override public void execute(PrivilegeContext context) { TestSystemRestrictable restrictable = new TestSystemRestrictable(); - PrivilegeContext.set(context); context.validateAction(restrictable); - PrivilegeContext.set(null); } } From ec5ff685a31e946a1d7ae42eab94b35e2686a193 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 31 Jul 2014 16:09:10 +0200 Subject: [PATCH 280/457] [New] Added new ObjectDao.removeAllBy() methods --- .../ch/eitchnet/xmlpers/api/ObjectDao.java | 69 ++++++++++++++++++- .../eitchnet/xmlpers/util/AssertionUtil.java | 2 +- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index a7c27d40b..9f5ab34cb 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -22,11 +22,14 @@ import static ch.eitchnet.xmlpers.util.AssertionUtil.assertNotNull; import static ch.eitchnet.xmlpers.util.AssertionUtil.assertObjectRead; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import ch.eitchnet.utils.objectfilter.ObjectFilter; import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.SubTypeRef; +import ch.eitchnet.xmlpers.objref.TypeRef; /** * @author Robert von Burg @@ -112,6 +115,68 @@ public class ObjectDao { } } + public long removeAllBy(TypeRef typeRef) { + assertNotClosed(); + + long removed = 0; + + Set refs = new HashSet(); + typeRef.lock(); + refs.add(typeRef); + try { + + Set types = tx.getMetadataDao().queryTypeSet(typeRef); + for (String type : types) { + ObjectRef childTypeRef = typeRef.getChildTypeRef(tx, type); + childTypeRef.lock(); + refs.add(childTypeRef); + + Set ids = queryKeySet(childTypeRef); + for (String id : ids) { + + ObjectRef idRef = childTypeRef.getChildIdRef(tx, id); + + PersistenceContext ctx = createCtx(idRef); + ctx.getObjectRef().lock(); + this.objectFilter.remove(ctx.getObjectRef().getType(), ctx); + removed++; + } + } + } finally { + for (ObjectRef ref : refs) { + ref.unlock(); + } + } + + return removed; + } + + public long removeAllBy(SubTypeRef subTypeRef) { + assertNotClosed(); + assertIsNotRootRef(subTypeRef); + assertIsNotIdRef(subTypeRef); + + long removed = 0; + + subTypeRef.lock(); + try { + Set ids = queryKeySet(subTypeRef); + for (String id : ids) { + + ObjectRef idRef = subTypeRef.getChildIdRef(tx, id); + + PersistenceContext ctx = createCtx(idRef); + ctx.getObjectRef().lock(); + this.objectFilter.remove(ctx.getObjectRef().getType(), ctx); + removed++; + } + } finally { + subTypeRef.unlock(); + } + + return removed; + } + public void removeById(ObjectRef objectRef) { assertNotClosed(); assertIsIdRef(objectRef); @@ -192,7 +257,7 @@ public class ObjectDao { } } - public Set queryKeySet(ObjectRef parentRef) { + public Set queryKeySet(ObjectRef parentRef) { assertNotClosed(); assertIsNotIdRef(parentRef); @@ -206,7 +271,7 @@ public class ObjectDao { } } - public long querySize(ObjectRef parentRef) { + public long querySize(ObjectRef parentRef) { assertNotClosed(); assertIsNotIdRef(parentRef); diff --git a/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java b/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java index 0a0fe4a9e..6a3c9535a 100644 --- a/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java +++ b/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java @@ -53,7 +53,7 @@ public class AssertionUtil { } public static void assertIsNotRootRef(ObjectRef objectRef) { - if (!objectRef.isRoot()) { + if (objectRef.isRoot()) { String msg = "RootRef not expected {0}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, objectRef.getName()); throw new IllegalArgumentException(msg); From 32c2c43fbdd86fb7618d8c0271dd6f20619103de Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 4 Aug 2014 00:44:09 +0200 Subject: [PATCH 281/457] [New] added Certificate.sessionDataMap for mutable session information --- .../ch/eitchnet/privilege/model/Certificate.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index ba2f74bc1..b7e38ac5b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -17,11 +17,13 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Collections; +import java.util.HashMap; import java.util.Locale; import java.util.Map; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.utils.helper.StringHelper; /** @@ -43,6 +45,7 @@ public final class Certificate implements Serializable { private Locale locale; private Map propertyMap; + private Map sessionDataMap; /** * Default constructor initializing with all information needed for this certificate @@ -93,15 +96,28 @@ public final class Certificate implements Serializable { this.propertyMap = Collections.emptyMap(); else this.propertyMap = Collections.unmodifiableMap(propertyMap); + + this.sessionDataMap = new HashMap<>(); } /** + * Returns the {@link User User's} property map. The map is immutable + * * @return the propertyMap */ public Map getPropertyMap() { return this.propertyMap; } + /** + * Returns a mutable {@link Map} for storing session relevant data + * + * @return the sessionDataMap + */ + public Map getSessionDataMap() { + return this.sessionDataMap; + } + /** * @return the locale */ From 8b1af0667b313bfb2396f0c3eef5049e3fb06631 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 4 Aug 2014 11:47:01 +0200 Subject: [PATCH 282/457] [New] Added method ObjectDao.hasElement() --- .../java/ch/eitchnet/xmlpers/api/FileDao.java | 7 +++++ .../ch/eitchnet/xmlpers/api/ObjectDao.java | 30 ++++++++++++------- .../ch/eitchnet/xmlpers/test/LockingTest.java | 1 + 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java index 2a4eeb3aa..8a7e54af4 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java @@ -46,6 +46,13 @@ public class FileDao { } } + public boolean exists(PersistenceContext ctx) { + ObjectRef objectRef = ctx.getObjectRef(); + assertIsIdRef(IoOperation.READ, objectRef); + File path = objectRef.getPath(this.pathBuilder); + return path.exists(); + } + public void performCreate(PersistenceContext ctx) { ObjectRef objectRef = ctx.getObjectRef(); assertIsIdRef(IoOperation.CREATE, objectRef); diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index 9f5ab34cb..c2a492ed5 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -125,16 +125,16 @@ public class ObjectDao { refs.add(typeRef); try { - Set types = tx.getMetadataDao().queryTypeSet(typeRef); + Set types = this.tx.getMetadataDao().queryTypeSet(typeRef); for (String type : types) { - ObjectRef childTypeRef = typeRef.getChildTypeRef(tx, type); + ObjectRef childTypeRef = typeRef.getChildTypeRef(this.tx, type); childTypeRef.lock(); refs.add(childTypeRef); Set ids = queryKeySet(childTypeRef); for (String id : ids) { - ObjectRef idRef = childTypeRef.getChildIdRef(tx, id); + ObjectRef idRef = childTypeRef.getChildIdRef(this.tx, id); PersistenceContext ctx = createCtx(idRef); ctx.getObjectRef().lock(); @@ -163,7 +163,7 @@ public class ObjectDao { Set ids = queryKeySet(subTypeRef); for (String id : ids) { - ObjectRef idRef = subTypeRef.getChildIdRef(tx, id); + ObjectRef idRef = subTypeRef.getChildIdRef(this.tx, id); PersistenceContext ctx = createCtx(idRef); ctx.getObjectRef().lock(); @@ -206,6 +206,19 @@ public class ObjectDao { } } + public boolean hasElement(ObjectRef objectRef) { + assertNotClosed(); + assertIsIdRef(objectRef); + + objectRef.lock(); + try { + PersistenceContext ctx = objectRef. createPersistenceContext(this.tx); + return this.fileDao.exists(ctx); + } finally { + objectRef.unlock(); + } + } + public T queryById(ObjectRef objectRef) { assertNotClosed(); assertIsIdRef(objectRef); @@ -213,13 +226,8 @@ public class ObjectDao { objectRef.lock(); try { PersistenceContext ctx = objectRef. createPersistenceContext(this.tx); - ctx.getObjectRef().lock(); - try { - this.fileDao.performRead(ctx); - return ctx.getObject(); - } finally { - ctx.getObjectRef().unlock(); - } + this.fileDao.performRead(ctx); + return ctx.getObject(); } finally { objectRef.unlock(); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java index ce8574cb6..473be20d4 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -165,6 +165,7 @@ public class LockingTest extends AbstractPersistenceTest { this.resourceId = resourceId; } + @Override public void run() { logger.info("Waiting for ok to work..."); //$NON-NLS-1$ From cfb9e3e0ba3bb6771b7835f889dbd1fe1f3b441e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 4 Aug 2014 12:45:47 +0200 Subject: [PATCH 283/457] [New] added DBC.assertNotEmpty() methods for arrays and collections --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index a12cf85f3..21d2d2755 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -17,6 +17,7 @@ package ch.eitchnet.utils.dbc; import java.io.File; import java.text.MessageFormat; +import java.util.Collection; import ch.eitchnet.utils.helper.StringHelper; @@ -78,6 +79,24 @@ public enum DBC { } } + public void assertNotEmpty(String msg, Object[] array) { + assertNotNull(msg, array); + if (array.length == 0) { + String ex = "Illegal empty value: {0}"; //$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 = "Illegal empty value: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + public void assertNotNull(String msg, Object value) { if (value == null) { String ex = "Illegal null value: {0}"; //$NON-NLS-1$ From 2a951e41ceea757ba5e157c9788971fd87f04511 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 4 Aug 2014 17:53:54 +0200 Subject: [PATCH 284/457] [Minor] fixed formatting of SystemHelper.asString() --- src/main/java/ch/eitchnet/utils/helper/SystemHelper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java index 3b72d38d1..f7f97d8f0 100644 --- a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java @@ -59,11 +59,11 @@ public class SystemHelper { public static String asString() { StringBuilder sb = new StringBuilder(); sb.append(SystemHelper.osName); - sb.append(StringHelper.EMPTY); + sb.append(StringHelper.SPACE); sb.append(SystemHelper.osArch); - sb.append(StringHelper.EMPTY); + sb.append(StringHelper.SPACE); sb.append(SystemHelper.osVersion); - sb.append(StringHelper.EMPTY); + sb.append(StringHelper.SPACE); sb.append("on Java "); //$NON-NLS-1$ sb.append(SystemHelper.javaVendor); sb.append(" version "); //$NON-NLS-1$ From 2a05432152c99b37bc12550e3ee55c03533f0e62 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 7 Aug 2014 00:42:36 +0200 Subject: [PATCH 285/457] [New] Added DBC.assertEmpty()-methods --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 21d2d2755..68d1e1928 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -71,6 +71,32 @@ public enum DBC { } } + public void assertEmpty(String msg, String value) { + if (!StringHelper.isEmpty(value)) { + String ex = "Illegal non-empty value: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertEmpty(String msg, Object[] array) { + assertNotNull(msg, array); + if (array.length != 0) { + String ex = "Illegal non-empty value: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + + public void assertEmpty(String msg, Collection collection) { + assertNotNull(msg, collection); + if (!collection.isEmpty()) { + String ex = "Illegal non-empty value: {0}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg); + throw new DbcException(ex); + } + } + public void assertNotEmpty(String msg, String value) { if (StringHelper.isEmpty(value)) { String ex = "Illegal empty value: {0}"; //$NON-NLS-1$ From 383de8c8710c843f1e2afa3f9686989d0c84fa2f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 8 Aug 2014 11:09:41 +0200 Subject: [PATCH 286/457] [Devel] added communication package [Devel] added communication package [Devel] added communication package [Devel] added communication package [Devel] added communication package [Devel] added communication package [Devel] added communication package [Devel] added communication package [Devel] added communication package [Devel] added communication package --- .../ch/eitchnet/communication/CommandKey.java | 68 +++ .../CommunicationConnection.java | 360 +++++++++++ .../communication/CommunicationEndpoint.java | 36 ++ .../communication/ConnectionException.java | 18 + .../communication/ConnectionMessages.java | 147 +++++ .../communication/ConnectionMode.java | 40 ++ .../communication/ConnectionObserver.java | 24 + .../communication/ConnectionState.java | 39 ++ .../ch/eitchnet/communication/IoMessage.java | 99 +++ .../communication/IoMessageVisitor.java | 25 + .../communication/StreamMessageVisitor.java | 22 + .../console/ConsoleEndpoint.java | 63 ++ .../console/ConsoleMessageVisitor.java | 11 + .../communication/file/FileEndpoint.java | 242 ++++++++ .../communication/file/FileEndpointMode.java | 20 + .../tcpip/ClientSocketEndpoint.java | 510 ++++++++++++++++ .../tcpip/ServerSocketEndpoint.java | 571 ++++++++++++++++++ .../tcpip/SocketEndpointConstants.java | 67 ++ .../tcpip/SocketMessageVisitor.java | 15 + .../utils/collections/MapOfLists.java | 120 ++++ .../eitchnet/utils/collections/MapOfMaps.java | 9 +- .../communication/AbstractEndpointTest.java | 40 ++ .../communication/ConsoleEndpointTest.java | 61 ++ .../communication/FileEndpointTest.java | 120 ++++ .../communication/SocketEndpointTest.java | 134 ++++ .../communication/TestConnectionObserver.java | 23 + .../eitchnet/communication/TestIoMessage.java | 25 + 27 files changed, 2908 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ch/eitchnet/communication/CommandKey.java create mode 100644 src/main/java/ch/eitchnet/communication/CommunicationConnection.java create mode 100644 src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java create mode 100644 src/main/java/ch/eitchnet/communication/ConnectionException.java create mode 100644 src/main/java/ch/eitchnet/communication/ConnectionMessages.java create mode 100644 src/main/java/ch/eitchnet/communication/ConnectionMode.java create mode 100644 src/main/java/ch/eitchnet/communication/ConnectionObserver.java create mode 100644 src/main/java/ch/eitchnet/communication/ConnectionState.java create mode 100644 src/main/java/ch/eitchnet/communication/IoMessage.java create mode 100644 src/main/java/ch/eitchnet/communication/IoMessageVisitor.java create mode 100644 src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java create mode 100644 src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java create mode 100644 src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java create mode 100644 src/main/java/ch/eitchnet/communication/file/FileEndpoint.java create mode 100644 src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java create mode 100644 src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java create mode 100644 src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java create mode 100644 src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java create mode 100644 src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java create mode 100644 src/main/java/ch/eitchnet/utils/collections/MapOfLists.java create mode 100644 src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java create mode 100644 src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java create mode 100644 src/test/java/ch/eitchnet/communication/FileEndpointTest.java create mode 100644 src/test/java/ch/eitchnet/communication/SocketEndpointTest.java create mode 100644 src/test/java/ch/eitchnet/communication/TestConnectionObserver.java create mode 100644 src/test/java/ch/eitchnet/communication/TestIoMessage.java diff --git a/src/main/java/ch/eitchnet/communication/CommandKey.java b/src/main/java/ch/eitchnet/communication/CommandKey.java new file mode 100644 index 000000000..b65ee5aa6 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/CommandKey.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.communication; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public class CommandKey { + private final String key1; + private final String key2; + private final int hashCode; + + /** + * @param key1 + * @param key2 + */ + public CommandKey(String key1, String key2) { + this.key1 = key1; + this.key2 = key2; + + final int prime = 31; + int result = 1; + result = prime * result + ((this.key1 == null) ? 0 : this.key1.hashCode()); + result = prime * result + ((this.key2 == null) ? 0 : this.key2.hashCode()); + this.hashCode = result; + } + + public static CommandKey key(String key1, String key2) { + return new CommandKey(key1, key2); + } + + @Override + public int hashCode() { + return this.hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CommandKey other = (CommandKey) obj; + return this.key1.equals(other.key1) && this.key2.equals(other.key2); + } + + @Override + public String toString() { + return this.key1 + StringHelper.COLON + this.key2; + } +} diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java new file mode 100644 index 000000000..49faf5d44 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.communication; + +import 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.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public class CommunicationConnection implements Runnable { + + protected static final Logger logger = LoggerFactory.getLogger(CommunicationConnection.class); + + private String id; + private ConnectionMode mode; + private Map parameters; + + private ConnectionState state; + private String stateMsg; + + private BlockingDeque messageQueue; + private Thread queueThread; + private volatile boolean run; + private MapOfLists connectionObservers; + + private CommunicationEndpoint endpoint; + private IoMessageVisitor converter; + + public CommunicationConnection(String id, ConnectionMode mode, Map parameters, + CommunicationEndpoint endpoint, IoMessageVisitor converter) { + this.id = id; + this.mode = mode; + this.parameters = parameters; + this.endpoint = endpoint; + this.converter = converter; + + this.state = ConnectionState.CREATED; + this.stateMsg = this.state.toString(); + this.messageQueue = new LinkedBlockingDeque<>(); + this.connectionObservers = new MapOfLists<>(); + } + + public String getId() { + return this.id; + } + + public int getQueueSize() { + return this.messageQueue.size(); + } + + public ConnectionState getState() { + return this.state; + } + + public String getStateMsg() { + return this.stateMsg; + } + + public ConnectionMode getMode() { + return this.mode; + } + + public Map getParameters() { + return this.parameters; + } + + public void clearQueue() { + this.messageQueue.clear(); + } + + public void addConnectionObserver(CommandKey key, ConnectionObserver observer) { + synchronized (this.connectionObservers) { + this.connectionObservers.addElement(key, observer); + } + } + + public void removeConnectionObserver(CommandKey key, ConnectionObserver observer) { + synchronized (this.connectionObservers) { + this.connectionObservers.removeElement(key, observer); + } + } + + public void notifyStateChange(ConnectionState state, String stateMsg) { + this.state = state; + this.stateMsg = 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.converter.configure(this); + this.endpoint.configure(this, this.converter); + this.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: + logger.info("Connecting..."); //$NON-NLS-1$ + if (this.queueThread != null) { + logger.warn(MessageFormat.format("{0}: Already connected!", this.id)); //$NON-NLS-1$ + } else { + logger.info(MessageFormat.format("Starting Integration connection {0}...", this.id)); //$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 entpoint when a new message has been received and parsed + * + * @param message + */ + public void notify(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); + + List observers; + synchronized (this.connectionObservers) { + List list = this.connectionObservers.getList(message.getKey()); + if (list == null) + return; + + observers = new ArrayList<>(list); + } + + for (ConnectionObserver observer : observers) { + try { + observer.notify(message.getKey(), message); + } catch (Exception e) { + String msg = "Failed to notify observer for key {0} on message with id {1}"; //$NON-NLS-1$ + logger.error(MessageFormat.format(msg, message.getKey(), message.getId())); + } + } + } + + @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$ + + this.endpoint.send(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); + } + } + } + } + + /** + * Called when the message has been handled + * + * @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; + + } + synchronized (this.connectionObservers) { + List observers = this.connectionObservers.getList(message.getKey()); + for (ConnectionObserver observer : observers) { + observer.notify(message.getKey(), message); + } + } + } + + public String getUri() { + return this.endpoint == null ? "0.0.0.0:0" : this.endpoint.getRemoteUri(); //$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()); + + if (this.mode == ConnectionMode.SIMULATION) { + message.setState(State.DONE, State.DONE.name()); + done(message); + } else { + this.messageQueue.add(message); + } + } +} diff --git a/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java b/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java new file mode 100644 index 000000000..7aaf10a44 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.communication; + +/** + * @author Robert von Burg + */ +public interface CommunicationEndpoint { + + public void configure(CommunicationConnection connection, IoMessageVisitor converter); + + public void start(); + + public void stop(); + + public void reset(); + + public String getLocalUri(); + + public String getRemoteUri(); + + public void send(IoMessage message) throws Exception; +} diff --git a/src/main/java/ch/eitchnet/communication/ConnectionException.java b/src/main/java/ch/eitchnet/communication/ConnectionException.java new file mode 100644 index 000000000..a6e75de2b --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/ConnectionException.java @@ -0,0 +1,18 @@ +package ch.eitchnet.communication; + +/** + * Base Exception for exceptions thrown by the communication classes + * + * @author Robert von Burg + */ +public class ConnectionException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } + + public ConnectionException(String message) { + super(message); + } +} diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java new file mode 100644 index 000000000..a6fab0c79 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java @@ -0,0 +1,147 @@ +package ch.eitchnet.communication; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * Helper class to thrown connection messages + * + * @author Robert von Burg + */ +public class ConnectionMessages { + + private static Logger logger = Logger.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.getName(), 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.getName(), 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) { + String msg = "{0}: parameter ''{1}'' is not set, using default value ''{2}''"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, clazz.getName(), parameterName, defValue); + Map properties = new HashMap(); + logger.warn(MessageFormat.format(msg, properties)); + } + + /** + * Convenience method to throw an exception when the connection is not yet configured + * + * @param connection + * @param message + */ + public static void assertConfigured(CommunicationConnection connection, String message) { + if (connection.getState() == ConnectionState.CREATED) { + String msg = "{0} : Not yet configured: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connection.getId(), message); + throw new ConnectionException(msg); + } + } + + /** + * Convenience method to throw an exception when the connection is not connected + * + * @param connection + * @param message + * + * @return + */ + public static ConnectionException throwNotConnected(CommunicationConnection connection, String message) { + String msg = "{0} : Not connected: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connection.getId(), message); + ConnectionException e = new ConnectionException(msg); + return e; + } + + /** + * Convenience method to throw an exception when the connection is not connected + * + * @param connection + * @param message + * + * @return + */ + public static ConnectionException throwNotConnected(CommunicationConnection connection, IoMessage message) { + String msg = "{0} : Not connected, can not send message with id {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connection.getId(), message.getId()); + ConnectionException e = new ConnectionException(msg); + return e; + } + + public static void assertLegalMessageVisitor(Class endpoint, + Class expectedVisitor, IoMessageVisitor actualVisitor) { + if (!(expectedVisitor.isAssignableFrom(actualVisitor.getClass()))) { + String msg = "{0} requires {1} but has received illegal type {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, endpoint.getName(), expectedVisitor.getName(), actualVisitor.getClass() + .getName()); + throw new ConnectionException(msg); + } + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMode.java b/src/main/java/ch/eitchnet/communication/ConnectionMode.java new file mode 100644 index 000000000..e08234c86 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/ConnectionMode.java @@ -0,0 +1,40 @@ +package ch.eitchnet.communication; + +import java.io.IOException; + +/** + *

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

      + * The modes have the following semantics: + *
        + *
      • OFF - the connection can only have states {@link ConnectionState#CREATED} and {@link ConnectionState#INITIALIZED} + * . Trying to use the connection will throw an exception
      • + *
      • ON - the connection can be used normally
      • + *
      • SIMULATION - the same as ON, with the difference that the connection should silently drop any work
      • + *
      + * + * @author Robert von Burg + */ +public enum ConnectionMode { + + /** + * Denotes that the {@link CommunicationConnection} is off. This means it cannot accept messages, process messages + * or do any other kind of work + */ + OFF, + + /** + * 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, + + /** + * 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; +} diff --git a/src/main/java/ch/eitchnet/communication/ConnectionObserver.java b/src/main/java/ch/eitchnet/communication/ConnectionObserver.java new file mode 100644 index 000000000..a0b5adc5e --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/ConnectionObserver.java @@ -0,0 +1,24 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.communication; + +/** + * @author Robert von Burg + */ +public interface ConnectionObserver { + + public void notify(CommandKey key, IoMessage message); +} diff --git a/src/main/java/ch/eitchnet/communication/ConnectionState.java b/src/main/java/ch/eitchnet/communication/ConnectionState.java new file mode 100644 index 000000000..49cd31bca --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/ConnectionState.java @@ -0,0 +1,39 @@ +package ch.eitchnet.communication; + +/** + *

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

      + * The states have the following semantics: + *
        + *
      • CREATED - initial state
      • + *
      • INITIALIZED - the appropriate connection parameters are found
      • + *
      • CONNECTING - the connection is trying to build up a connection
      • + *
      • WAITING - the connection is waiting before retrying to connect
      • + *
      • CONNECTED - the connection has just been established
      • + *
      • IDLE - the connection is connected, but waiting for work
      • + *
      • WORKING - the connection is working
      • + *
      • BROKEN - the connection has lost the connection and is waiting before reconnecting, or another unknown failure + * occurred
      • + *
      • DISCONNECTED - the connection has been disconnected
      • + *
      + * + * @author Robert von Burg + */ +public enum ConnectionState { + + // initial + CREATED, + // configured and ready to connect + INITIALIZED, + // working + CONNECTING, + WAITING, + CONNECTED, + IDLE, + WORKING, + BROKEN, + // disconnected due to connection error or manual disconnect/stop + DISCONNECTED; +} diff --git a/src/main/java/ch/eitchnet/communication/IoMessage.java b/src/main/java/ch/eitchnet/communication/IoMessage.java new file mode 100644 index 000000000..f473b0df3 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/IoMessage.java @@ -0,0 +1,99 @@ +package ch.eitchnet.communication; + +import java.util.Date; + +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; + +public class IoMessage { + + private final String id; + private final CommandKey key; + private Date updated; + private State state; + private String stateMsg; + + public IoMessage(String id, CommandKey key) { + this.id = id; + this.key = key; + this.state = State.CREATED; + this.stateMsg = StringHelper.DASH; + this.updated = new Date(); + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + /** + * @return the key + */ + public CommandKey getKey() { + return this.key; + } + + /** + * @return the updated + */ + public Date getUpdated() { + return this.updated; + } + + /** + * @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("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(); + } + + public enum State { + CREATED, // new + PENDING, // outbound + ACCEPTED, // inbound + DONE, // completed + FAILED, // completed + FATAL; // not sendable + } +} diff --git a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java new file mode 100644 index 000000000..f1ae3ac68 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java @@ -0,0 +1,25 @@ +package ch.eitchnet.communication; + +import ch.eitchnet.communication.console.ConsoleMessageVisitor; + +/** + *

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

      + * + *

      + * Concrete implementations must be thread safe! + *

      + * + * @author Robert von Burg + */ +public abstract class IoMessageVisitor { + + protected CommunicationConnection connection; + + public void configure(CommunicationConnection connection) { + this.connection = connection; + } +} diff --git a/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java b/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java new file mode 100644 index 000000000..052f45b51 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java @@ -0,0 +1,22 @@ +package ch.eitchnet.communication; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + *

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

      + * + *

      + * Concrete implementations must be thread safe! + *

      + * + * @author Robert von Burg + */ +public abstract class StreamMessageVisitor extends IoMessageVisitor { + + public abstract void visit(OutputStream outputStream, IoMessage message) throws Exception; + + public abstract IoMessage visit(InputStream inputStream) throws Exception; +} diff --git a/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java b/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java new file mode 100644 index 000000000..a3f290386 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java @@ -0,0 +1,63 @@ +package ch.eitchnet.communication.console; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; + +/** + * @author Robert von Burg + */ +public class ConsoleEndpoint implements CommunicationEndpoint { + + private static final Logger logger = LoggerFactory.getLogger(ConsoleEndpoint.class); + private CommunicationConnection connection; + private ConsoleMessageVisitor messageVisitor; + + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + this.connection = connection; + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), ConsoleMessageVisitor.class, messageVisitor); + this.messageVisitor = (ConsoleMessageVisitor) messageVisitor; + } + + @Override + public void start() { + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + + @Override + public void stop() { + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); + } + + @Override + public void reset() { + this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); + } + + @Override + public String getLocalUri() { + return "console"; //$NON-NLS-1$ + } + + @Override + public String getRemoteUri() { + return "console"; //$NON-NLS-1$ + } + + @Override + public void send(IoMessage message) throws Exception { + this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); + try { + this.messageVisitor.visit(logger, message); + } finally { + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + } +} diff --git a/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java b/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java new file mode 100644 index 000000000..9991589b4 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java @@ -0,0 +1,11 @@ +package ch.eitchnet.communication.console; + +import org.slf4j.Logger; + +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; + +public abstract class ConsoleMessageVisitor extends IoMessageVisitor { + + public abstract void visit(Logger logger, IoMessage message) throws Exception; +} diff --git a/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java b/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java new file mode 100644 index 000000000..3f9d15d80 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2006 - 2011 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ + +package ch.eitchnet.communication.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionException; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; +import ch.eitchnet.communication.StreamMessageVisitor; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * An {@link CommunicationEndpoint} which writes and/or reads from a designated file + * + * @author Robert von Burg + */ +public class FileEndpoint implements CommunicationEndpoint, Runnable { + + public static final String ENDPOINT_MODE = "endpointMode"; //$NON-NLS-1$ + public static final String INBOUND_FILENAME = "inboundFilename"; //$NON-NLS-1$ + public static final String OUTBOUND_FILENAME = "outboundFilename"; //$NON-NLS-1$ + public static final long POLL_TIME = 1000l; + + private static final Logger logger = LoggerFactory.getLogger(FileEndpoint.class); + + private CommunicationConnection connection; + + private FileEndpointMode endpointMode; + private String inboundFilename; + private String outboundFilename; + private Thread thread; + private boolean run = false; + private StreamMessageVisitor messageVisitor; + + /** + * {@link FileEndpoint} needs the following parameters on the configuration to be initialized + *
        + *
      • outboundFilename: the file name where the {@link IoMessage} contents are written to. The value may contain + * {@link System#getProperty(String)} place holders which will be evaluated
      • + *
      + */ + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + this.connection = connection; + + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), StreamMessageVisitor.class, messageVisitor); + this.messageVisitor = (StreamMessageVisitor) messageVisitor; + + configure(); + } + + private void configure() { + Map parameters = this.connection.getParameters(); + + String endpointModeS = parameters.get(ENDPOINT_MODE); + if (StringHelper.isEmpty(endpointModeS)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); + } + try { + this.endpointMode = FileEndpointMode.valueOf(endpointModeS); + } catch (Exception e) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); + } + + if (this.endpointMode.isRead()) { + this.inboundFilename = parameters.get(INBOUND_FILENAME); + if (StringHelper.isEmpty(this.inboundFilename)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, INBOUND_FILENAME, + this.inboundFilename); + } + } + + if (this.endpointMode.isWrite()) { + this.outboundFilename = parameters.get(OUTBOUND_FILENAME); + if (StringHelper.isEmpty(this.outboundFilename)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, OUTBOUND_FILENAME, + this.outboundFilename); + } + } + } + + @Override + public String getLocalUri() { + return new File(this.inboundFilename).getAbsolutePath(); + } + + @Override + public String getRemoteUri() { + return new File(this.outboundFilename).getAbsolutePath(); + } + + @Override + public void start() { + if (this.endpointMode.isRead()) { + this.thread = new Thread(this, new File(this.inboundFilename).getName()); + this.run = true; + this.thread.start(); + } + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + + @Override + public void stop() { + stopThread(); + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); + } + + @Override + public void reset() { + stopThread(); + configure(); + this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); + } + + private void stopThread() { + this.run = false; + if (this.thread != null) { + try { + this.thread.interrupt(); + this.thread.join(2000l); + } catch (Exception e) { + logger.error(MessageFormat.format("Error while interrupting thread: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } + + this.thread = null; + } + } + + @Override + public void send(IoMessage message) throws Exception { + if (!this.endpointMode.isWrite()) { + String msg = "FileEnpoint mode is {0} and thus write is not allowed!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.endpointMode); + throw new ConnectionException(msg); + } + + this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); + + // open the stream + try (FileOutputStream outputStream = new FileOutputStream(this.outboundFilename, false)) { + + // write the message using the visitor + this.messageVisitor.visit(outputStream, message); + + } finally { + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + } + + @Override + public void 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.notify(message); + } + } + } +} diff --git a/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java b/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java new file mode 100644 index 000000000..798867071 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java @@ -0,0 +1,20 @@ +package ch.eitchnet.communication.file; + +public enum FileEndpointMode { + READ(true, false), WRITE(false, true), READ_WRITE(true, true); + private boolean read; + private boolean write; + + private FileEndpointMode(boolean read, boolean write) { + this.read = read; + this.write = write; + } + + public boolean isRead() { + return this.read; + } + + public boolean isWrite() { + return this.write; + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java new file mode 100644 index 000000000..c96a23b98 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -0,0 +1,510 @@ +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.apache.log4j.Logger; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionException; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessage.State; +import ch.eitchnet.communication.IoMessageVisitor; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

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

      + *

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

      + * + * @author Robert von Burg + */ +public class ClientSocketEndpoint implements CommunicationEndpoint { + + protected static final Logger logger = Logger.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; + private DataOutputStream outputStream; + private DataInputStream inputStream; + + private CommunicationConnection connection; + + private 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 + 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); + } + + 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}"; //$NON-NLS-1$ + logger.error(MessageFormat.format(msg, this.remoteInputAddressS, Integer.toString(this.remoteInputPort))); + logger.error(e, e); + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } + } + } + + /** + * closes the connection HARD by calling close() on the streams and socket. All Exceptions are caught to make sure + * that the connections are cleaned up + */ + protected void closeConnection() { + + this.connected = false; + this.connection.notifyStateChange(ConnectionState.BROKEN, null); + + if (this.outputStream != null) { + try { + this.outputStream.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing OutputStream: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.outputStream = null; + } + } + + if (this.inputStream != null) { + try { + this.inputStream.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing InputStream: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.inputStream = null; + } + } + + if (this.socket != null) { + try { + this.socket.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing OutputSocket: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.socket = null; + } + + String msg = "Socket closed for connection {0} at remote input address {1}:{2}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.connection.getId(), this.remoteInputAddressS, + Integer.toString(this.remoteInputPort))); + } + } + + /** + *

      + * Configures this {@link ClientSocketEndpoint} + *

      + * gets the parameter map from the connection and reads the following parameters from the map: + *
        + *
      • remoteInputAddress - the IP or Hostname of the remote server
      • + *
      • remoteInputPort - the port to which the socket should be established
      • + *
      • localOutputAddress - the IP or Hostname of the local server (if null, then the network layer will decide)
      • + *
      • localOutputPort - the local port from which the socket should go out of (if null, then the network layer will + * decide)
      • + *
      • retry - a configured retry wait time. Default is {@link SocketEndpointConstants#RETRY}
      • + *
      • timeout - the timeout after which an idle socket is deemed dead. Default is + * {@link SocketEndpointConstants#TIMEOUT}
      • + *
      • useTimeout - if true, then the timeout is activated, otherwise it is. default is + * {@link SocketEndpointConstants#USE_TIMEOUT}
      • + *
      • clearOnConnect - if true, then the after a successful connect the input is cleared by discarding all + * available bytes. This can be useful in cases where the channel is clogged with stale data. default is + * {@link SocketEndpointConstants#CLEAR_ON_CONNECT}
      • + *
      + * + * @see CommunicationEndpoint#configure(CommunicationConnection, IoMessageVisitor) + */ + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + if (this.connection != null && connection.getState().compareTo(ConnectionState.INITIALIZED) > 0) { + String msg = "{0}:{1} already configured."; //$NON-NLS-1$ + logger.warn(MessageFormat.format(msg, this.getClass().getSimpleName(), connection.getId())); + return; + } + + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), SocketMessageVisitor.class, messageVisitor); + this.messageVisitor = (SocketMessageVisitor) messageVisitor; + this.connection = connection; + configure(); + } + + private void configure() { + Map parameters = this.connection.getParameters(); + + this.remoteInputAddressS = parameters.get(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_ADDRESS); + String remoteInputPortS = parameters.get(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_PORT); + this.localOutputAddressS = parameters.get(SocketEndpointConstants.PARAMETER_LOCAL_OUTPUT_ADDRESS); + String localOutputPortS = parameters.get(SocketEndpointConstants.PARAMETER_LOCAL_OUTPUT_PORT); + + // parse remote input Address to InetAddress object + try { + this.remoteInputAddress = InetAddress.getByName(this.remoteInputAddressS); + } catch (UnknownHostException e) { + throw ConnectionMessages.throwInvalidParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_REMOTE_INPUT_ADDRESS, this.remoteInputAddressS); + } + + // parse remote input address port to integer + try { + this.remoteInputPort = Integer.parseInt(remoteInputPortS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ClientSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_REMOTE_INPUT_PORT, remoteInputPortS); + } + + // if local output address is not set, then we will use the localhost InetAddress + if (this.localOutputAddressS == null || this.localOutputAddressS.length() == 0) { + logger.warn("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 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 { + logger.error(e, e); + message.setState(State.FATAL, e.getLocalizedMessage()); + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } + } finally { + if (this.closeAfterSend) { + closeConnection(); + } + } + } + } +} diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java new file mode 100644 index 000000000..17bbe3cc9 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -0,0 +1,571 @@ +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.apache.log4j.Logger; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionException; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

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

      + *

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

      + * + * @see ServerSocketEndpoint#configure(CommunicationConnection, IoMessageVisitor) for details on configuring the end + * point + * + * @author Robert von Burg + */ +public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { + + protected static final Logger logger = Logger.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; + private DataOutputStream outputStream; + private DataInputStream inputStream; + + private CommunicationConnection connection; + + private 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 + 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())); + //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}"; //$NON-NLS-1$ + logger.error(MessageFormat.format(msg, this.connection.getId())); + logger.error(e, e); + this.connected = false; + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } + } + } + } + + /** + * closes the connection HARD by calling close() on the streams and socket. All Exceptions are caught to make sure + * that the connections are cleaned up + */ + protected void closeConnection() { + + this.connected = false; + this.connection.notifyStateChange(ConnectionState.BROKEN, null); + + if (this.outputStream != null) { + try { + this.outputStream.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing OutputStream: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.outputStream = null; + } + } + + if (this.inputStream != null) { + try { + this.inputStream.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing InputStream: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.inputStream = null; + } + } + + if (this.socket != null) { + try { + this.socket.close(); + } catch (IOException e) { + logger.error(MessageFormat.format("Error closing InputSocket: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } finally { + this.socket = null; + } + + String msg = "Socket closed for inbound connection {0} at local input address {1}:{2}"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, this.connection.getId(), this.localInputAddressS, + Integer.toString(this.localInputPort))); + } + } + + /** + *

      + * Configures this {@link ServerSocketEndpoint} + *

      + * gets the parameter map from the connection and reads the following parameters from the map: + *
        + *
      • localInputAddress - the local IP or Hostname to bind to for incoming connections
      • + *
      • localInputPort - the local port on which to listen for incoming connections
      • + *
      • remoteOutputAddress - the IP or Hostname of the remote client. If this value is not null, then it will be + * verified that the connecting client is connecting from this address
      • + *
      • remoteOutputPort - the port from which the remote client must connect. If this value is not null, then it + * will be verified that the connecting client is connecting from this port
      • + *
      • retry - a configured retry wait time. Default is {@link SocketEndpointConstants#RETRY}
      • + *
      • timeout - the timeout after which an idle socket is deemed dead. Default is + * {@link SocketEndpointConstants#TIMEOUT}
      • + *
      • useTimeout - if true, then the timeout is activated. default is {@link SocketEndpointConstants#USE_TIMEOUT}
      • + *
      • clearOnConnect - if true, then the after a successful connect the input is cleared by discarding all + * available bytes. This can be useful in cases where the channel is clogged with stale data. default is + * {@link SocketEndpointConstants#CLEAR_ON_CONNECT}
      • + *
      + * + * @see CommunicationEndpoint#configure(CommunicationConnection, IoMessageVisitor) + */ + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + if (this.connection != null && connection.getState().compareTo(ConnectionState.INITIALIZED) > 0) { + logger.warn(MessageFormat.format("Inbound connection {0} already configured.", connection.getId())); //$NON-NLS-1$ + return; + } + + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), SocketMessageVisitor.class, messageVisitor); + this.messageVisitor = (SocketMessageVisitor) messageVisitor; + this.connection = connection; + configure(); + } + + private void configure() { + Map parameters = this.connection.getParameters(); + + this.localInputAddressS = parameters.get(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_ADDRESS); + String localInputPortS = parameters.get(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT); + this.remoteOutputAddressS = parameters.get(SocketEndpointConstants.PARAMETER_REMOTE_OUTPUT_ADDRESS); + String remoteOutputPortS = parameters.get(SocketEndpointConstants.PARAMETER_REMOTE_OUTPUT_PORT); + + // parse local Address to InetAddress object + try { + this.localInputAddress = InetAddress.getByName(this.localInputAddressS); + } catch (UnknownHostException e) { + throw ConnectionMessages.throwInvalidParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_LOCAL_INPUT_ADDRESS, this.localInputAddressS); + } + + // parse local address port to integer + try { + this.localInputPort = Integer.parseInt(localInputPortS); + } catch (NumberFormatException e) { + throw ConnectionMessages.throwInvalidParameter(ServerSocketEndpoint.class, + SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT, localInputPortS); + } + + // if remote address is not set, then we will use the localhost InetAddress + if (this.remoteOutputAddressS == null || this.remoteOutputAddressS.length() == 0) { + logger.warn("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.fatal("Fatal BindException occurred! Port is already in use, or address is illegal!"); //$NON-NLS-1$ + logger.fatal(e, 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.notify(message); + } + } + + } catch (Exception e) { + if (e instanceof InterruptedException) { + logger.error("Interrupted!"); //$NON-NLS-1$ + } else { + logger.error(e, 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 send(IoMessage message) throws Exception { + String msg = "The Server Socket can not send messages, use the {0} implementation instead!"; //$NON-NLS-1$ + throw new UnsupportedOperationException(MessageFormat.format(msg, ClientSocketEndpoint.class.getName())); + } +} diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java b/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java new file mode 100644 index 000000000..ea967644b --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java @@ -0,0 +1,67 @@ +package ch.eitchnet.communication.tcpip; + +/** + * Constants used in the communication classes + * + * @author Robert von Burg + */ +public class SocketEndpointConstants { + + public static final String PARAMETER_USE_TIMEOUT = "useTimeout"; //$NON-NLS-1$ + public static final String PARAMETER_TIMEOUT = "timeout"; //$NON-NLS-1$ + public static final String PARAMETER_RETRY = "retry"; //$NON-NLS-1$ + public static final String PARAMETER_CLEAR_ON_CONNECT = "clearOnConnect"; //$NON-NLS-1$ + public static final String PARAMETER_CONNECT_ON_START = "connectOnStart"; //$NON-NLS-1$ + public static final String PARAMETER_CLOSE_AFTER_SEND = "closeAfterSend"; //$NON-NLS-1$ + + public static final String PARAMETER_REMOTE_OUTPUT_PORT = "remoteOutputPort"; //$NON-NLS-1$ + public static final String PARAMETER_REMOTE_OUTPUT_ADDRESS = "remoteOutputAddress"; //$NON-NLS-1$ + public static final String PARAMETER_LOCAL_INPUT_PORT = "localInputPort"; //$NON-NLS-1$ + public static final String PARAMETER_LOCAL_INPUT_ADDRESS = "localInputAddress"; //$NON-NLS-1$ + + public static final String PARAMETER_LOCAL_OUTPUT_ADDRESS = "localOutputAddress"; //$NON-NLS-1$ + public static final String PARAMETER_LOCAL_OUTPUT_PORT = "localOutputPort"; //$NON-NLS-1$ + public static final String PARAMETER_REMOTE_INPUT_ADDRESS = "remoteInputAddress"; //$NON-NLS-1$ + public static final String PARAMETER_REMOTE_INPUT_PORT = "remoteInputPort"; //$NON-NLS-1$ + + /** + * Time to wait in milliseconds before reestablishing a connection. Default is 60000ms + */ + public static final long RETRY = 60000l; + + /** + * The time after which a connection is deemed dead. Value is 60000ms + */ + public static final int TIMEOUT = 60000; + + /** + * Default is to use a timeout on socket connections, thus this value is true + */ + public static final boolean USE_TIMEOUT = true; + + /** + * Default is to not clear the input socket on connect, thus this value is false + */ + public static final boolean CLEAR_ON_CONNECT = false; + + /** + * Default is to connect on start of the connection + */ + public static final boolean CONNECT_ON_START = true; + + /** + * Default is to not close after sending + */ + public static final boolean CLOSE_AFTER_SEND = false; + + /** + * Default is to disconnect after a null message is received when reading from a TCP socket, thus this value is true + */ + public static final boolean DISCONNECT_ON_NULL_MSG = true; + + /** + * If {@link #DISCONNECT_ON_NULL_MSG} is activated, then this is the default time used to wait before reading again, + * which is 10000ms + */ + public static final long WAIT_TIME_ON_NULL_MSG = 10000l; +} diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java b/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java new file mode 100644 index 000000000..056bb04aa --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java @@ -0,0 +1,15 @@ +package ch.eitchnet.communication.tcpip; + +import java.io.DataInputStream; +import java.io.DataOutputStream; + +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; + +public abstract class SocketMessageVisitor extends IoMessageVisitor { + + public abstract IoMessage visit(DataInputStream inputStream, DataOutputStream outputStream) throws Exception; + + public abstract void visit(DataInputStream inputStream, DataOutputStream outputStream, IoMessage message) + throws Exception; +} diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java b/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java new file mode 100644 index 000000000..dc64e2c79 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java @@ -0,0 +1,120 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.collections; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * @author Robert von Burg + */ +public class MapOfLists { + + private Map> mapOfLists; + + public MapOfLists() { + this.mapOfLists = new HashMap<>(); + } + + public Set keySet() { + return this.mapOfLists.keySet(); + } + + public List getList(T t) { + return this.mapOfLists.get(t); + } + + public boolean addElement(T t, U u) { + List list = this.mapOfLists.get(t); + if (list == null) { + list = new ArrayList<>(); + this.mapOfLists.put(t, list); + } + return list.add(u); + } + + public boolean addList(T t, List u) { + List list = this.mapOfLists.get(t); + if (list == null) { + list = new ArrayList<>(); + this.mapOfLists.put(t, list); + } + return list.addAll(u); + } + + public boolean removeElement(T t, U u) { + List list = this.mapOfLists.get(t); + if (list == null) { + return false; + } + boolean removed = list.remove(u); + if (list.isEmpty()) { + this.mapOfLists.remove(t); + } + + return removed; + } + + public List removeList(T t) { + return this.mapOfLists.remove(t); + } + + public void clear() { + Set>> entrySet = this.mapOfLists.entrySet(); + Iterator>> iter = entrySet.iterator(); + while (iter.hasNext()) { + iter.next().getValue().clear(); + iter.remove(); + } + } + + public boolean containsList(T t) { + return this.mapOfLists.containsKey(t); + } + + public boolean containsElement(T t, U u) { + List list = this.mapOfLists.get(t); + if (list == null) + return false; + return list.contains(u); + } + + public int sizeKeys() { + return this.mapOfLists.size(); + } + + public int size() { + int size = 0; + Set>> entrySet = this.mapOfLists.entrySet(); + Iterator>> iter = entrySet.iterator(); + while (iter.hasNext()) { + size += iter.next().getValue().size(); + } + return size; + } + + public int size(T t) { + List list = this.mapOfLists.get(t); + if (list.size() == 0) + return 0; + return list.size(); + } +} diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java index 887999eaf..4ea4337fe 100644 --- a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java +++ b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java @@ -126,6 +126,10 @@ public class MapOfMaps { return map.containsKey(u); } + public int sizeKeys() { + return this.mapOfMaps.size(); + } + public int size() { int size = 0; Set>> entrySet = this.mapOfMaps.entrySet(); @@ -137,6 +141,9 @@ public class MapOfMaps { } public int size(T t) { - return this.mapOfMaps.get(t).size(); + Map map = this.mapOfMaps.get(t); + if (map == null) + return 0; + return map.size(); } } diff --git a/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java b/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java new file mode 100644 index 000000000..2d1f65ee7 --- /dev/null +++ b/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java @@ -0,0 +1,40 @@ +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; + +public class AbstractEndpointTest { + + static final Logger logger = LoggerFactory.getLogger(FileEndpointTest.class); + + public static TestIoMessage createTestMessage(String key1, String key2) { + return createTestMessage(CommandKey.key(key1, key2)); + } + + @SuppressWarnings("nls") + public static TestIoMessage createTestMessage(CommandKey key) { + TestIoMessage msg = new TestIoMessage(UUID.randomUUID().toString(), key); + List lines = new ArrayList<>(); + lines.add("bla"); + lines.add("foo"); + lines.add("bar"); + lines.add("bla"); + msg.setContents(lines); + return msg; + } + + protected void waitForMessage(TestConnectionObserver observer) throws InterruptedException { + long start = System.currentTimeMillis(); + while (observer.getMessage() == null) { + if (System.currentTimeMillis() - start > 2000) + fail("Connection didn't send message in 2s!"); //$NON-NLS-1$ + Thread.sleep(50); + } + } +} diff --git a/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java b/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java new file mode 100644 index 000000000..54d4f960e --- /dev/null +++ b/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java @@ -0,0 +1,61 @@ +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; + +public class ConsoleEndpointTest extends AbstractEndpointTest { + + private static final String CONNECTION_ID = "Console"; //$NON-NLS-1$ + private CommunicationConnection connection; + + @Before + public void before() { + + Map parameters = new HashMap<>(); + CommunicationEndpoint endpoint = new ConsoleEndpoint(); + ConsoleMessageVisitor messageVisitor = new ConsoleMessageVisitorExtension(); + this.connection = new CommunicationConnection(CONNECTION_ID, ConnectionMode.ON, parameters, endpoint, + messageVisitor); + this.connection.configure(); + } + + @Test + public void testConsoleEndpoint() throws InterruptedException { + + this.connection.start(); + + CommandKey key = CommandKey.key(CONNECTION_ID, "logger"); //$NON-NLS-1$ + TestIoMessage msg = createTestMessage(key); + + TestConnectionObserver observer = new TestConnectionObserver(); + this.connection.addConnectionObserver(key, observer); + this.connection.send(msg); + waitForMessage(observer); + + assertEquals(msg.getKey(), observer.getMessage().getKey()); + + } + + private final class ConsoleMessageVisitorExtension extends ConsoleMessageVisitor { + public ConsoleMessageVisitorExtension() { + // no-op + } + + @Override + public void visit(Logger logger, IoMessage message) throws Exception { + TestIoMessage msg = (TestIoMessage) message; + for (String line : msg.getContents()) { + logger.info(line); + } + } + } +} diff --git a/src/test/java/ch/eitchnet/communication/FileEndpointTest.java b/src/test/java/ch/eitchnet/communication/FileEndpointTest.java new file mode 100644 index 000000000..454433a76 --- /dev/null +++ b/src/test/java/ch/eitchnet/communication/FileEndpointTest.java @@ -0,0 +1,120 @@ +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; + +public class FileEndpointTest extends AbstractEndpointTest { + + public static final String INBOUND_FILENAME = "target/test_in.txt"; //$NON-NLS-1$ + public static final String OUTBOUND_FILENAME = "target/test_out.txt"; //$NON-NLS-1$ + public static final String CONNECTION_ID = "FileTestEndpoint"; //$NON-NLS-1$ + + private CommunicationConnection connection; + + @Before + public void before() { + + new File(OUTBOUND_FILENAME).delete(); + new File(INBOUND_FILENAME).delete(); + + Map parameters = new HashMap<>(); + parameters.put(FileEndpoint.ENDPOINT_MODE, FileEndpointMode.READ_WRITE.name()); + parameters.put(FileEndpoint.INBOUND_FILENAME, INBOUND_FILENAME); + parameters.put(FileEndpoint.OUTBOUND_FILENAME, OUTBOUND_FILENAME); + + ConnectionMode mode = ConnectionMode.ON; + CommunicationEndpoint endpoint = new FileEndpoint(); + StreamMessageVisitor messageVisitor = new StreamMessageVisitorExtension(); + + this.connection = new CommunicationConnection(CONNECTION_ID, mode, parameters, endpoint, messageVisitor); + this.connection.configure(); + } + + @After + public void after() { + if (this.connection != null) + this.connection.stop(); + } + + @Test + public void testFileEndpoint() throws InterruptedException { + + String inboundFilename = new File(INBOUND_FILENAME).getName(); + String outboundFilename = new File(OUTBOUND_FILENAME).getName(); + + // send a message + this.connection.start(); + TestConnectionObserver outboundObserver = new TestConnectionObserver(); + TestIoMessage message = createTestMessage(outboundFilename, FileEndpointMode.WRITE.name()); + this.connection.addConnectionObserver(message.getKey(), outboundObserver); + this.connection.send(message); + + // wait till the message has been sent + waitForMessage(outboundObserver); + this.connection.stop(); + assertEquals(message.getKey(), outboundObserver.getMessage().getKey()); + + // now test reading a file + this.connection.start(); + CommandKey inboundKey = CommandKey.key(inboundFilename, FileEndpointMode.READ.name()); + TestConnectionObserver inboundObserver = new TestConnectionObserver(); + this.connection.addConnectionObserver(inboundKey, inboundObserver); + FileHelper.writeStringToFile("Hello\nWorld!", new File(INBOUND_FILENAME)); //$NON-NLS-1$ + + // wait for thread to pick up the file + waitForMessage(inboundObserver); + assertEquals(inboundKey, inboundObserver.getMessage().getKey()); + } + + public static final class StreamMessageVisitorExtension extends StreamMessageVisitor { + private String inboundFilename; + + @Override + public void configure(CommunicationConnection connection) { + super.configure(connection); + Map parameters = connection.getParameters(); + String filePath = parameters.get(FileEndpoint.INBOUND_FILENAME); + this.inboundFilename = new File(filePath).getName(); + } + + @Override + public IoMessage visit(InputStream inputStream) throws Exception { + + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + List lines = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + return new TestIoMessage(UUID.randomUUID().toString(), CommandKey.key(this.inboundFilename, + FileEndpointMode.READ.name()), lines); + } + + @Override + public void visit(OutputStream outputStream, IoMessage message) throws Exception { + TestIoMessage msg = (TestIoMessage) message; + for (String line : msg.getContents()) { + outputStream.write(line.getBytes()); + outputStream.write('\n'); + } + } + } +} diff --git a/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java b/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java new file mode 100644 index 000000000..80227ac0a --- /dev/null +++ b/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java @@ -0,0 +1,134 @@ +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; + +public class SocketEndpointTest extends AbstractEndpointTest { + + private static final String PORT = "45678"; //$NON-NLS-1$ + private static final String HOST = "localhost"; //$NON-NLS-1$ + private static final String CLIENT_CONNECTION_ID = "ClientSocket"; //$NON-NLS-1$ + private static final String SERVER_CONNECTION_ID = "ServerSocket"; //$NON-NLS-1$ + private CommunicationConnection clientConnection; + private CommunicationConnection serverConnection; + + @Before + public void before() { + + { + Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_ADDRESS, HOST); + parameters.put(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_PORT, PORT); + + // we close after send, so that the server can read whole lines, as that is what we are sending + parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.TRUE.toString()); + + CommunicationEndpoint endpoint = new ClientSocketEndpoint(); + SocketMessageVisitor messageVisitor = new SocketMessageVisitorExtension(); + this.clientConnection = new CommunicationConnection(CLIENT_CONNECTION_ID, ConnectionMode.ON, parameters, + endpoint, messageVisitor); + this.clientConnection.configure(); + } + + { + Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_ADDRESS, HOST); + parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT, PORT); + + CommunicationEndpoint endpoint = new ServerSocketEndpoint(); + SocketMessageVisitor messageVisitor = new SocketMessageVisitorExtension(); + 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); + 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() { + // do nothing + } + + @Override + public void visit(DataInputStream inputStream, DataOutputStream outputStream, IoMessage message) + throws Exception { + TestIoMessage msg = (TestIoMessage) message; + logger.info(MessageFormat + .format("Writing {0} lines for message {1}", msg.getContents().size(), msg.getId())); //$NON-NLS-1$ + for (String line : msg.getContents()) { + outputStream.writeBytes(line); + outputStream.write('\n'); + } + outputStream.flush(); + } + + @Override + public IoMessage visit(DataInputStream inputStream, DataOutputStream outputStream) throws Exception { + + List lines = new ArrayList<>(); + + // since we are reading whole lines, we must close the stream when we read null i.e. EOF + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + logger.info("Reading from stream..."); //$NON-NLS-1$ + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } + logger.info(MessageFormat.format("Read {0} lines from stream.", lines.size())); //$NON-NLS-1$ + + return new TestIoMessage(UUID.randomUUID().toString(), CommandKey.key(SERVER_CONNECTION_ID, "lines"), lines); //$NON-NLS-1$ + } + } +} diff --git a/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java b/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java new file mode 100644 index 000000000..a2882b7cd --- /dev/null +++ b/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java @@ -0,0 +1,23 @@ +package ch.eitchnet.communication; + +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestConnectionObserver implements ConnectionObserver { + + private static final Logger logger = LoggerFactory.getLogger(FileEndpointTest.class); + + private IoMessage message; + + public IoMessage getMessage() { + return this.message; + } + + @Override + public void notify(CommandKey key, IoMessage message) { + this.message = message; + logger.info(MessageFormat.format("Received message with key {0} and message {1}", key, message)); //$NON-NLS-1$ + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/communication/TestIoMessage.java b/src/test/java/ch/eitchnet/communication/TestIoMessage.java new file mode 100644 index 000000000..cdd3d139b --- /dev/null +++ b/src/test/java/ch/eitchnet/communication/TestIoMessage.java @@ -0,0 +1,25 @@ +package ch.eitchnet.communication; + +import java.util.List; + +public class TestIoMessage extends IoMessage { + + private List contents; + + public TestIoMessage(String id, CommandKey key) { + super(id, key); + } + + public TestIoMessage(String id, CommandKey key, List contents) { + super(id, key); + this.contents = contents; + } + + public List getContents() { + return this.contents; + } + + public void setContents(List contents) { + this.contents = contents; + } +} From c0655417dad6307eaec4d0a2674a3a94be9d2165 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Aug 2014 14:21:11 +0200 Subject: [PATCH 287/457] [New] added MapOfMaps.getAllElements() methods --- .../eitchnet/utils/collections/MapOfMaps.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java index 4ea4337fe..7e829ee0b 100644 --- a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java +++ b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java @@ -15,8 +15,10 @@ */ 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; @@ -80,6 +82,23 @@ public class MapOfMaps { return map.put(u, v); } + public List getAllElements() { + List all = new ArrayList<>(); + for (Map u : this.mapOfMaps.values()) { + all.addAll(u.values()); + } + return all; + } + + public List getAllElements(T t) { + List all = new ArrayList<>(); + Map map = this.mapOfMaps.get(t); + if (map != null) { + all.addAll(map.values()); + } + return all; + } + public void addMap(T t, Map u) { Map map = this.mapOfMaps.get(t); if (map == null) { From 22ddf8db7ec0bc45bb94af7410041f733719f1a6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Aug 2014 14:27:38 +0200 Subject: [PATCH 288/457] [Devel] added communication package [Devel] added communication package --- .../ch/eitchnet/communication/CommandKey.java | 2 +- .../CommunicationConnection.java | 71 ++++++---- .../communication/CommunicationEndpoint.java | 2 +- .../communication/ConnectionException.java | 15 +++ .../communication/ConnectionMessages.java | 15 +++ .../communication/ConnectionMode.java | 15 +++ .../communication/ConnectionObserver.java | 2 +- .../communication/ConnectionState.java | 15 +++ .../ch/eitchnet/communication/IoMessage.java | 60 ++++++++- .../communication/IoMessageArchive.java | 41 ++++++ .../communication/IoMessageVisitor.java | 15 +++ .../communication/SimpleMessageArchive.java | 121 ++++++++++++++++++ .../communication/StreamMessageVisitor.java | 15 +++ .../console/ConsoleEndpoint.java | 15 +++ .../console/ConsoleMessageVisitor.java | 15 +++ .../communication/file/FileEndpoint.java | 16 ++- .../communication/file/FileEndpointMode.java | 15 +++ .../tcpip/ClientSocketEndpoint.java | 15 +++ .../tcpip/ServerSocketEndpoint.java | 15 +++ .../tcpip/SocketEndpointConstants.java | 15 +++ .../tcpip/SocketMessageVisitor.java | 15 +++ .../utils/exceptions/XmlException.java | 1 - .../eitchnet/utils/helper/ArraysHelper.java | 1 - .../ch/eitchnet/utils/helper/ByteHelper.java | 1 - .../ch/eitchnet/utils/helper/MathHelper.java | 3 +- .../utils/io/FileProgressListener.java | 17 ++- .../utils/io/FileStreamProgressWatcher.java | 15 +++ .../utils/io/LoggingFileProgressListener.java | 15 +++ .../utils/io/ProgressableFileInputStream.java | 15 +++ .../ch/eitchnet/utils/iso8601/DateFormat.java | 4 +- .../utils/iso8601/DurationFormat.java | 4 +- .../eitchnet/utils/iso8601/FormatFactory.java | 12 +- .../ch/eitchnet/utils/iso8601/ISO8601.java | 2 +- .../utils/iso8601/ISO8601Duration.java | 4 +- .../utils/iso8601/ISO8601FormatFactory.java | 2 +- .../utils/iso8601/ISO8601Worktime.java | 4 +- .../utils/iso8601/WorktimeFormat.java | 2 +- .../communication/AbstractEndpointTest.java | 38 +++++- .../communication/ConsoleEndpointTest.java | 20 ++- .../communication/FileEndpointTest.java | 22 +++- .../SimpleMessageArchiveTest.java | 70 ++++++++++ .../communication/SocketEndpointTest.java | 23 +++- .../communication/TestConnectionObserver.java | 18 +++ .../eitchnet/communication/TestIoMessage.java | 26 +++- .../java/ch/eitchnet/utils/dbc/DBCTest.java | 17 ++- 45 files changed, 774 insertions(+), 72 deletions(-) create mode 100644 src/main/java/ch/eitchnet/communication/IoMessageArchive.java create mode 100644 src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java create mode 100644 src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java diff --git a/src/main/java/ch/eitchnet/communication/CommandKey.java b/src/main/java/ch/eitchnet/communication/CommandKey.java index b65ee5aa6..f34389bf0 100644 --- a/src/main/java/ch/eitchnet/communication/CommandKey.java +++ b/src/main/java/ch/eitchnet/communication/CommandKey.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 Robert von Burg + * Copyright 2014 Robert von Burg * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java index 49faf5d44..803dffc58 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -1,23 +1,17 @@ /* - * 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 - * . + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package ch.eitchnet.communication; @@ -33,6 +27,7 @@ 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; /** @@ -55,15 +50,24 @@ public class CommunicationConnection implements Runnable { private MapOfLists connectionObservers; private CommunicationEndpoint endpoint; - private IoMessageVisitor converter; + private IoMessageVisitor messageVisitor; + + private IoMessageArchive archive; public CommunicationConnection(String id, ConnectionMode mode, Map parameters, - CommunicationEndpoint endpoint, IoMessageVisitor converter) { + 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.converter = converter; + this.messageVisitor = messageVisitor; this.state = ConnectionState.CREATED; this.stateMsg = this.state.toString(); @@ -71,6 +75,14 @@ public class CommunicationConnection implements Runnable { this.connectionObservers = new MapOfLists<>(); } + public void setArchive(IoMessageArchive archive) { + this.archive = archive; + } + + public IoMessageArchive getArchive() { + return this.archive; + } + public String getId() { return this.id; } @@ -132,8 +144,8 @@ public class CommunicationConnection implements Runnable { * Configure the underlying {@link CommunicationEndpoint} and {@link IoMessageVisitor} */ public void configure() { - this.converter.configure(this); - this.endpoint.configure(this, this.converter); + this.messageVisitor.configure(this); + this.endpoint.configure(this, this.messageVisitor); this.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.name()); } @@ -152,7 +164,7 @@ public class CommunicationConnection implements Runnable { if (this.queueThread != null) { logger.warn(MessageFormat.format("{0}: Already connected!", this.id)); //$NON-NLS-1$ } else { - logger.info(MessageFormat.format("Starting Integration connection {0}...", this.id)); //$NON-NLS-1$ + logger.info(MessageFormat.format("Starting Connection {0}...", this.id)); //$NON-NLS-1$ this.run = true; this.queueThread = new Thread(this, MessageFormat.format("{0}_OUT", this.id)); //$NON-NLS-1$ this.queueThread.start(); @@ -207,7 +219,7 @@ public class CommunicationConnection implements Runnable { } /** - * Called by the underlying entpoint when a new message has been received and parsed + * Called by the underlying endpoint when a new message has been received and parsed * * @param message */ @@ -237,6 +249,9 @@ public class CommunicationConnection implements Runnable { logger.error(MessageFormat.format(msg, message.getKey(), message.getId())); } } + + if (this.archive != null) + this.archive.archive(message); } @Override @@ -278,6 +293,10 @@ public class CommunicationConnection implements Runnable { message.setState(State.FATAL, e.getLocalizedMessage()); done(message); } + } finally { + if (message != null && this.archive != null) { + this.archive.archive(message); + } } } } diff --git a/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java b/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java index 7aaf10a44..ef0a75813 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 Robert von Burg + * Copyright 2014 Robert von Burg * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/ch/eitchnet/communication/ConnectionException.java b/src/main/java/ch/eitchnet/communication/ConnectionException.java index a6e75de2b..465477567 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionException.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionException.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; /** diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java index a6fab0c79..d3f4940b4 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import java.text.MessageFormat; diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMode.java b/src/main/java/ch/eitchnet/communication/ConnectionMode.java index e08234c86..fbc44a768 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionMode.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionMode.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import java.io.IOException; diff --git a/src/main/java/ch/eitchnet/communication/ConnectionObserver.java b/src/main/java/ch/eitchnet/communication/ConnectionObserver.java index a0b5adc5e..8445ca357 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionObserver.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionObserver.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 Robert von Burg + * Copyright 2014 Robert von Burg * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/ch/eitchnet/communication/ConnectionState.java b/src/main/java/ch/eitchnet/communication/ConnectionState.java index 49cd31bca..13724331e 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionState.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionState.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; /** diff --git a/src/main/java/ch/eitchnet/communication/IoMessage.java b/src/main/java/ch/eitchnet/communication/IoMessage.java index f473b0df3..554a8d144 100644 --- a/src/main/java/ch/eitchnet/communication/IoMessage.java +++ b/src/main/java/ch/eitchnet/communication/IoMessage.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import java.util.Date; @@ -9,13 +24,15 @@ public class IoMessage { private final String id; private final CommandKey key; + private final String connectionId; private Date updated; private State state; private String stateMsg; - public IoMessage(String id, CommandKey key) { + 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(); @@ -35,6 +52,13 @@ public class IoMessage { return this.key; } + /** + * @return the connectionId + */ + public String getConnectionId() { + return this.connectionId; + } + /** * @return the updated */ @@ -42,6 +66,15 @@ public class IoMessage { return this.updated; } + /** + * Used for testing purposes only! + * + * @param date + */ + void setUpdated(Date date) { + this.updated = date; + } + /** * @return the state */ @@ -88,6 +121,31 @@ public class IoMessage { 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 diff --git a/src/main/java/ch/eitchnet/communication/IoMessageArchive.java b/src/main/java/ch/eitchnet/communication/IoMessageArchive.java new file mode 100644 index 000000000..ee0af8077 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/IoMessageArchive.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.communication; + +import java.util.List; + +public interface IoMessageArchive { + + public int getMaxSize(); + + public void setMaxSize(int maxSize); + + public int getTrimSize(); + + public void setTrimSize(int trimSize); + + public int size(); + + public List getAll(); + + public List getBy(String connectionId); + + public List getBy(String connectionId, CommandKey key); + + public void clearArchive(); + + public void archive(IoMessage message); +} diff --git a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java index f1ae3ac68..c7505ce88 100644 --- a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import ch.eitchnet.communication.console.ConsoleMessageVisitor; diff --git a/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java b/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java new file mode 100644 index 000000000..27414f3db --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java @@ -0,0 +1,121 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.communication; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; + +public class SimpleMessageArchive implements IoMessageArchive { + + private int maxSize; + private int trimSize; + private TreeSet messageArchive; + + public SimpleMessageArchive() { + this(1000, 100); + } + + public SimpleMessageArchive(int maxSize, int trimSize) { + this.maxSize = maxSize; + this.trimSize = trimSize; + + this.messageArchive = new TreeSet<>(new Comparator() { + @Override + public int compare(IoMessage o1, IoMessage o2) { + return o1.getUpdated().compareTo(o2.getUpdated()); + } + }); + } + + @Override + public synchronized int getMaxSize() { + return this.maxSize; + } + + @Override + public synchronized void setMaxSize(int maxSize) { + this.maxSize = maxSize; + trim(); + } + + @Override + public synchronized int getTrimSize() { + return this.trimSize; + } + + @Override + public synchronized void setTrimSize(int trimSize) { + this.trimSize = trimSize; + } + + @Override + public synchronized int size() { + return this.messageArchive.size(); + } + + @Override + public synchronized List getAll() { + List all = new ArrayList<>(this.messageArchive); + return all; + } + + @Override + public synchronized List getBy(String connectionId) { + List all = new ArrayList(); + for (IoMessage msg : this.messageArchive) { + if (msg.getConnectionId().equals(connectionId)) + all.add(msg); + } + return all; + } + + @Override + public synchronized List getBy(String connectionId, CommandKey key) { + List all = new ArrayList(); + for (IoMessage msg : this.messageArchive) { + if (msg.getConnectionId().equals(connectionId) && msg.getKey().equals(key)) + all.add(msg); + } + return all; + } + + @Override + public synchronized void clearArchive() { + this.messageArchive.clear(); + } + + @Override + public synchronized void archive(IoMessage message) { + this.messageArchive.add(message); + trim(); + } + + protected void trim() { + if (this.messageArchive.size() <= this.maxSize) + return; + + Iterator iter = this.messageArchive.iterator(); + for (int i = 0; i <= this.trimSize; i++) { + if (iter.hasNext()) { + iter.next(); + iter.remove(); + } + } + } +} diff --git a/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java b/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java index 052f45b51..894ebf883 100644 --- a/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import java.io.InputStream; diff --git a/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java b/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java index a3f290386..11bdd4e3d 100644 --- a/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication.console; import org.slf4j.Logger; diff --git a/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java b/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java index 9991589b4..ccad0ef30 100644 --- a/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication.console; import org.slf4j.Logger; diff --git a/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java b/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java index 3f9d15d80..a320a10d7 100644 --- a/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java @@ -1,14 +1,18 @@ /* - * Copyright (c) 2006 - 2011 + * Copyright 2014 Robert von Burg * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * All rights reserved. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; diff --git a/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java b/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java index 798867071..842b0b00e 100644 --- a/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java +++ b/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication.file; public enum FileEndpointMode { diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java index c96a23b98..99052e43d 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication.tcpip; import java.io.DataInputStream; diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java index 17bbe3cc9..4619c0774 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication.tcpip; import java.io.DataInputStream; diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java b/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java index ea967644b..74ccbdc0a 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication.tcpip; /** diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java b/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java index 056bb04aa..6d9b1d7a5 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication.tcpip; import java.io.DataInputStream; diff --git a/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java b/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java index 1071eeee2..097ac910c 100644 --- a/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java +++ b/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java @@ -17,7 +17,6 @@ package ch.eitchnet.utils.exceptions; /** * @author Robert von Burg - * */ public class XmlException extends RuntimeException { private static final long serialVersionUID = 1L; diff --git a/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java b/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java index fdb837ec4..5404d9e7f 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java @@ -19,7 +19,6 @@ import java.util.Arrays; /** * @author Robert von Burg - * */ public class ArraysHelper { diff --git a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java index c9657a51d..97697ee28 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java @@ -17,7 +17,6 @@ package ch.eitchnet.utils.helper; /** * @author Robert von Burg - * */ public class ByteHelper { diff --git a/src/main/java/ch/eitchnet/utils/helper/MathHelper.java b/src/main/java/ch/eitchnet/utils/helper/MathHelper.java index 2b4b1f34b..6c868b889 100644 --- a/src/main/java/ch/eitchnet/utils/helper/MathHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/MathHelper.java @@ -21,7 +21,8 @@ import java.math.RoundingMode; /** * A helper class that contains mathematical computations that can be used throughout. * - * @author msmock, gattom + * @author Martin Smock + * @author Michael Gatto */ public class MathHelper { diff --git a/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java b/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java index e7bcd4eeb..60a236da7 100644 --- a/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java +++ b/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.io; /** @@ -39,7 +54,7 @@ public interface FileProgressListener { * 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(long)} but in case of errors it can be different + * {@link #begin(int, long, long)} but in case of errors it can be different */ public void end(int percent, long position); } diff --git a/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java b/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java index b6105a200..10c0a2ca2 100644 --- a/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java +++ b/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.io; import java.text.MessageFormat; diff --git a/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java b/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java index d5d6faf5f..96b133e07 100644 --- a/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java +++ b/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.io; import java.text.MessageFormat; diff --git a/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java b/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java index dbd1514ef..b98f525a3 100644 --- a/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java +++ b/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.io; import java.io.File; diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java index edbb7f646..b3ef20ada 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java @@ -18,9 +18,9 @@ package ch.eitchnet.utils.iso8601; import java.util.Date; /** - * interface for all date formats internally used by rsp applications + * Interface for date formatting * - * @author msmock + * Martin Smock */ public interface DateFormat { diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java index 411c70d9c..b5189d2e7 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java @@ -16,9 +16,9 @@ package ch.eitchnet.utils.iso8601; /** - * interface for all duration formats internally used by the platform + * Interface for duration formatting * - * @author msmock + * Martin Smock */ public interface DurationFormat { diff --git a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java index 931d1c720..6dbf336f2 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java @@ -21,42 +21,42 @@ 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 * - * @author msmock + * Martin Smock */ public interface FormatFactory { /** * return the formatter for dates * - * @return RSPDurationFormat + * @return {@link DurationFormat} */ public DateFormat getDateFormat(); /** * return the formatter for durations * - * @return RSPDurationFormat + * @return {@link DurationFormat} */ public DurationFormat getDurationFormat(); /** * return the formatter for work time * - * @return RSPWorktimeFormat + * @return {@link WorktimeFormat} */ public WorktimeFormat getWorktimeFormat(); /** * the date format used in xml import and export * - * @return RSPDateFormat + * @return {@link DateFormat} */ public DateFormat getXmlDateFormat(); /** * the duration format used in xml import and export * - * @return RSPDurationFormat + * @return {@link DurationFormat} */ public DurationFormat getXmlDurationFormat(); diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java index 2c9f1de68..327e67b2c 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java @@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.StringHelper; /** - * + * @author Martin Smock */ @SuppressWarnings("nls") public class ISO8601 implements DateFormat { diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java index c319c719e..9d4ae3320 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java @@ -42,8 +42,8 @@ package ch.eitchnet.utils.iso8601; * minutes and seconds *

      * - * @author msmock - * @author gattom (reimplementation using enum) + * @author Martin Smock + * @author Michael Gatto (reimplementation using enum) */ @SuppressWarnings("nls") public class ISO8601Duration implements DurationFormat { diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java index 682ad6bbd..c8ea878c7 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java @@ -22,7 +22,7 @@ import ch.eitchnet.utils.helper.MathHelper; /** * Default factory for date formats used for serialization. * - * @author msmock + * @author Martin Smock */ public class ISO8601FormatFactory implements FormatFactory { diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java index 81f96cfa5..31dde2a1c 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java @@ -42,8 +42,8 @@ package ch.eitchnet.utils.iso8601; * minutes and seconds *

      * - * @author msmock - * @author gattom (reimplementation using enum) + * @author Martin Smock + * @author Michael Gatto (reimplementation using enum) */ @SuppressWarnings("nls") public class ISO8601Worktime implements WorktimeFormat { diff --git a/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java b/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java index 48fbf65b3..c026e060f 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java @@ -18,7 +18,7 @@ package ch.eitchnet.utils.iso8601; /** * interface for the worktime format * - * @author msmock + * @author Martin Smock */ public interface WorktimeFormat { diff --git a/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java b/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java index 2d1f65ee7..ec25837e9 100644 --- a/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java +++ b/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import static org.junit.Assert.fail; @@ -9,17 +24,32 @@ import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ public class AbstractEndpointTest { static final Logger logger = LoggerFactory.getLogger(FileEndpointTest.class); - public static TestIoMessage createTestMessage(String key1, String key2) { - return createTestMessage(CommandKey.key(key1, key2)); + 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) { - TestIoMessage msg = new TestIoMessage(UUID.randomUUID().toString(), key); + public static TestIoMessage createTestMessage(CommandKey key, String connectionId) { + TestIoMessage msg = new TestIoMessage(UUID.randomUUID().toString(), key, connectionId); List lines = new ArrayList<>(); lines.add("bla"); lines.add("foo"); diff --git a/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java b/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java index 54d4f960e..9a7e41bc0 100644 --- a/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java +++ b/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import static org.junit.Assert.assertEquals; @@ -12,6 +27,9 @@ import org.slf4j.Logger; import ch.eitchnet.communication.console.ConsoleEndpoint; import ch.eitchnet.communication.console.ConsoleMessageVisitor; +/** + * @author Robert von Burg + */ public class ConsoleEndpointTest extends AbstractEndpointTest { private static final String CONNECTION_ID = "Console"; //$NON-NLS-1$ @@ -34,7 +52,7 @@ public class ConsoleEndpointTest extends AbstractEndpointTest { this.connection.start(); CommandKey key = CommandKey.key(CONNECTION_ID, "logger"); //$NON-NLS-1$ - TestIoMessage msg = createTestMessage(key); + TestIoMessage msg = createTestMessage(key, CONNECTION_ID); TestConnectionObserver observer = new TestConnectionObserver(); this.connection.addConnectionObserver(key, observer); diff --git a/src/test/java/ch/eitchnet/communication/FileEndpointTest.java b/src/test/java/ch/eitchnet/communication/FileEndpointTest.java index 454433a76..9ae899d8c 100644 --- a/src/test/java/ch/eitchnet/communication/FileEndpointTest.java +++ b/src/test/java/ch/eitchnet/communication/FileEndpointTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import static org.junit.Assert.assertEquals; @@ -21,6 +36,9 @@ import ch.eitchnet.communication.file.FileEndpoint; import ch.eitchnet.communication.file.FileEndpointMode; import ch.eitchnet.utils.helper.FileHelper; +/** + * @author Robert von Burg + */ public class FileEndpointTest extends AbstractEndpointTest { public static final String INBOUND_FILENAME = "target/test_in.txt"; //$NON-NLS-1$ @@ -63,7 +81,7 @@ public class FileEndpointTest extends AbstractEndpointTest { // send a message this.connection.start(); TestConnectionObserver outboundObserver = new TestConnectionObserver(); - TestIoMessage message = createTestMessage(outboundFilename, FileEndpointMode.WRITE.name()); + TestIoMessage message = createTestMessage(outboundFilename, FileEndpointMode.WRITE.name(), CONNECTION_ID); this.connection.addConnectionObserver(message.getKey(), outboundObserver); this.connection.send(message); @@ -105,7 +123,7 @@ public class FileEndpointTest extends AbstractEndpointTest { lines.add(line); } return new TestIoMessage(UUID.randomUUID().toString(), CommandKey.key(this.inboundFilename, - FileEndpointMode.READ.name()), lines); + FileEndpointMode.READ.name()), CONNECTION_ID, lines); } @Override diff --git a/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java b/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java new file mode 100644 index 000000000..483041519 --- /dev/null +++ b/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.communication; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.Test; + +/** + * @author Robert von Burg + */ +public class SimpleMessageArchiveTest extends AbstractEndpointTest { + + @Test + public void testArchive() throws InterruptedException { + + IoMessageArchive archive = new SimpleMessageArchive(20, 5); + + CommandKey key = CommandKey.key("key1", "key2"); //$NON-NLS-1$//$NON-NLS-2$ + String connectionId = "connection1"; //$NON-NLS-1$ + + int i = 0; + for (; i < 20; i++) { + TestIoMessage msg = new TestIoMessage(UUID.randomUUID().toString(), key, connectionId); + // update the time by plus 1, otherwise the tree set does not add it + msg.setUpdated(new Date(i + 1)); + archive.archive(msg); + } + + assertEquals(20, archive.size()); + + // add one more + TestIoMessage msg = new TestIoMessage(UUID.randomUUID().toString(), key, connectionId); + msg.setUpdated(new Date(i + 1)); + archive.archive(msg); + + // validate the trimming works + assertEquals(15, archive.size()); + + // Now make sure our last element is still in the list + List all = archive.getAll(); + Collections.sort(all, new Comparator() { + @Override + public int compare(IoMessage o1, IoMessage o2) { + return o1.getUpdated().compareTo(o2.getUpdated()); + } + }); + IoMessage message = all.get(all.size() - 1); + assertEquals(msg.getId(), message.getId()); + } +} diff --git a/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java b/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java index 80227ac0a..3e9425912 100644 --- a/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java +++ b/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import static org.junit.Assert.assertEquals; @@ -22,6 +37,9 @@ import ch.eitchnet.communication.tcpip.ServerSocketEndpoint; import ch.eitchnet.communication.tcpip.SocketEndpointConstants; import ch.eitchnet.communication.tcpip.SocketMessageVisitor; +/** + * @author Robert von Burg + */ public class SocketEndpointTest extends AbstractEndpointTest { private static final String PORT = "45678"; //$NON-NLS-1$ @@ -85,7 +103,7 @@ public class SocketEndpointTest extends AbstractEndpointTest { CommandKey outboundKey = CommandKey.key(CLIENT_CONNECTION_ID, "lines"); //$NON-NLS-1$ this.clientConnection.addConnectionObserver(outboundKey, clientObserver); - TestIoMessage outboundMsg = createTestMessage(outboundKey); + TestIoMessage outboundMsg = createTestMessage(outboundKey, CLIENT_CONNECTION_ID); this.clientConnection.send(outboundMsg); waitForMessage(clientObserver); assertEquals(outboundMsg.getKey(), clientObserver.getMessage().getKey()); @@ -128,7 +146,8 @@ public class SocketEndpointTest extends AbstractEndpointTest { } 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"), lines); //$NON-NLS-1$ + return new TestIoMessage(UUID.randomUUID().toString(), + CommandKey.key(SERVER_CONNECTION_ID, "lines"), SERVER_CONNECTION_ID, lines); //$NON-NLS-1$ } } } diff --git a/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java b/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java index a2882b7cd..36db42fc5 100644 --- a/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java +++ b/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import java.text.MessageFormat; @@ -5,6 +20,9 @@ import java.text.MessageFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * @author Robert von Burg + */ public class TestConnectionObserver implements ConnectionObserver { private static final Logger logger = LoggerFactory.getLogger(FileEndpointTest.class); diff --git a/src/test/java/ch/eitchnet/communication/TestIoMessage.java b/src/test/java/ch/eitchnet/communication/TestIoMessage.java index cdd3d139b..c4d473d08 100644 --- a/src/test/java/ch/eitchnet/communication/TestIoMessage.java +++ b/src/test/java/ch/eitchnet/communication/TestIoMessage.java @@ -1,17 +1,35 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.communication; import java.util.List; +/** + * @author Robert von Burg + */ public class TestIoMessage extends IoMessage { private List contents; - public TestIoMessage(String id, CommandKey key) { - super(id, key); + public TestIoMessage(String id, CommandKey key, String connectionId) { + super(id, key, connectionId); } - public TestIoMessage(String id, CommandKey key, List contents) { - super(id, key); + public TestIoMessage(String id, CommandKey key, String connectionId, List contents) { + super(id, key, connectionId); this.contents = contents; } diff --git a/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java b/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java index 2f73cdb45..5877e46d2 100644 --- a/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java +++ b/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ch.eitchnet.utils.dbc; import java.io.File; @@ -12,7 +27,7 @@ import ch.eitchnet.utils.dbc.DBC.DbcException; * The class DBCTest contains tests for the class {@link DBC}. * * @generatedBy CodePro at 2/2/14 8:13 PM - * @author eitch + * @author Robert von Burg * @version $Revision: 1.0 $ */ @SuppressWarnings("nls") From b87784112ef8ba1b501f932e21b5829fdc8b778e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Aug 2014 14:42:37 +0200 Subject: [PATCH 289/457] [Project] Fixed broken imports --- .../eitchnet/communication/ConnectionMessages.java | 5 +++-- .../communication/tcpip/ClientSocketEndpoint.java | 9 +++++---- .../communication/tcpip/ServerSocketEndpoint.java | 13 +++++++------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java index d3f4940b4..69eff51a5 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java @@ -19,7 +19,8 @@ import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.utils.helper.StringHelper; @@ -30,7 +31,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class ConnectionMessages { - private static Logger logger = Logger.getLogger(ConnectionMessages.class); + private static Logger logger = LoggerFactory.getLogger(ConnectionMessages.class); /** * Utility class diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java index 99052e43d..216964853 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -24,7 +24,8 @@ import java.net.UnknownHostException; import java.text.MessageFormat; import java.util.Map; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.communication.CommunicationConnection; import ch.eitchnet.communication.CommunicationEndpoint; @@ -51,7 +52,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class ClientSocketEndpoint implements CommunicationEndpoint { - protected static final Logger logger = Logger.getLogger(ClientSocketEndpoint.class); + protected static final Logger logger = LoggerFactory.getLogger(ClientSocketEndpoint.class); // state variables private boolean connected; @@ -210,7 +211,7 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { } catch (Exception e) { String msg = "Error while connecting to {0}:{1}"; //$NON-NLS-1$ logger.error(MessageFormat.format(msg, this.remoteInputAddressS, Integer.toString(this.remoteInputPort))); - logger.error(e, e); + logger.error(e.getMessage(), e); this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); } } @@ -511,7 +512,7 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { logger.warn("Socket has been closed!"); //$NON-NLS-1$ message.setState(State.FATAL, "Socket has been closed!"); //$NON-NLS-1$ } else { - logger.error(e, e); + logger.error(e.getMessage(), e); message.setState(State.FATAL, e.getLocalizedMessage()); this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); } diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java index 4619c0774..0b8d864e0 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -27,7 +27,8 @@ import java.net.UnknownHostException; import java.text.MessageFormat; import java.util.Map; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.communication.CommunicationConnection; import ch.eitchnet.communication.CommunicationEndpoint; @@ -55,7 +56,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { - protected static final Logger logger = Logger.getLogger(ServerSocketEndpoint.class); + protected static final Logger logger = LoggerFactory.getLogger(ServerSocketEndpoint.class); private Thread serverThread; @@ -228,7 +229,7 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { } else { String msg = "Error while opening socket for inbound connection {0}"; //$NON-NLS-1$ logger.error(MessageFormat.format(msg, this.connection.getId())); - logger.error(e, e); + logger.error(e.getMessage(), e); this.connected = false; this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); } @@ -532,8 +533,8 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { this.serverSocket = new ServerSocket(this.localInputPort, 1, this.localInputAddress); this.serverSocket.setReuseAddress(true); } catch (BindException e) { - logger.fatal("Fatal BindException occurred! Port is already in use, or address is illegal!"); //$NON-NLS-1$ - logger.fatal(e, 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; @@ -559,7 +560,7 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { if (e instanceof InterruptedException) { logger.error("Interrupted!"); //$NON-NLS-1$ } else { - logger.error(e, e); + logger.error(e.getMessage(), e); } this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); } finally { From b94423f1ce32045cda88f624708b35d78208291b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Aug 2014 14:44:27 +0200 Subject: [PATCH 290/457] [Minor] fixed compiler warnings about calling close() on stream --- src/main/java/ch/eitchnet/utils/helper/FileHelper.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 7d73800ec..397ed6382 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -147,9 +147,7 @@ public class FileHelper { 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) { @@ -168,10 +166,7 @@ public class FileHelper { public static final void writeStringToFile(String string, File dstFile) { try (BufferedWriter bufferedwriter = new BufferedWriter(new FileWriter(dstFile));) { - bufferedwriter.write(string); - bufferedwriter.close(); - } catch (FileNotFoundException e) { throw new RuntimeException("Filed does not exist " + dstFile.getAbsolutePath()); //$NON-NLS-1$ } catch (IOException e) { @@ -297,9 +292,7 @@ public class FileHelper { outBuffer.write(theByte); } - inBuffer.close(); outBuffer.flush(); - outBuffer.close(); if (checksum) { String fromFileMD5 = StringHelper.getHexString(FileHelper.hashFileMd5(fromFile)); @@ -554,7 +547,6 @@ public class FileHelper { complete.update(buffer, 0, numRead); } } while (numRead != -1); - fis.close(); return complete.digest(); } catch (Exception e) { From b54d16487b27a899b2622e134f1cf6571d702c66 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Aug 2014 15:09:24 +0200 Subject: [PATCH 291/457] [New] Added StringHelper.getUniqueId() --- .../eitchnet/utils/helper/StringHelper.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index b7f1b6302..2d90238a6 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -46,6 +46,11 @@ public class StringHelper { 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 */ @@ -629,4 +634,19 @@ public class StringHelper { throw new RuntimeException(msg); } } + + /** + * 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() { + + if (uniqueId == Long.MAX_VALUE - 1) { + uniqueId = 0; + } + + uniqueId += 1; + return Long.toString(uniqueId); + } } From 4c467df11ae1ac25038c0d920cf9341975a9e6c5 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Aug 2014 15:53:04 +0200 Subject: [PATCH 292/457] [New] Added AsciiHelper --- .../ch/eitchnet/utils/helper/AsciiHelper.java | 312 ++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java b/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java new file mode 100644 index 000000000..b06c24474 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java @@ -0,0 +1,312 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.helper; + +/** + * ASCII constants + * + * @author Robert von Burg + */ +public class AsciiHelper { + + /** + * ASCII Value 0, interpretation: NUL
      + * Description: Null character + */ + public static final char NUL = (char) 0; // Null character + + /** + * ASCII Value 1, interpretation: SOH
      + * Description: Start of Header + */ + public static final char SOH = (char) 1; // Start of Header + + /** + * ASCII Value 2, interpretation: STX
      + * Description: Start of Text + */ + public static final char STX = (char) 2; // Start of Text + + /** + * ASCII Value 3, interpretation: ETX
      + * Description: End of Text + */ + public static final char ETX = (char) 3; // End of Text + + /** + * ASCII Value 4, interpretation: EOT
      + * Description: End of Transmission + */ + public static final char EOT = (char) 4; // End of Transmission + + /** + * ASCII Value 5, interpretation: ENQ
      + * Description: Enquiry + */ + public static final char ENQ = (char) 5; // Enquiry + + /** + * ASCII Value 6, interpretation: ACK
      + * Description: Acknowledgement + */ + public static final char ACK = (char) 6; // Acknowledgement + + /** + * ASCII Value 7, interpretation: BEL
      + * Description: Bell + */ + public static final char BEL = (char) 7; // Bell + + /** + * ASCII Value 8, interpretation: BS
      + * Description: Backspace + */ + public static final char BS = (char) 8; // Backspace + + /** + * ASCII Value 9, interpretation: HT
      + * Description: Horizontal Tab + */ + public static final char HT = (char) 9; // Horizontal Tab + + /** + * ASCII Value 10, interpretation: LF
      + * Description: Line Feed + */ + public static final char LF = (char) 10; // Line Feed + + /** + * ASCII Value 11, interpretation: VT
      + * Description: Vertical Tab + */ + public static final char VT = (char) 11; // Vertical Tab + + /** + * ASCII Value 12, interpretation: FF
      + * Description: Form Feed + */ + public static final char FF = (char) 12; // Form Feed + + /** + * ASCII Value 13, interpretation: CR
      + * Description: Carriage Return + */ + public static final char CR = (char) 13; // Carriage Return + + /** + * ASCII Value 14, interpretation: SO
      + * Description: Shift Out + */ + public static final char SO = (char) 14; // Shift Out + + /** + * ASCII Value 15, interpretation: SI
      + * Description: Shift In + */ + public static final char SI = (char) 15; // Shift In + + /** + * ASCII Value 16, interpretation: DLE
      + * Description: Data Link Escape + */ + public static final char DLE = (char) 16; // Data Link Escape + + /** + * ASCII Value 17, interpretation: DC1
      + * Description: (XON) Device Control 1 + */ + public static final char DC1 = (char) 17; // (XON) Device Control 1 + + /** + * ASCII Value 18, interpretation: DC2
      + * Description: Device Control 2 + */ + public static final char DC2 = (char) 18; // Device Control 2 + + /** + * ASCII Value 19 interpretation: DC3
      + * Description: (XOFF) Device Control 3 + */ + public static final char DC3 = (char) 19; // (XOFF) Device Control 3 + + /** + * ASCII Value 20, interpretation: DC4
      + * Description: Device Control 4 + */ + public static final char DC4 = (char) 20; // Device Control 4 + + /** + * ASCII Value 21, interpretation: NAK
      + * Description: Negative Acknowledgment + */ + public static final char NAK = (char) 21; // Negative Acknowledgment + + /** + * ASCII Value 22, interpretation: SYN
      + * Description: Synchronous Idle + */ + public static final char SYN = (char) 22; // Synchronous Idle + + /** + * ASCII Value 23, interpretation: ETB
      + * Description: End of Transmission Block + */ + public static final char ETB = (char) 23; // End of Transmission Block + + /** + * ASCII Value 24, interpretation: CAN
      + * Description: Cancel + */ + public static final char CAN = (char) 24; // Cancel + + /** + * ASCII Value 25, interpretation: EM
      + * Description: End of Medium + */ + public static final char EM = (char) 25; // End of Medium + + /** + * ASCII Value 26, interpretation: SUB
      + * Description: Substitute + */ + public static final char SUB = (char) 26; // Substitute + + /** + * ASCII Value 27, interpretation: ESC
      + * Description: Escape + */ + public static final char ESC = (char) 27; // Escape + + /** + * ASCII Value 28, interpretation: FS
      + * Description: File Separator + */ + public static final char FS = (char) 28; // File Separator + + /** + * ASCII Value 29, interpretation: GS
      + * Description: Group Separator + */ + public static final char GS = (char) 29; // Group Separator + + /** + * ASCII Value 30, interpretation: RS
      + * Description: Request to Send (Record Separator) + */ + public static final char RS = (char) 30; // Request to Send (Record Separator) + + /** + * ASCII Value 31, interpretation: US
      + * Description: Unit Separator + */ + public static final char US = (char) 31; // Unit Separator + + /** + * ASCII Value 32, interpretation: SP
      + * Description: Space + */ + public static final char SP = (char) 32; // Space + + /** + * ASCII Value 127, interpretation: DEL
      + * Description: Delete + */ + public static final char DEL = (char) 127; // Delete + + /** + * Returns the ASCII Text of a certain 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 + ")"; + } + } +} From 52a04f0c33b5b5c543ce3ac5f31d813f7c7a7760 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Aug 2014 15:53:32 +0200 Subject: [PATCH 293/457] [Minor] made some fields protected in tcpip endpoints --- .../communication/tcpip/ClientSocketEndpoint.java | 9 +++++---- .../communication/tcpip/ServerSocketEndpoint.java | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java index 216964853..d881c541f 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -76,12 +76,13 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { // connection private Socket socket; - private DataOutputStream outputStream; - private DataInputStream inputStream; - private CommunicationConnection connection; + protected DataOutputStream outputStream; + protected DataInputStream inputStream; - private SocketMessageVisitor messageVisitor; + protected CommunicationConnection connection; + + protected SocketMessageVisitor messageVisitor; /** * Default constructor diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java index 0b8d864e0..ae12d8c7e 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -82,12 +82,13 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { // connection private ServerSocket serverSocket; private Socket socket; - private DataOutputStream outputStream; - private DataInputStream inputStream; - private CommunicationConnection connection; + protected DataOutputStream outputStream; + protected DataInputStream inputStream; - private SocketMessageVisitor messageVisitor; + protected CommunicationConnection connection; + + protected SocketMessageVisitor messageVisitor; /** * Default constructor From c7484934a5380f50eb7af4b8493d27ae98436fbe Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 12 Aug 2014 16:00:25 +0200 Subject: [PATCH 294/457] [New] Added AsciiHelper --- .../java/ch/eitchnet/utils/helper/AsciiHelper.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java b/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java index b06c24474..7f648de0f 100644 --- a/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java @@ -226,6 +226,16 @@ public class AsciiHelper { */ 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 * From 828d725a692092ee33a84a51d4cd832425f7fb0b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 14 Aug 2014 16:22:15 +0200 Subject: [PATCH 295/457] [Project] using parent version 1.1.0-SNAPSHOT --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0f2ef0408..e48ed96f9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ ch.eitchnet ch.eitchnet.parent - 0.1.0-SNAPSHOT + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml ch.eitchnet.privilege - jar 0.2.0-SNAPSHOT + jar ch.eitchnet.privilege https://github.com/eitchnet/ch.eitchnet.privilege 2011 @@ -25,6 +25,7 @@ scm:git:https://github.com/eitchnet/ch.eitchnet.privilege.git scm:git:git@github.com:eitchnet/ch.eitchnet.privilege.git https://github.com/eitchnet/ch.eitchnet.privilege + HEAD From b5ac7d008f882c7bf6cbd2fa585f2b559db3348f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 14 Aug 2014 16:23:10 +0200 Subject: [PATCH 296/457] [Project] using parent version 1.1.0-SNAPSHOT --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 609b01d1a..793f66be1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ ch.eitchnet ch.eitchnet.parent - 0.1.0-SNAPSHOT + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml ch.eitchnet.utils - jar 0.2.0-SNAPSHOT + jar ch.eitchnet.utils These utils contain project independent helper classes and utilities for reuse https://github.com/eitchnet/ch.eitchnet.utils @@ -26,6 +26,7 @@ scm:git:https://github.com/eitchnet/ch.eitchnet.utils.git scm:git:git@github.com:eitchnet/ch.eitchnet.utils.git https://github.com/eitchnet/ch.eitchnet.utils + HEAD From d236d8589bb9a087a26e24c88e4ca7f7a8b0fa62 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 14 Aug 2014 16:23:30 +0200 Subject: [PATCH 297/457] [Project] using parent version 1.1.0-SNAPSHOT --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5bd34d729..b90e5dea4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ ch.eitchnet ch.eitchnet.parent - 0.1.0-SNAPSHOT + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml ch.eitchnet.xmlpers - jar 0.3.0-SNAPSHOT + jar ch.eitchnet.xmlpers https://github.com/eitchnet/ch.eitchnet.xmlpers @@ -32,6 +32,7 @@ scm:git:https://github.com/eitchnet/ch.eitchnet.xmlpers.git scm:git:git@github.com:eitchnet/ch.eitchnet.xmlpers.git https://github.com/eitchnet/ch.eitchnet.xmlpers + HEAD From 863ffecd957a98c96c0c2e62a6b5785f29921fe0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 15 Aug 2014 14:53:38 +0200 Subject: [PATCH 298/457] [New] added small chat server/client --- pom.xml | 15 ++- .../CommunicationConnection.java | 31 +++++- .../communication/ConnectionMessages.java | 14 ++- .../ConnectionStateObserver.java | 6 + .../ch/eitchnet/communication/chat/Chat.java | 104 ++++++++++++++++++ .../communication/chat/ChatClient.java | 91 +++++++++++++++ .../communication/chat/ChatIoMessage.java | 31 ++++++ .../chat/ChatMessageVisitor.java | 38 +++++++ .../communication/chat/ChatServer.java | 88 +++++++++++++++ .../tcpip/ClientSocketEndpoint.java | 14 ++- .../tcpip/ServerSocketEndpoint.java | 18 +-- .../tcpip/SocketMessageVisitor.java | 10 ++ src/main/java/log4j.xml | 30 +++++ .../communication/SocketEndpointTest.java | 9 +- 14 files changed, 467 insertions(+), 32 deletions(-) create mode 100644 src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java create mode 100644 src/main/java/ch/eitchnet/communication/chat/Chat.java create mode 100644 src/main/java/ch/eitchnet/communication/chat/ChatClient.java create mode 100644 src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java create mode 100644 src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java create mode 100644 src/main/java/ch/eitchnet/communication/chat/ChatServer.java create mode 100644 src/main/java/log4j.xml diff --git a/pom.xml b/pom.xml index 793f66be1..f81efd194 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,11 @@ + + org.slf4j + slf4j-log4j12 + runtime + @@ -46,9 +51,9 @@ org.apache.maven.plugins maven-source-plugin - + org.apache.maven.plugins - maven-javadoc-plugin + maven-javadoc-plugin org.apache.maven.plugins @@ -57,11 +62,11 @@ org.apache.maven.plugins maven-site-plugin - - + + org.apache.maven.plugins - maven-deploy-plugin + maven-deploy-plugin diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java index 803dffc58..846be6086 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -48,6 +48,7 @@ public class CommunicationConnection implements Runnable { private Thread queueThread; private volatile boolean run; private MapOfLists connectionObservers; + private List connectionStateObservers; private CommunicationEndpoint endpoint; private IoMessageVisitor messageVisitor; @@ -73,6 +74,7 @@ public class CommunicationConnection implements Runnable { this.stateMsg = this.state.toString(); this.messageQueue = new LinkedBlockingDeque<>(); this.connectionObservers = new MapOfLists<>(); + this.connectionStateObservers = new ArrayList<>(); } public void setArchive(IoMessageArchive archive) { @@ -123,9 +125,31 @@ public class CommunicationConnection implements Runnable { } } + public void addConnectionStateObserver(ConnectionStateObserver observer) { + synchronized (this.connectionStateObservers) { + this.connectionStateObservers.add(observer); + } + } + + public void removeConnectionStateObserver(ConnectionStateObserver observer) { + synchronized (this.connectionStateObservers) { + this.connectionStateObservers.remove(observer); + } + } + public void notifyStateChange(ConnectionState state, String stateMsg) { + ConnectionState oldState = this.state; + String oldStateMsg = this.stateMsg; this.state = state; this.stateMsg = stateMsg; + + List observers; + synchronized (this.connectionStateObservers) { + observers = new ArrayList<>(this.connectionStateObservers); + } + for (ConnectionStateObserver observer : observers) { + observer.notify(oldState, oldStateMsg, state, stateMsg); + } } public void switchMode(ConnectionMode mode) { @@ -160,7 +184,6 @@ public class CommunicationConnection implements Runnable { logger.info("Started SIMULATION connection!"); //$NON-NLS-1$ break; case ON: - logger.info("Connecting..."); //$NON-NLS-1$ if (this.queueThread != null) { logger.warn(MessageFormat.format("{0}: Already connected!", this.id)); //$NON-NLS-1$ } else { @@ -327,8 +350,10 @@ public class CommunicationConnection implements Runnable { } synchronized (this.connectionObservers) { List observers = this.connectionObservers.getList(message.getKey()); - for (ConnectionObserver observer : observers) { - observer.notify(message.getKey(), message); + if (observers != null) { + for (ConnectionObserver observer : observers) { + observer.notify(message.getKey(), message); + } } } } diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java index 69eff51a5..18aae76ad 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java @@ -72,7 +72,7 @@ public class ConnectionMessages { value = StringHelper.NULL; String msg = "{0}: parameter ''{1}'' has invalid value ''{2}''"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, clazz.getName(), parameterName, value); + msg = MessageFormat.format(msg, clazz.getSimpleName(), parameterName, value); ConnectionException e = new ConnectionException(msg); return e; } @@ -88,7 +88,7 @@ public class ConnectionMessages { */ 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.getName(), parameter1, parameter1); + msg = MessageFormat.format(msg, clazz.getSimpleName(), parameter1, parameter1); ConnectionException e = new ConnectionException(msg); return e; } @@ -101,10 +101,12 @@ public class ConnectionMessages { * @param defValue */ public static void warnUnsetParameter(Class clazz, String parameterName, String defValue) { - String msg = "{0}: parameter ''{1}'' is not set, using default value ''{2}''"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, clazz.getName(), parameterName, defValue); - Map properties = new HashMap(); - logger.warn(MessageFormat.format(msg, properties)); + if (logger.isDebugEnabled()) { + String msg = "{0}: parameter ''{1}'' is not set, using default value ''{2}''"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, clazz.getSimpleName(), parameterName, defValue); + Map properties = new HashMap(); + logger.warn(MessageFormat.format(msg, properties)); + } } /** diff --git a/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java b/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java new file mode 100644 index 000000000..3b98f1f17 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java @@ -0,0 +1,6 @@ +package ch.eitchnet.communication; + +public interface ConnectionStateObserver { + + public void notify(ConnectionState oldState, String oldStateMsg, ConnectionState newState, String newStateMsg); +} diff --git a/src/main/java/ch/eitchnet/communication/chat/Chat.java b/src/main/java/ch/eitchnet/communication/chat/Chat.java new file mode 100644 index 000000000..2e53e2a73 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/chat/Chat.java @@ -0,0 +1,104 @@ +package ch.eitchnet.communication.chat; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.text.MessageFormat; + +import ch.eitchnet.utils.helper.StringHelper; + +public class Chat { + + public static void main(String[] args) { + + if (args.length < 3) + printIllegalArgsAndExit(args); + + if (args[0].equals("server")) { //$NON-NLS-1$ + if (args.length != 3) + printIllegalArgsAndExit(args); + startServer(args); + } else if (args[0].equals("client")) { //$NON-NLS-1$ + if (args.length != 4) + printIllegalArgsAndExit(args); + startClient(args); + } + } + + private static void startServer(String[] args) { + + // port + int port; + String portS = args[1]; + 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[2]; + + // start + ChatServer chatServer = new ChatServer(port, username); + chatServer.start(); + } + + private static void startClient(String[] args) { + + // server + InetAddress host; + String hostS = args[1]; + try { + host = InetAddress.getByName(hostS); + } catch (UnknownHostException e1) { + System.err.println(MessageFormat.format("Illegal server address: {0}", hostS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + + // port + int port; + String portS = args[2]; + try { + port = Integer.parseInt(portS); + } catch (NumberFormatException e) { + System.err.println(MessageFormat.format("Illegal port: {0}", portS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + if (port < 1 || port > 65535) { + System.err.println(MessageFormat.format("Illegal port: {0}", port)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + + // username + String username = args[3]; + + // start + ChatClient chatClient = new ChatClient(host, port, username); + chatClient.start(); + } + + private static void printIllegalArgsAndExit(String[] args) { + System.err.print("Illegal arguments: "); //$NON-NLS-1$ + if (args.length == 0) { + System.err.print("(none)"); //$NON-NLS-1$ + } else { + for (String arg : args) { + System.err.print(arg + StringHelper.SPACE); + } + } + System.err.println(); + System.err.println("Usage: java ...Chat server "); //$NON-NLS-1$ + System.err.println("Usage: java ...Chat client "); //$NON-NLS-1$ + System.exit(1); + } +} diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatClient.java b/src/main/java/ch/eitchnet/communication/chat/ChatClient.java new file mode 100644 index 000000000..903c45fef --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/chat/ChatClient.java @@ -0,0 +1,91 @@ +package ch.eitchnet.communication.chat; + +import static ch.eitchnet.communication.chat.ChatMessageVisitor.inboundKey; +import static ch.eitchnet.communication.chat.ChatMessageVisitor.outboundKey; + +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +import ch.eitchnet.communication.CommandKey; +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.ConnectionMode; +import ch.eitchnet.communication.ConnectionObserver; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.ConnectionStateObserver; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.tcpip.ClientSocketEndpoint; +import ch.eitchnet.communication.tcpip.SocketEndpointConstants; +import ch.eitchnet.communication.tcpip.SocketMessageVisitor; + +public class ChatClient implements ConnectionObserver, ConnectionStateObserver { + + private static final String id = "ChatClient"; //$NON-NLS-1$ + private InetAddress host; + private int port; + private String username; + private boolean connected; + + public ChatClient(InetAddress host, int port, String username) { + this.host = host; + this.port = port; + this.username = username; + } + + public void start() { + ConnectionMode mode = ConnectionMode.ON; + + Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_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) { + this.notifyAll(); + } + } +} diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java b/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java new file mode 100644 index 000000000..a9bf851eb --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java @@ -0,0 +1,31 @@ +package ch.eitchnet.communication.chat; + +import ch.eitchnet.communication.CommandKey; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.utils.helper.StringHelper; + +public class ChatIoMessage extends IoMessage { + + private String chatMsg; + + public ChatIoMessage(String id, CommandKey key, String connectionId) { + super(id, key, connectionId); + } + + public String getChatMsg() { + return this.chatMsg; + } + + public void setChatMsg(String chagMsg) { + this.chatMsg = chagMsg; + } + + public static ChatIoMessage msg(CommandKey key, String connId, String username, String msg) { + + String line = username + StringHelper.COLON + StringHelper.SPACE + msg; + + ChatIoMessage chatIoMessage = new ChatIoMessage(StringHelper.getUniqueId(), key, connId); + chatIoMessage.setChatMsg(line); + return chatIoMessage; + } +} diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java b/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java new file mode 100644 index 000000000..6589bb389 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java @@ -0,0 +1,38 @@ +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 { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String line = bufferedReader.readLine(); + 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.writeBytes(chatIoMessage.getChatMsg()); + outputStream.writeByte('\n'); + outputStream.flush(); + } +} diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatServer.java b/src/main/java/ch/eitchnet/communication/chat/ChatServer.java new file mode 100644 index 000000000..3fd82ea84 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/chat/ChatServer.java @@ -0,0 +1,88 @@ +package ch.eitchnet.communication.chat; + +import static ch.eitchnet.communication.chat.ChatMessageVisitor.inboundKey; +import static ch.eitchnet.communication.chat.ChatMessageVisitor.outboundKey; + +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 int port; + private String username; + private boolean connected; + + public ChatServer(int port, String username) { + this.port = port; + this.username = username; + } + + public void start() { + ConnectionMode mode = ConnectionMode.ON; + + Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_USE_TIMEOUT, Boolean.FALSE.toString()); + 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) { + this.notifyAll(); + } + } +} diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java index d881c541f..c58926f3b 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -173,9 +173,11 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { } // configure the socket - 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())); + 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); @@ -196,7 +198,7 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { this.inputStream.skip(available); } - msg = "Connected {0}: {1}:{2} with local side {3}:{4}"; //$NON-NLS-1$ + 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()))); @@ -325,7 +327,7 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { // if local output address is not set, then we will use the localhost InetAddress if (this.localOutputAddressS == null || this.localOutputAddressS.length() == 0) { - logger.warn("No localOutputAddress set. Using localhost"); //$NON-NLS-1$ + logger.debug("No localOutputAddress set. Using localhost"); //$NON-NLS-1$ } else { // parse local output address name to InetAddress object @@ -462,7 +464,7 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { 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$ + // 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) { diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java index ae12d8c7e..8cc5b71ec 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -187,9 +187,11 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { } // configure the socket - 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())); + 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); @@ -344,7 +346,7 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { // if remote address is not set, then we will use the localhost InetAddress if (this.remoteOutputAddressS == null || this.remoteOutputAddressS.length() == 0) { - logger.warn("No remoteOutputAddress set. Allowing connection from any remote address"); //$NON-NLS-1$ + logger.debug("No remoteOutputAddress set. Allowing connection from any remote address"); //$NON-NLS-1$ } else { // parse remote output address name to InetAddress object @@ -464,7 +466,7 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { 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$ + // logger.info(MessageFormat.format("Enabling connection {0}...", this.connection.getId())); //$NON-NLS-1$ this.closed = false; this.serverThread = new Thread(this, this.connection.getId()); @@ -528,9 +530,9 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { 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))); + // 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) { diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java b/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java index 6d9b1d7a5..1c9610820 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java @@ -23,6 +23,16 @@ import ch.eitchnet.communication.IoMessageVisitor; public abstract class SocketMessageVisitor extends IoMessageVisitor { + protected final String connectionId; + + public SocketMessageVisitor(String connectionId) { + this.connectionId = connectionId; + } + + public String getConnectionId() { + return this.connectionId; + } + public abstract IoMessage visit(DataInputStream inputStream, DataOutputStream outputStream) throws Exception; public abstract void visit(DataInputStream inputStream, DataOutputStream outputStream, IoMessage message) diff --git a/src/main/java/log4j.xml b/src/main/java/log4j.xml new file mode 100644 index 000000000..0a2a73d06 --- /dev/null +++ b/src/main/java/log4j.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java b/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java index 3e9425912..d2db3c0b5 100644 --- a/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java +++ b/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java @@ -61,7 +61,7 @@ public class SocketEndpointTest extends AbstractEndpointTest { parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.TRUE.toString()); CommunicationEndpoint endpoint = new ClientSocketEndpoint(); - SocketMessageVisitor messageVisitor = new SocketMessageVisitorExtension(); + SocketMessageVisitor messageVisitor = new SocketMessageVisitorExtension(CLIENT_CONNECTION_ID); this.clientConnection = new CommunicationConnection(CLIENT_CONNECTION_ID, ConnectionMode.ON, parameters, endpoint, messageVisitor); this.clientConnection.configure(); @@ -73,7 +73,7 @@ public class SocketEndpointTest extends AbstractEndpointTest { parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT, PORT); CommunicationEndpoint endpoint = new ServerSocketEndpoint(); - SocketMessageVisitor messageVisitor = new SocketMessageVisitorExtension(); + SocketMessageVisitor messageVisitor = new SocketMessageVisitorExtension(SERVER_CONNECTION_ID); this.serverConnection = new CommunicationConnection(SERVER_CONNECTION_ID, ConnectionMode.ON, parameters, endpoint, messageVisitor); this.serverConnection.configure(); @@ -114,8 +114,9 @@ public class SocketEndpointTest extends AbstractEndpointTest { } private final class SocketMessageVisitorExtension extends SocketMessageVisitor { - public SocketMessageVisitorExtension() { - // do nothing + + public SocketMessageVisitorExtension(String connectionId) { + super(connectionId); } @Override From 3283e0e0cfd8250670efa5264d0d437e0f9054c7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 15 Aug 2014 15:11:32 +0200 Subject: [PATCH 299/457] [New] added small chat server/client --- .../java/ch/eitchnet/communication/chat/ChatClient.java | 1 + .../ch/eitchnet/communication/chat/ChatMessageVisitor.java | 7 +++++++ .../java/ch/eitchnet/communication/chat/ChatServer.java | 1 + 3 files changed, 9 insertions(+) diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatClient.java b/src/main/java/ch/eitchnet/communication/chat/ChatClient.java index 903c45fef..51b2c4823 100644 --- a/src/main/java/ch/eitchnet/communication/chat/ChatClient.java +++ b/src/main/java/ch/eitchnet/communication/chat/ChatClient.java @@ -37,6 +37,7 @@ public class ChatClient implements ConnectionObserver, ConnectionStateObserver { ConnectionMode mode = ConnectionMode.ON; Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_RETRY, "10000"); //$NON-NLS-1$ parameters.put(SocketEndpointConstants.PARAMETER_USE_TIMEOUT, Boolean.FALSE.toString()); parameters.put(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_ADDRESS, this.host.getHostAddress()); parameters.put(SocketEndpointConstants.PARAMETER_REMOTE_INPUT_PORT, Integer.toString(this.port)); diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java b/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java index 6589bb389..8737289bb 100644 --- a/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java @@ -21,8 +21,15 @@ public class ChatMessageVisitor extends SocketMessageVisitor { @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; diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatServer.java b/src/main/java/ch/eitchnet/communication/chat/ChatServer.java index 3fd82ea84..9a0259890 100644 --- a/src/main/java/ch/eitchnet/communication/chat/ChatServer.java +++ b/src/main/java/ch/eitchnet/communication/chat/ChatServer.java @@ -34,6 +34,7 @@ public class ChatServer implements ConnectionObserver, ConnectionStateObserver { ConnectionMode mode = ConnectionMode.ON; Map parameters = new HashMap<>(); + parameters.put(SocketEndpointConstants.PARAMETER_RETRY, "10000"); //$NON-NLS-1$ parameters.put(SocketEndpointConstants.PARAMETER_USE_TIMEOUT, Boolean.FALSE.toString()); parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT, Integer.toString(this.port)); parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.FALSE.toString()); From a1a2b814086d1bac6dfbe89ae3df84ff0c4f1369 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 15 Aug 2014 15:34:57 +0200 Subject: [PATCH 300/457] [New] added small chat server/client --- .../ch/eitchnet/communication/chat/Chat.java | 47 +++++++++++++++---- .../chat/ChatMessageVisitor.java | 4 +- .../communication/chat/ChatServer.java | 6 ++- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/chat/Chat.java b/src/main/java/ch/eitchnet/communication/chat/Chat.java index 2e53e2a73..5dd90d451 100644 --- a/src/main/java/ch/eitchnet/communication/chat/Chat.java +++ b/src/main/java/ch/eitchnet/communication/chat/Chat.java @@ -1,8 +1,11 @@ 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; @@ -10,25 +13,51 @@ public class Chat { public static void main(String[] args) { - if (args.length < 3) + if (args.length < 4) printIllegalArgsAndExit(args); if (args[0].equals("server")) { //$NON-NLS-1$ - if (args.length != 3) - printIllegalArgsAndExit(args); startServer(args); } else if (args[0].equals("client")) { //$NON-NLS-1$ - if (args.length != 4) - printIllegalArgsAndExit(args); startClient(args); } } private static void startServer(String[] args) { + // server + InetAddress host; + String hostS = args[1]; + try { + host = InetAddress.getByName(hostS); + } catch (UnknownHostException e1) { + System.err.println(MessageFormat.format("Illegal server address: {0}", hostS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + return; + } + boolean isHostAddress = false; + try { + for (Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); interfaces + .hasMoreElements();) { + NetworkInterface iface = interfaces.nextElement(); + for (Enumeration addresses = iface.getInetAddresses(); addresses.hasMoreElements();) { + InetAddress inetAddress = addresses.nextElement(); + if (inetAddress.getHostAddress().equals(host.getHostAddress())) + isHostAddress = true; + } + } + } catch (SocketException e) { + System.err.println("Oops: " + e.getMessage()); //$NON-NLS-1$ + } + + if (!isHostAddress) { + System.err.println(MessageFormat.format("The address {0} is not an address of this host!", hostS)); //$NON-NLS-1$ + printIllegalArgsAndExit(args); + } + // port int port; - String portS = args[1]; + String portS = args[2]; try { port = Integer.parseInt(portS); } catch (NumberFormatException e) { @@ -43,10 +72,10 @@ public class Chat { } // username - String username = args[2]; + String username = args[3]; // start - ChatServer chatServer = new ChatServer(port, username); + ChatServer chatServer = new ChatServer(host, port, username); chatServer.start(); } @@ -97,7 +126,7 @@ public class Chat { } } System.err.println(); - System.err.println("Usage: java ...Chat server "); //$NON-NLS-1$ + System.err.println("Usage: java ...Chat server "); //$NON-NLS-1$ System.err.println("Usage: java ...Chat client "); //$NON-NLS-1$ System.exit(1); } diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java b/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java index 8737289bb..29667480f 100644 --- a/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java @@ -38,8 +38,8 @@ public class ChatMessageVisitor extends SocketMessageVisitor { @Override public void visit(DataInputStream inputStream, DataOutputStream outputStream, IoMessage message) throws Exception { ChatIoMessage chatIoMessage = (ChatIoMessage) message; - outputStream.writeBytes(chatIoMessage.getChatMsg()); - outputStream.writeByte('\n'); + outputStream.writeChars(chatIoMessage.getChatMsg()); + outputStream.writeChar('\n'); outputStream.flush(); } } diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatServer.java b/src/main/java/ch/eitchnet/communication/chat/ChatServer.java index 9a0259890..d94409626 100644 --- a/src/main/java/ch/eitchnet/communication/chat/ChatServer.java +++ b/src/main/java/ch/eitchnet/communication/chat/ChatServer.java @@ -3,6 +3,7 @@ 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; @@ -21,11 +22,13 @@ 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(int port, String username) { + public ChatServer(InetAddress address, int port, String username) { + this.address = address; this.port = port; this.username = username; } @@ -36,6 +39,7 @@ public class ChatServer implements ConnectionObserver, ConnectionStateObserver { Map parameters = new HashMap<>(); parameters.put(SocketEndpointConstants.PARAMETER_RETRY, "10000"); //$NON-NLS-1$ parameters.put(SocketEndpointConstants.PARAMETER_USE_TIMEOUT, Boolean.FALSE.toString()); + parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_ADDRESS, this.address.getHostAddress()); parameters.put(SocketEndpointConstants.PARAMETER_LOCAL_INPUT_PORT, Integer.toString(this.port)); parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.FALSE.toString()); parameters.put(SocketEndpointConstants.PARAMETER_CLOSE_AFTER_SEND, Boolean.FALSE.toString()); From b38c57e424c049fb6d7bdcd446d78d6fc75b7b05 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 17 Aug 2014 14:02:00 +0200 Subject: [PATCH 301/457] [Minor] renamed test classes --- .../xmlpers/test/AbstractPersistenceTest.java | 8 ++--- .../ch/eitchnet/xmlpers/test/FileDaoTest.java | 10 +++--- .../ch/eitchnet/xmlpers/test/LockingTest.java | 8 ++--- .../xmlpers/test/ObjectDaoResourceTest.java | 28 +++++++-------- .../ch/eitchnet/xmlpers/test/RealmTest.java | 16 ++++----- .../xmlpers/test/TransactionResultTest.java | 8 ++--- .../ch/eitchnet/xmlpers/test/XmlTestMain.java | 36 +++++++++---------- ...actory.java => MyModelContextFactory.java} | 12 +++---- ...ceDomParser.java => MyModelDomParser.java} | 18 +++++----- ...Factory.java => MyModelParserFactory.java} | 12 +++---- ...ceSaxParser.java => MyModelSaxParser.java} | 18 +++++----- .../xmlpers/test/model/ModelBuilder.java | 20 +++++------ .../model/{Resource.java => MyModel.java} | 14 ++++---- .../{Parameter.java => MyParameter.java} | 6 ++-- 14 files changed, 107 insertions(+), 107 deletions(-) rename src/test/java/ch/eitchnet/xmlpers/test/impl/{ResourceContextFactory.java => MyModelContextFactory.java} (72%) rename src/test/java/ch/eitchnet/xmlpers/test/impl/{ResourceDomParser.java => MyModelDomParser.java} (85%) rename src/test/java/ch/eitchnet/xmlpers/test/impl/{ResourceParserFactory.java => MyModelParserFactory.java} (74%) rename src/test/java/ch/eitchnet/xmlpers/test/impl/{ResourceSaxParser.java => MyModelSaxParser.java} (84%) rename src/test/java/ch/eitchnet/xmlpers/test/model/{Resource.java => MyModel.java} (86%) rename src/test/java/ch/eitchnet/xmlpers/test/model/{Parameter.java => MyParameter.java} (94%) diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java index eaea56e6a..312a9f19e 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java @@ -27,10 +27,10 @@ import ch.eitchnet.xmlpers.api.PersistenceConstants; import ch.eitchnet.xmlpers.api.PersistenceManager; import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; import ch.eitchnet.xmlpers.test.impl.BookContextFactory; -import ch.eitchnet.xmlpers.test.impl.ResourceContextFactory; +import ch.eitchnet.xmlpers.test.impl.MyModelContextFactory; import ch.eitchnet.xmlpers.test.impl.TestConstants; import ch.eitchnet.xmlpers.test.model.Book; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyModel; public abstract class AbstractPersistenceTest { @@ -67,8 +67,8 @@ public abstract class AbstractPersistenceTest { protected void setup(Properties properties) { properties.setProperty(PersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ this.persistenceManager = PersistenceManagerLoader.load(properties); - this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Resource.class, - TestConstants.TYPE_RES, new ResourceContextFactory()); + this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(MyModel.class, + TestConstants.TYPE_RES, new MyModelContextFactory()); this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Book.class, TestConstants.TYPE_BOOK, new BookContextFactory()); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java index c50e125cc..31f19d4d1 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java @@ -38,7 +38,7 @@ import ch.eitchnet.xmlpers.impl.DefaultPersistenceRealm; import ch.eitchnet.xmlpers.impl.DefaultPersistenceTransaction; import ch.eitchnet.xmlpers.impl.PathBuilder; import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyModel; /** * @author Robert von Burg @@ -90,12 +90,12 @@ public class FileDaoTest extends AbstractPersistenceTest { private void testCrud(PersistenceContextFactoryDelegator ctxFactoryDelegator, FileDao fileDao) { - Resource resource = createResource(); + MyModel resource = createResource(); assertResource(resource); - Class classType = resource.getClass(); - PersistenceContextFactory ctxFactory = ctxFactoryDelegator.getCtxFactory(classType); + Class classType = resource.getClass(); + PersistenceContextFactory ctxFactory = ctxFactoryDelegator.getCtxFactory(classType); ObjectReferenceCache objectRefCache = this.realm.getObjectRefCache(); - PersistenceContext context = ctxFactory.createCtx(objectRefCache, resource); + PersistenceContext context = ctxFactory.createCtx(objectRefCache, resource); context.setObject(resource); fileDao.performCreate(context); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java index 473be20d4..68de2d5a9 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -35,7 +35,7 @@ import ch.eitchnet.xmlpers.api.PersistenceConstants; import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; import ch.eitchnet.xmlpers.objref.LockableObject; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyModel; /** * @author Robert von Burg @@ -117,7 +117,7 @@ public class LockingTest extends AbstractPersistenceTest { // create resource which is to be updated try (PersistenceTransaction tx = this.persistenceManager.openTx()) { - Resource resource = createResource(resourceId); + MyModel resource = createResource(resourceId); tx.getObjectDao().add(resource); } @@ -208,7 +208,7 @@ public class LockingTest extends AbstractPersistenceTest { @Override protected void doWork(PersistenceTransaction tx) { - Resource resource = createResource(this.resourceId); + MyModel resource = createResource(this.resourceId); tx.getObjectDao().add(resource); } } @@ -223,7 +223,7 @@ public class LockingTest extends AbstractPersistenceTest { protected void doWork(PersistenceTransaction tx) { IdOfSubTypeRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, RES_TYPE, this.resourceId); - Resource resource = tx.getObjectDao().queryById(objectRef); + MyModel resource = tx.getObjectDao().queryById(objectRef); assertNotNull(resource); updateResource(resource); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java index 7923a8ff1..72e40c3ae 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java @@ -46,7 +46,7 @@ import ch.eitchnet.xmlpers.objref.ObjectRef; import ch.eitchnet.xmlpers.objref.SubTypeRef; import ch.eitchnet.xmlpers.test.impl.TestConstants; import ch.eitchnet.xmlpers.test.model.ModelBuilder; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyModel; /** * @author Robert von Burg @@ -93,7 +93,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { ObjectDao objectDao; // create new resource - Resource resource = createResource(); + MyModel resource = createResource(); try (PersistenceTransaction tx = freshTx(ioMode);) { objectDao = tx.getObjectDao(); objectDao.add(resource); @@ -157,12 +157,12 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { String type = "testBulk" + ioMode.name(); //$NON-NLS-1$ // create a list of resources - List resources = new ArrayList<>(10); + List resources = new ArrayList<>(10); for (int i = 0; i < 10; i++) { String id = RES_ID + "_" + i; //$NON-NLS-1$ String name = "Bulk Test Object. " + i; //$NON-NLS-1$ - Resource resource = createResource(id, name, type); + MyModel resource = createResource(id, name, type); resources.add(resource); } @@ -204,14 +204,14 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { // create a resource try (PersistenceTransaction tx = this.persistenceManager.openTx()) { - Resource resource = createResource(id, name, subType); + MyModel resource = createResource(id, name, subType); tx.getObjectDao().add(resource); } // read by id try (PersistenceTransaction tx = this.persistenceManager.openTx()) { ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(classType, subType, id); - Resource resource = tx.getObjectDao().queryById(objectRef); + MyModel resource = tx.getObjectDao().queryById(objectRef); assertNotNull("Expected to read resource by ID", resource); //$NON-NLS-1$ } @@ -224,7 +224,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { // fail to read by id try (PersistenceTransaction tx = this.persistenceManager.openTx()) { ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(classType, subType, id); - Resource resource = tx.getObjectDao().queryById(objectRef); + MyModel resource = tx.getObjectDao().queryById(objectRef); assertNull("Expected that resource was deleted by ID, thus can not be read anymore", resource); //$NON-NLS-1$ } } @@ -239,7 +239,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { // update try (PersistenceTransaction tx = this.persistenceManager.openTx()) { - Resource resource = createResource(); + MyModel resource = createResource(); tx.getObjectDao().update(resource); } } @@ -254,7 +254,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { // delete try (PersistenceTransaction tx = this.persistenceManager.openTx()) { - Resource resource = createResource(); + MyModel resource = createResource(); tx.getObjectDao().remove(resource); } } @@ -270,7 +270,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { try (PersistenceTransaction tx = this.persistenceManager.openTx()) { String id = "shouldAllowAllOperationsInSameTx_create"; //$NON-NLS-1$ - Resource resource = createResource(id, name, subType); + MyModel resource = createResource(id, name, subType); tx.getObjectDao().add(resource); } @@ -279,7 +279,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { try (PersistenceTransaction tx = this.persistenceManager.openTx()) { String id = "shouldAllowAllOperationsInSameTx_create_modify"; //$NON-NLS-1$ - Resource resource = createResource(id, name, subType); + MyModel resource = createResource(id, name, subType); tx.getObjectDao().add(resource); tx.getObjectDao().update(resource); @@ -289,7 +289,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { try (PersistenceTransaction tx = this.persistenceManager.openTx()) { String id = "shouldAllowAllOperationsInSameTx_create_delete"; //$NON-NLS-1$ - Resource resource = createResource(id, name, subType); + MyModel resource = createResource(id, name, subType); tx.getObjectDao().add(resource); tx.getObjectDao().remove(resource); @@ -299,7 +299,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { try (PersistenceTransaction tx = this.persistenceManager.openTx()) { String id = "shouldAllowAllOperationsInSameTx_create_modify_delete"; //$NON-NLS-1$ - Resource resource = createResource(id, name, subType); + MyModel resource = createResource(id, name, subType); tx.getObjectDao().add(resource); tx.getObjectDao().update(resource); @@ -310,7 +310,7 @@ public class ObjectDaoResourceTest extends AbstractPersistenceTest { // prepare for read/modify try (PersistenceTransaction tx = this.persistenceManager.openTx()) { - Resource resource = createResource(id, name, subType); + MyModel resource = createResource(id, name, subType); tx.getObjectDao().add(resource); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java b/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java index 534e18183..d3f91e337 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java @@ -30,7 +30,7 @@ import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.objref.ObjectRef; import ch.eitchnet.xmlpers.test.impl.TestConstants; import ch.eitchnet.xmlpers.test.model.ModelBuilder; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyModel; @SuppressWarnings("nls") public class RealmTest extends AbstractPersistenceTest { @@ -62,21 +62,21 @@ public class RealmTest extends AbstractPersistenceTest { // create in first realm try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { - Resource resource1 = ModelBuilder.createResource(id, name, type); + MyModel resource1 = ModelBuilder.createResource(id, name, type); txRealm1.getObjectDao().add(resource1); } // find in first realm try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { ObjectRef objectRef = txRealm1.getObjectRefCache().getIdOfSubTypeRef(objType, type, id); - Resource resource = txRealm1.getObjectDao().queryById(objectRef); + MyModel resource = txRealm1.getObjectDao().queryById(objectRef); assertNotNull("Resource was not found in first realm!", resource); } // fail to find in second realm try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { ObjectRef objectRef = txRealm2.getObjectRefCache().getIdOfSubTypeRef(objType, type, id); - Resource resource = txRealm2.getObjectDao().queryById(objectRef); + MyModel resource = txRealm2.getObjectDao().queryById(objectRef); assertNull("Resource was not created in second realm, thus not expected to be found there!", resource); } } @@ -92,13 +92,13 @@ public class RealmTest extends AbstractPersistenceTest { // create in first realm try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { - Resource resource1 = ModelBuilder.createResource(id, name, subType); + MyModel resource1 = ModelBuilder.createResource(id, name, subType); txRealm1.getObjectDao().add(resource1); } // create in second realm try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { - Resource resource1 = ModelBuilder.createResource(id, name, subType); + MyModel resource1 = ModelBuilder.createResource(id, name, subType); txRealm2.getObjectDao().add(resource1); } @@ -111,14 +111,14 @@ public class RealmTest extends AbstractPersistenceTest { // fail to find in second realm try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { ObjectRef objectRef = txRealm2.getObjectRefCache().getIdOfSubTypeRef(objType, subType, id); - Resource resource = txRealm2.getObjectDao().queryById(objectRef); + MyModel resource = txRealm2.getObjectDao().queryById(objectRef); assertNull("Resource was not deleted from second realm, thus not expected to be found there!", resource); } // find in first realm try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { ObjectRef objectRef = txRealm1.getObjectRefCache().getIdOfSubTypeRef(objType, subType, id); - Resource resource = txRealm1.getObjectDao().queryById(objectRef); + MyModel resource = txRealm1.getObjectDao().queryById(objectRef); assertNotNull("Resource was not found in first realm!", resource); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java b/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java index 576cc1486..8aa5e5a53 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java @@ -38,7 +38,7 @@ import ch.eitchnet.xmlpers.api.PersistenceConstants; import ch.eitchnet.xmlpers.api.PersistenceTransaction; import ch.eitchnet.xmlpers.api.TransactionResult; import ch.eitchnet.xmlpers.test.model.Book; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyModel; /** * @author Robert von Burg @@ -95,14 +95,14 @@ public class TransactionResultTest extends AbstractPersistenceTest { private void performChanges(TransactionResult txResult) { // create a list of resources - List resources = new ArrayList<>(10); + List resources = new ArrayList<>(10); int i = 0; for (; i < 10; i++) { String id = RES_ID + "_" + i; //$NON-NLS-1$ String name = "Tx Result Test 1 Object. " + i; //$NON-NLS-1$ String type = "testTxResult1"; //$NON-NLS-1$ - Resource resource = createResource(id, name, type); + MyModel resource = createResource(id, name, type); resources.add(resource); } for (; i < 20; i++) { @@ -110,7 +110,7 @@ public class TransactionResultTest extends AbstractPersistenceTest { String name = "Tx Result Test 2 Object. " + i; //$NON-NLS-1$ String type = "testTxResult2"; //$NON-NLS-1$ - Resource resource = createResource(id, name, type); + MyModel resource = createResource(id, name, type); resources.add(resource); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java index 8ea42fa45..f05e83215 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java @@ -47,8 +47,8 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; import ch.eitchnet.utils.helper.XmlHelper; import ch.eitchnet.xmlpers.test.model.ModelBuilder; -import ch.eitchnet.xmlpers.test.model.Parameter; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyParameter; +import ch.eitchnet.xmlpers.test.model.MyModel; /** * @author Robert von Burg @@ -59,7 +59,7 @@ public class XmlTestMain { private static final Logger logger = LoggerFactory.getLogger(XmlTestMain.class); - private static Resource res; + private static MyModel res; public static void main(String[] args) throws Exception { @@ -70,7 +70,7 @@ public class XmlTestMain { writeSax(res); writeDom(res); - List resoures; + List resoures; resoures = readDom(); logger.info("Parsed Resources:\n" + resoures); resoures = readSax(); @@ -81,7 +81,7 @@ public class XmlTestMain { * @return * */ - private static List readDom() throws Exception { + private static List readDom() throws Exception { File file = new File("target/res_dom.xml"); DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); @@ -89,7 +89,7 @@ public class XmlTestMain { Document document = docBuilder.parse(file); Element rootElement = document.getDocumentElement(); - List resources = new ArrayList(); + List resources = new ArrayList(); NodeList resElements = rootElement.getElementsByTagName("Resource"); for (int i = 0; i < resElements.getLength(); i++) { @@ -98,7 +98,7 @@ public class XmlTestMain { String name = resElement.getAttribute("name"); String type = resElement.getAttribute("type"); - Resource res = new Resource(); + MyModel res = new MyModel(); res.setId(id); res.setName(name); res.setType(type); @@ -114,7 +114,7 @@ public class XmlTestMain { return resources; } - private static void parseParameters(Resource res, NodeList paramElements) { + private static void parseParameters(MyModel res, NodeList paramElements) { for (int i = 0; i < paramElements.getLength(); i++) { Element paramElement = (Element) paramElements.item(i); String id = paramElement.getAttribute("id"); @@ -122,7 +122,7 @@ public class XmlTestMain { String type = paramElement.getAttribute("type"); String value = paramElement.getAttribute("value"); - Parameter param = new Parameter(); + MyParameter param = new MyParameter(); param.setId(id); param.setName(name); param.setType(type); @@ -136,10 +136,10 @@ public class XmlTestMain { * @return * */ - private static List readSax() throws Exception { + private static List readSax() throws Exception { - final List resources = new ArrayList<>(); - final Resource[] currentRes = new Resource[1]; + final List resources = new ArrayList<>(); + final MyModel[] currentRes = new MyModel[1]; DefaultHandler xmlHandler = new DefaultHandler() { @@ -149,7 +149,7 @@ public class XmlTestMain { switch (qName) { case "Resource": - Resource res = new Resource(); + MyModel res = new MyModel(); res.setId(attributes.getValue("id")); res.setName(attributes.getValue("name")); res.setType(attributes.getValue("type")); @@ -157,7 +157,7 @@ public class XmlTestMain { resources.add(res); break; case "Parameter": - Parameter param = new Parameter(); + MyParameter param = new MyParameter(); param.setId(attributes.getValue("id")); param.setName(attributes.getValue("name")); param.setType(attributes.getValue("type")); @@ -182,7 +182,7 @@ public class XmlTestMain { return resources; } - private static void writeDom(Resource res) throws Exception { + private static void writeDom(MyModel res) throws Exception { DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); @@ -194,7 +194,7 @@ public class XmlTestMain { resElement.setAttribute("type", res.getType()); for (String paramId : res.getParameterKeySet()) { - Parameter param = res.getParameterBy(paramId); + MyParameter param = res.getParameterBy(paramId); Element paramElement = doc.createElement("Parameter"); paramElement.setAttribute("id", param.getId()); paramElement.setAttribute("name", param.getName()); @@ -250,7 +250,7 @@ public class XmlTestMain { } } - private static void writeSax(Resource res) throws Exception { + private static void writeSax(MyModel res) throws Exception { XMLOutputFactory factory = XMLOutputFactory.newInstance(); File file = new File("target/res_sax.xml"); @@ -268,7 +268,7 @@ public class XmlTestMain { writer.writeAttribute("type", res.getType()); for (String paramId : res.getParameterKeySet()) { - Parameter param = res.getParameterBy(paramId); + MyParameter param = res.getParameterBy(paramId); writer.writeEmptyElement("Parameter"); writer.writeAttribute("id", param.getId()); writer.writeAttribute("name", param.getName()); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java similarity index 72% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java index 03d67d7fc..218c82922 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceContextFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java @@ -20,20 +20,20 @@ import ch.eitchnet.xmlpers.api.PersistenceContextFactory; import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; import ch.eitchnet.xmlpers.objref.ObjectRef; import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyModel; -public class ResourceContextFactory implements PersistenceContextFactory { +public class MyModelContextFactory implements PersistenceContextFactory { @Override - public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, Resource t) { + public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, MyModel t) { IdOfSubTypeRef objectRef = objectRefCache.getIdOfSubTypeRef(TestConstants.TYPE_RES, t.getType(), t.getId()); return createCtx(objectRef); } @Override - public PersistenceContext createCtx(ObjectRef objectRef) { - PersistenceContext ctx = new PersistenceContext<>(objectRef); - ctx.setParserFactory(new ResourceParserFactory()); + public PersistenceContext createCtx(ObjectRef objectRef) { + PersistenceContext ctx = new PersistenceContext<>(objectRef); + ctx.setParserFactory(new MyModelParserFactory()); return ctx; } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java similarity index 85% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java index f9b4ecea4..f7ede8f27 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java @@ -23,21 +23,21 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.test.model.Parameter; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyParameter; +import ch.eitchnet.xmlpers.test.model.MyModel; import ch.eitchnet.xmlpers.util.DomUtil; -public class ResourceDomParser implements DomParser { +public class MyModelDomParser implements DomParser { - private Resource resource; + private MyModel resource; @Override - public Resource getObject() { + public MyModel getObject() { return this.resource; } @Override - public void setObject(Resource resource) { + public void setObject(MyModel resource) { this.resource = resource; } @@ -56,7 +56,7 @@ public class ResourceDomParser implements DomParser { element.setAttribute("type", this.resource.getType()); for (String paramId : this.resource.getParameterKeySet()) { - Parameter param = this.resource.getParameterBy(paramId); + MyParameter param = this.resource.getParameterBy(paramId); Element paramElement = document.createElement("Parameter"); element.appendChild(paramElement); @@ -79,7 +79,7 @@ public class ResourceDomParser implements DomParser { String name = rootElement.getAttribute("name"); String type = rootElement.getAttribute("type"); - Resource resource = new Resource(id, name, type); + MyModel resource = new MyModel(id, name, type); NodeList children = rootElement.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { @@ -93,7 +93,7 @@ public class ResourceDomParser implements DomParser { String paramType = paramElement.getAttribute("type"); String paramValue = paramElement.getAttribute("value"); - Parameter param = new Parameter(paramId, paramName, paramType, paramValue); + MyParameter param = new MyParameter(paramId, paramName, paramType, paramValue); resource.addParameter(param); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java similarity index 74% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java index 72787b11a..0760861fb 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceParserFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java @@ -18,17 +18,17 @@ package ch.eitchnet.xmlpers.test.impl; import ch.eitchnet.xmlpers.api.DomParser; import ch.eitchnet.xmlpers.api.ParserFactory; import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyModel; -public class ResourceParserFactory implements ParserFactory { +public class MyModelParserFactory implements ParserFactory { @Override - public DomParser getDomParser() { - return new ResourceDomParser(); + public DomParser getDomParser() { + return new MyModelDomParser(); } @Override - public SaxParser getSaxParser() { - return new ResourceSaxParser(); + public SaxParser getSaxParser() { + return new MyModelSaxParser(); } } \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java similarity index 84% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java rename to src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java index 7a87057f7..1e8efcc2b 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/ResourceSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java @@ -23,20 +23,20 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.SaxParser; import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; -import ch.eitchnet.xmlpers.test.model.Parameter; -import ch.eitchnet.xmlpers.test.model.Resource; +import ch.eitchnet.xmlpers.test.model.MyParameter; +import ch.eitchnet.xmlpers.test.model.MyModel; -class ResourceSaxParser extends DefaultHandler implements SaxParser { +class MyModelSaxParser extends DefaultHandler implements SaxParser { - private Resource resource; + private MyModel resource; @Override - public Resource getObject() { + public MyModel getObject() { return this.resource; } @Override - public void setObject(Resource object) { + public void setObject(MyModel object) { this.resource = object; } @@ -54,7 +54,7 @@ class ResourceSaxParser extends DefaultHandler implements SaxParser { writer.writeAttribute("name", this.resource.getName()); writer.writeAttribute("type", this.resource.getType()); for (String paramId : this.resource.getParameterKeySet()) { - Parameter param = this.resource.getParameterBy(paramId); + MyParameter param = this.resource.getParameterBy(paramId); writer.writeEmptyElement("Parameter"); writer.writeAttribute("id", param.getId()); writer.writeAttribute("name", param.getName()); @@ -72,7 +72,7 @@ class ResourceSaxParser extends DefaultHandler implements SaxParser { String id = attributes.getValue("id"); String name = attributes.getValue("name"); String type = attributes.getValue("type"); - Resource resource = new Resource(id, name, type); + MyModel resource = new MyModel(id, name, type); this.resource = resource; break; case "Parameter": @@ -80,7 +80,7 @@ class ResourceSaxParser extends DefaultHandler implements SaxParser { name = attributes.getValue("name"); type = attributes.getValue("type"); String value = attributes.getValue("value"); - Parameter param = new Parameter(id, name, type, value); + MyParameter param = new MyParameter(id, name, type, value); this.resource.addParameter(param); break; default: diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java index 8d9576b40..229382476 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -43,22 +43,22 @@ public class ModelBuilder { public static final String BOOK_PRESS_2 = "Another press"; public static final double BOOK_PRICE = 45.55D; - public static Resource createResource() { + public static MyModel createResource() { return createResource(RES_ID, RES_NAME, RES_TYPE); } - public static Resource createResource(String id) { + public static MyModel createResource(String id) { return createResource(id, RES_NAME, RES_TYPE); } - public static Resource createResource(String id, String name, String type) { - Resource resource = new Resource(id, name, type); - Parameter param = new Parameter(PARAM_ID, PARAM_NAME, PARAM_TYPE, PARAM_VALUE_1); + public static MyModel createResource(String id, String name, String type) { + MyModel resource = new MyModel(id, name, type); + MyParameter param = new MyParameter(PARAM_ID, PARAM_NAME, PARAM_TYPE, PARAM_VALUE_1); resource.addParameter(param); return resource; } - public static void updateResource(Resource resource) { + public static void updateResource(MyModel resource) { resource.setName(RES_NAME_MODIFIED); resource.getParameterBy(PARAM_ID).setValue(PARAM_VALUE_2); } @@ -95,12 +95,12 @@ public class ModelBuilder { Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); } - public static void assertResource(Resource resource) { + public static void assertResource(MyModel resource) { Assert.assertNotNull(resource); Assert.assertEquals(RES_ID, resource.getId()); Assert.assertEquals(RES_NAME, resource.getName()); Assert.assertEquals(RES_TYPE, resource.getType()); - Parameter param = resource.getParameterBy(PARAM_ID); + MyParameter param = resource.getParameterBy(PARAM_ID); Assert.assertNotNull(param); Assert.assertEquals(PARAM_ID, param.getId()); Assert.assertEquals(PARAM_NAME, param.getName()); @@ -108,12 +108,12 @@ public class ModelBuilder { Assert.assertEquals(PARAM_VALUE_1, param.getValue()); } - public static void assertResourceUpdated(Resource resource) { + public static void assertResourceUpdated(MyModel resource) { Assert.assertNotNull(resource); Assert.assertEquals(RES_ID, resource.getId()); Assert.assertEquals(RES_NAME_MODIFIED, resource.getName()); Assert.assertEquals(RES_TYPE, resource.getType()); - Parameter param = resource.getParameterBy(PARAM_ID); + MyParameter param = resource.getParameterBy(PARAM_ID); Assert.assertNotNull(param); Assert.assertEquals(PARAM_ID, param.getId()); Assert.assertEquals(PARAM_NAME, param.getName()); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java b/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java similarity index 86% rename from src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java rename to src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java index a9dd1cca7..f61a0fa62 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/Resource.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java @@ -20,17 +20,17 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -public class Resource { +public class MyModel { private String id; private String name; private String type; - private Map parameters = new HashMap(); + private Map parameters = new HashMap(); /** * */ - public Resource() { + public MyModel() { // empty constructor } @@ -39,7 +39,7 @@ public class Resource { * @param name * @param type */ - public Resource(String id, String name, String type) { + public MyModel(String id, String name, String type) { super(); this.id = id; this.name = name; @@ -57,7 +57,7 @@ public class Resource { builder.append(", type="); builder.append(this.type); builder.append(", parameters="); - for (Entry param : this.parameters.entrySet()) { + for (Entry param : this.parameters.entrySet()) { builder.append("\n"); builder.append(" " + param.getKey() + " = " + param.getValue()); } @@ -107,7 +107,7 @@ public class Resource { this.type = type; } - public void addParameter(Parameter parameter) { + public void addParameter(MyParameter parameter) { this.parameters.put(parameter.getId(), parameter); } @@ -115,7 +115,7 @@ public class Resource { return this.parameters.keySet(); } - public Parameter getParameterBy(String id) { + public MyParameter getParameterBy(String id) { return this.parameters.get(id); } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java b/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java similarity index 94% rename from src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java rename to src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java index d3b422ec0..d4ca1f189 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/Parameter.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java @@ -15,7 +15,7 @@ */ package ch.eitchnet.xmlpers.test.model; -public class Parameter { +public class MyParameter { private String id; private String name; @@ -25,7 +25,7 @@ public class Parameter { /** * */ - public Parameter() { + public MyParameter() { // empty constructor } @@ -35,7 +35,7 @@ public class Parameter { * @param type * @param value */ - public Parameter(String id, String name, String type, String value) { + public MyParameter(String id, String name, String type, String value) { super(); this.id = id; this.name = name; From 34630585a15592f3922bbcc13ff913cfda4fff1c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Aug 2014 12:51:43 +0200 Subject: [PATCH 302/457] [New] Added a DateRange class --- .../eitchnet/utils/collections/DateRange.java | 118 +++++++++++++++++ .../utils/collections/DateRangeTest.java | 121 ++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/collections/DateRange.java create mode 100644 src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java diff --git a/src/main/java/ch/eitchnet/utils/collections/DateRange.java b/src/main/java/ch/eitchnet/utils/collections/DateRange.java new file mode 100644 index 000000000..ac1dc5cf2 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/collections/DateRange.java @@ -0,0 +1,118 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.collections; + +import java.util.Date; + +import ch.eitchnet.utils.dbc.DBC; + +/** + * @author Robert von Burg + */ +public class DateRange { + + private boolean fromInclusive; + private boolean toInclusive; + private Date fromDate; + private Date toDate; + + public DateRange from(Date from, boolean inclusive) { + this.fromDate = from; + this.fromInclusive = inclusive; + validate(); + return this; + } + + public DateRange to(Date to, boolean inclusive) { + this.toDate = to; + this.toInclusive = inclusive; + validate(); + return this; + } + + private void validate() { + if (toDate != null && fromDate != null) + DBC.INTERIM.assertTrue("From must be before to!", toDate.compareTo(fromDate) >= 0); + } + + /** + * @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; + } + + public boolean contains(Date date) { + DBC.PRE.assertNotNull("Date must be given!", date); + 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 (toInclusive) + toContains = compare >= 0; + else + toContains = compare > 0; + } + + if (this.fromDate != null) { + int compare = this.fromDate.compareTo(date); + if (fromInclusive) + fromContains = compare <= 0; + else + fromContains = compare < 0; + } + return toContains && fromContains; + } +} diff --git a/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java b/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java new file mode 100644 index 000000000..e8b553f6b --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.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)}. + */ + @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)}. + */ + @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() { + 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#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)); + } +} From 08a92abde7f5a36b263cb70778bad88292b6396e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Aug 2014 12:52:05 +0200 Subject: [PATCH 303/457] [New] Added StringHelper.getUniqueIdLong --- .../java/ch/eitchnet/utils/helper/StringHelper.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 2d90238a6..ee2755922 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -641,12 +641,21 @@ public class StringHelper { * @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 Long.toString(uniqueId); + return uniqueId; } } From b47d3e3dcd3c445316264a8925cb3948ea9a6392 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Aug 2014 18:54:10 +0200 Subject: [PATCH 304/457] [Minor] firstname and lastname are not required for SYSTEM users --- .../ch/eitchnet/privilege/model/UserRep.java | 14 ++++++++------ .../privilege/model/internal/User.java | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 26bdc141e..5f50594e2 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -90,15 +90,17 @@ public class UserRep implements Serializable { if (StringHelper.isEmpty(this.username)) throw new PrivilegeException("username is null or empty"); //$NON-NLS-1$ - if (StringHelper.isEmpty(this.firstname)) - throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ - - if (StringHelper.isEmpty(this.surname)) - throw new PrivilegeException("surname is null or empty"); //$NON-NLS-1$ - if (this.userState == null) throw new PrivilegeException("userState is null"); //$NON-NLS-1$ + if (this.userState != UserState.SYSTEM) { + if (StringHelper.isEmpty(this.firstname)) + throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ + + if (StringHelper.isEmpty(this.surname)) + throw new PrivilegeException("surname is null or empty"); //$NON-NLS-1$ + } + if (this.roles == null) throw new PrivilegeException("roles is null"); //$NON-NLS-1$ } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 0e260ccaf..8e710e235 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -84,17 +84,19 @@ public final class User { if (StringHelper.isEmpty(userId)) { throw new PrivilegeException("No UserId defined!"); //$NON-NLS-1$ } + if (userState == null) { + throw new PrivilegeException("No userState defined!"); //$NON-NLS-1$ + } if (StringHelper.isEmpty(username)) { throw new PrivilegeException("No username defined!"); //$NON-NLS-1$ } - if (StringHelper.isEmpty(firstname)) { - throw new PrivilegeException("No firstname defined!"); //$NON-NLS-1$ - } - if (StringHelper.isEmpty(surname)) { - throw new PrivilegeException("No surname defined!"); //$NON-NLS-1$ - } - if (userState == null) { - throw new PrivilegeException("No userState defined!"); //$NON-NLS-1$ + if (userState != UserState.SYSTEM) { + if (StringHelper.isEmpty(surname)) { + throw new PrivilegeException("No surname defined!"); //$NON-NLS-1$ + } + if (StringHelper.isEmpty(firstname)) { + throw new PrivilegeException("No firstname defined!"); //$NON-NLS-1$ + } } // password may be null, meaning not able to login From d2faed1d2eabaaba0720e00f8c457ce9b90d44ed Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Aug 2014 20:36:18 +0200 Subject: [PATCH 305/457] [Major] added firstname and lastname to certificate Also renamed model attribute from surname to lastname --- config/PrivilegeModel.xml | 6 +-- .../handler/DefaultPrivilegeHandler.java | 40 +++++++++---------- .../privilege/handler/PrivilegeHandler.java | 8 ++-- .../privilege/helper/XmlConstants.java | 4 +- .../eitchnet/privilege/model/Certificate.java | 37 ++++++++++++++++- .../ch/eitchnet/privilege/model/UserRep.java | 32 +++++++-------- .../privilege/model/internal/User.java | 30 +++++++------- .../xml/PrivilegeModelDomWriter.java | 8 ++-- .../xml/PrivilegeModelSaxReader.java | 10 ++--- src/main/resources/PrivilegeModel.xsd | 2 +- .../ch/eitchnet/privilege/test/XmlTest.java | 6 +-- 11 files changed, 108 insertions(+), 75 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 488ae379a..1ab9708d6 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -5,7 +5,7 @@ Application - Administrator + Administrator ENABLED en_GB @@ -20,7 +20,7 @@ System User - Administrator + Administrator SYSTEM en_GB @@ -30,7 +30,7 @@ System User - Administrator + Administrator SYSTEM en_GB diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index f48a10964..e3ec04f6f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -129,7 +129,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String selUserId = selectorRep.getUserId(); String selUsername = selectorRep.getUsername(); String selFirstname = selectorRep.getFirstname(); - String selSurname = selectorRep.getSurname(); + String selLastname = selectorRep.getLastname(); UserState selUserState = selectorRep.getUserState(); Locale selLocale = selectorRep.getLocale(); Set selRoles = selectorRep.getRoles(); @@ -143,7 +143,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { boolean userIdSelected; boolean usernameSelected; boolean firstnameSelected; - boolean surnameSelected; + boolean lastnameSelected; boolean userStateSelected; boolean localeSelected; boolean roleSelected; @@ -173,13 +173,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { else firstnameSelected = false; - // surname - if (selSurname == null) - surnameSelected = true; - else if (selSurname.equals(user.getSurname())) - surnameSelected = true; + // lastname + if (selLastname == null) + lastnameSelected = true; + else if (selLastname.equals(user.getLastname())) + lastnameSelected = true; else - surnameSelected = false; + lastnameSelected = false; // user state if (selUserState == null) @@ -203,7 +203,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // properties propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties()); - boolean selected = userIdSelected && usernameSelected && firstnameSelected && surnameSelected + boolean selected = userIdSelected && usernameSelected && firstnameSelected && lastnameSelected && userStateSelected && localeSelected && roleSelected && propertySelected; if (selected) @@ -303,7 +303,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), - userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), + userRep.getLastname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), userRep.getProperties()); // delegate to persistence handler @@ -387,7 +387,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { newRoles.add(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); + user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -467,7 +467,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); + user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -505,14 +505,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); + user.getLastname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); } @Override - public void setUserName(Certificate certificate, String username, String firstname, String surname) { + public void setUserName(Certificate certificate, String username, String firstname, String lastname) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -524,7 +524,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, lastname, user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler @@ -569,7 +569,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); + user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -598,7 +598,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), state, user.getRoles(), user.getLocale(), user.getProperties()); + user.getLastname(), state, user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -673,8 +673,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, System.currentTimeMillis(), username, authToken, user.getLocale(), - new HashMap(user.getProperties())); + certificate = new Certificate(sessionId, System.currentTimeMillis(), username, user.getFirstname(), + user.getLastname(), authToken, user.getLocale(), new HashMap(user.getProperties())); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); @@ -1055,7 +1055,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - authToken, user.getLocale(), new HashMap(user.getProperties())); + null, null, authToken, user.getLocale(), new HashMap(user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index fafd54bdc..d527fd171 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -256,7 +256,7 @@ public interface PrivilegeHandler { throws AccessDeniedException, PrivilegeException; /** - * Changes the name of the user. This changes the first name and the surname. If either value is null, then that + * Changes the name of the user. This changes the first name and the lastname. If either value is null, then that * value is not changed * * @param certificate @@ -265,15 +265,15 @@ public interface PrivilegeHandler { * the username of the {@link User} for which the name is to be changed * @param firstname * the new first name - * @param surname - * the new surname + * @param lastname + * the new lastname * * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserName(Certificate certificate, String username, String firstname, String surname) + public void setUserName(Certificate certificate, String username, String firstname, String lastname) throws AccessDeniedException, PrivilegeException; /** diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index 079fe433d..d4a0c1e9d 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -134,9 +134,9 @@ public class XmlConstants { public static final String XML_FIRSTNAME = "Firstname"; /** - * XML_SURNAME = "Surname" : + * XML_LASTNAME = "Lastname" : */ - public static final String XML_SURNAME = "Surname"; + public static final String XML_LASTNAME = "Lastname"; /** * XML_STATE = "State" : diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index b7e38ac5b..9cb3d9ada 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -40,6 +40,8 @@ public final class Certificate implements Serializable { private final String sessionId; private final long loginTime; private final String username; + private final String firstname; + private final String lastname; private final String authToken; private Locale locale; @@ -59,6 +61,10 @@ public final class Certificate implements Serializable { * the users session id * @param username * the users login name + * @param firstname + * the users first name + * @param lastname + * the users last name * @param authToken * the authentication token defining the users unique session and is a private field of this certificate. * @param locale @@ -67,8 +73,8 @@ public final class Certificate implements Serializable { * a {@link Map} containing string value pairs of properties for the logged in user. These properties can * be edited and can be used for the user to change settings of this session */ - public Certificate(String sessionId, long loginTime, String username, String authToken, Locale locale, - Map propertyMap) { + public Certificate(String sessionId, long loginTime, String username, String firstname, String lastname, + String authToken, Locale locale, Map propertyMap) { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { @@ -84,6 +90,8 @@ public final class Certificate implements Serializable { this.sessionId = sessionId; this.loginTime = loginTime; this.username = username; + this.firstname = firstname; + this.lastname = lastname; this.authToken = authToken; // if no locale is given, set default @@ -147,6 +155,20 @@ public final class Certificate implements Serializable { return this.username; } + /** + * @return the firstname + */ + public String getFirstname() { + return this.firstname; + } + + /** + * @return the lastname + */ + public String getLastname() { + return this.lastname; + } + /** * @return the loginTime */ @@ -176,6 +198,17 @@ public final class Certificate implements Serializable { builder.append(this.sessionId); builder.append(", username="); builder.append(this.username); + + if (StringHelper.isNotEmpty(this.firstname)) { + builder.append(", firstname="); + builder.append(this.firstname); + } + + if (StringHelper.isNotEmpty(this.lastname)) { + builder.append(", lastname="); + builder.append(this.lastname); + } + builder.append(", locale="); builder.append(this.locale); builder.append("]"); diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 5f50594e2..3ffa0738d 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -39,7 +39,7 @@ public class UserRep implements Serializable { private final String userId; private String username; private String firstname; - private String surname; + private String lastname; private UserState userState; private Set roles; private Locale locale; @@ -54,8 +54,8 @@ public class UserRep implements Serializable { * the user's login name * @param firstname * the user's first name - * @param surname - * the user's surname + * @param lastname + * the user's last name * @param userState * the user's {@link UserState} * @param roles @@ -65,12 +65,12 @@ public class UserRep implements Serializable { * @param propertyMap * a {@link Map} containing string value pairs of properties for this user */ - public UserRep(String userId, String username, String firstname, String surname, UserState userState, + public UserRep(String userId, String username, String firstname, String lastname, UserState userState, Set roles, Locale locale, Map propertyMap) { this.userId = userId; this.username = username; this.firstname = firstname; - this.surname = surname; + this.lastname = lastname; this.userState = userState; this.roles = roles; this.locale = locale; @@ -97,8 +97,8 @@ public class UserRep implements Serializable { if (StringHelper.isEmpty(this.firstname)) throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ - if (StringHelper.isEmpty(this.surname)) - throw new PrivilegeException("surname is null or empty"); //$NON-NLS-1$ + if (StringHelper.isEmpty(this.lastname)) + throw new PrivilegeException("lastname is null or empty"); //$NON-NLS-1$ } if (this.roles == null) @@ -143,18 +143,18 @@ public class UserRep implements Serializable { } /** - * @return the surname + * @return the lastname */ - public String getSurname() { - return this.surname; + public String getLastname() { + return this.lastname; } /** - * @param surname - * the surname to set + * @param lastname + * the lastname to set */ - public void setSurname(String surname) { - this.surname = surname; + public void setLastname(String lastname) { + this.lastname = lastname; } /** @@ -259,8 +259,8 @@ public class UserRep implements Serializable { builder.append(this.username); builder.append(", firstname="); builder.append(this.firstname); - builder.append(", surname="); - builder.append(this.surname); + builder.append(", lastname="); + builder.append(this.lastname); builder.append(", userState="); builder.append(this.userState); builder.append(", locale="); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 8e710e235..f54ccb630 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -29,7 +29,7 @@ import ch.eitchnet.utils.helper.StringHelper; /** * This class defines the actual login information for a given user which can be granted privileges. Every user is - * granted a set of {@link Role}s and has a {@link UserState} including detail information like first name and surname + * granted a set of {@link Role}s and has a {@link UserState} including detail information like first name and lastname * *

      * Note: This is an internal object which is not to be serialized or passed to clients, {@link UserRep}s are used for @@ -46,7 +46,7 @@ public final class User { private final String password; private final String firstname; - private final String surname; + private final String lastname; private final UserState userState; @@ -67,8 +67,8 @@ public final class User { * the user's password (hashed) * @param firstname * the user's first name - * @param surname - * the user's surname + * @param lastname + * the user's lastname * @param userState * the user's {@link UserState} * @param roles @@ -78,7 +78,7 @@ public final class User { * @param propertyMap * a {@link Map} containing string value pairs of properties for this user */ - public User(String userId, String username, String password, String firstname, String surname, UserState userState, + public User(String userId, String username, String password, String firstname, String lastname, UserState userState, Set roles, Locale locale, Map propertyMap) { if (StringHelper.isEmpty(userId)) { @@ -91,8 +91,8 @@ public final class User { throw new PrivilegeException("No username defined!"); //$NON-NLS-1$ } if (userState != UserState.SYSTEM) { - if (StringHelper.isEmpty(surname)) { - throw new PrivilegeException("No surname defined!"); //$NON-NLS-1$ + if (StringHelper.isEmpty(lastname)) { + throw new PrivilegeException("No lastname defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(firstname)) { throw new PrivilegeException("No firstname defined!"); //$NON-NLS-1$ @@ -111,7 +111,7 @@ public final class User { this.userState = userState; this.firstname = firstname; - this.surname = surname; + this.lastname = lastname; if (roles == null) this.roles = Collections.emptySet(); @@ -153,17 +153,17 @@ public final class User { } /** - * @return the firstname + * @return the first name */ public String getFirstname() { return this.firstname; } /** - * @return the surname + * @return the last name */ - public String getSurname() { - return this.surname; + public String getLastname() { + return this.lastname; } /** @@ -233,7 +233,7 @@ public final class User { * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients */ public UserRep asUserRep() { - return new UserRep(this.userId, this.username, this.firstname, this.surname, this.userState, + return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, new HashSet(this.roles), this.locale, new HashMap(this.propertyMap)); } @@ -252,8 +252,8 @@ public final class User { builder.append(this.username); builder.append(", firstname="); builder.append(this.firstname); - builder.append(", surname="); - builder.append(this.surname); + builder.append(", lastname="); + builder.append(this.lastname); builder.append(", locale="); builder.append(this.locale); builder.append(", userState="); diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 6e419a542..480904019 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -72,10 +72,10 @@ public class PrivilegeModelDomWriter { firstnameElement.setTextContent(user.getFirstname()); userElement.appendChild(firstnameElement); - // add surname element - Element surnameElement = doc.createElement(XmlConstants.XML_SURNAME); - surnameElement.setTextContent(user.getSurname()); - userElement.appendChild(surnameElement); + // add lastname element + Element lastnameElement = doc.createElement(XmlConstants.XML_LASTNAME); + lastnameElement.setTextContent(user.getLastname()); + userElement.appendChild(lastnameElement); // add state element Element stateElement = doc.createElement(XmlConstants.XML_STATE); diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index 1ac89649e..03f43bf91 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -206,7 +206,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { // // Application -// Administrator +// Administrator // ENABLED // en_GB // @@ -227,7 +227,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { String username; String password; String firstName; - String surname; + String lastname; UserState userState; Locale locale; Set userRoles; @@ -259,8 +259,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { if (qName.equals(XmlConstants.XML_FIRSTNAME)) { this.firstName = this.text.toString().trim(); - } else if (qName.equals(XmlConstants.XML_SURNAME)) { - this.surname = this.text.toString().trim(); + } else if (qName.equals(XmlConstants.XML_LASTNAME)) { + this.lastname = this.text.toString().trim(); } else if (qName.equals(XmlConstants.XML_STATE)) { this.userState = UserState.valueOf(this.text.toString().trim()); } else if (qName.equals(XmlConstants.XML_LOCALE)) { @@ -269,7 +269,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.userRoles.add(this.text.toString().trim()); } else if (qName.equals(XmlConstants.XML_USER)) { - User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, + User user = new User(this.userId, this.username, this.password, this.firstName, this.lastname, this.userState, this.userRoles, this.locale, this.parameters); logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$ getUsers().add(user); diff --git a/src/main/resources/PrivilegeModel.xsd b/src/main/resources/PrivilegeModel.xsd index 7d0ea3d40..d2f82d897 100644 --- a/src/main/resources/PrivilegeModel.xsd +++ b/src/main/resources/PrivilegeModel.xsd @@ -45,7 +45,7 @@ Copyright 2013 Robert von Burg <eitch@eitchnet.ch> - + diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index a120c4d6b..b1db9740e 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -183,7 +183,7 @@ public class XmlTest { assertEquals("admin", admin.getUsername()); assertEquals("8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", admin.getPassword()); assertEquals("Application", admin.getFirstname()); - assertEquals("Administrator", admin.getSurname()); + assertEquals("Administrator", admin.getLastname()); assertEquals(UserState.ENABLED, admin.getUserState()); assertEquals("en_gb", admin.getLocale().toString()); assertThat(admin.getRoles(), containsInAnyOrder("PrivilegeAdmin", "AppUser")); @@ -198,7 +198,7 @@ public class XmlTest { assertEquals("system_admin", systemAdmin.getUsername()); assertEquals(null, systemAdmin.getPassword()); assertEquals("System User", systemAdmin.getFirstname()); - assertEquals("Administrator", systemAdmin.getSurname()); + assertEquals("Administrator", systemAdmin.getLastname()); assertEquals(UserState.SYSTEM, systemAdmin.getUserState()); assertEquals("en_gb", systemAdmin.getLocale().toString()); assertThat(systemAdmin.getRoles(), containsInAnyOrder("system_admin_privileges")); @@ -338,6 +338,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("a2127d20a61e00bcdbb61569cd2b200c4f0f111c972bac3b1e54df3b2fcdc8be", fileHash); + assertEquals("c9732a05bf0ed53d89b3d12e7c8d7216150b6a91412d1bf47fbe3e6f3be750ff", fileHash); } } From bb949f38af6e48831b924bbe193c4c2c2e1da849 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Aug 2014 22:28:33 +0200 Subject: [PATCH 306/457] [Minor] Added check for user's referencing inexistant oles --- .../privilege/handler/XmlPersistenceHandler.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index d94c6b246..dc21d5b91 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -189,6 +189,19 @@ public class XmlPersistenceHandler implements PersistenceHandler { logger.info(MessageFormat.format("Read {0} Users", this.userMap.size())); //$NON-NLS-1$ logger.info(MessageFormat.format("Read {0} Roles", this.roleMap.size())); //$NON-NLS-1$ + // validate referenced roles exist + for (User user : users) { + for (String roleName : user.getRoles()) { + + // validate that role exists + if (getRole(roleName) == null) { + String msg = "Role {0} does not exist referenced by user {1}"; + msg = MessageFormat.format(msg, roleName, user.getUsername()); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + } + } + // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; for (String username : this.userMap.keySet()) { From 97bd4fa08968832230d8f8383f20704824534a07 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 23 Aug 2014 20:46:58 +0200 Subject: [PATCH 307/457] [Minor] removed setting of object on CTX - do by FactoryDelegator When you implement a ContextFactory, and create the PersistenceContext, then set the object as well --- src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java | 10 ++-------- .../eitchnet/xmlpers/test/impl/BookContextFactory.java | 4 +++- .../xmlpers/test/impl/MyModelContextFactory.java | 4 +++- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index c2a492ed5..f5064db6e 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -53,7 +53,6 @@ public class ObjectDao { assertNotClosed(); assertNotNull(object); PersistenceContext ctx = createCtx(object); - ctx.setObject(object); ctx.getObjectRef().lock(); this.objectFilter.add(ctx.getObjectRef().getType(), ctx); } @@ -64,7 +63,6 @@ public class ObjectDao { if (!objects.isEmpty()) { for (T object : objects) { PersistenceContext ctx = createCtx(object); - ctx.setObject(object); ctx.getObjectRef().lock(); this.objectFilter.add(ctx.getObjectRef().getType(), ctx); } @@ -75,7 +73,6 @@ public class ObjectDao { assertNotClosed(); assertNotNull(object); PersistenceContext ctx = createCtx(object); - ctx.setObject(object); ctx.getObjectRef().lock(); this.objectFilter.update(ctx.getObjectRef().getType(), ctx); } @@ -86,7 +83,6 @@ public class ObjectDao { if (!objects.isEmpty()) { for (T object : objects) { PersistenceContext ctx = createCtx(object); - ctx.setObject(object); ctx.getObjectRef().lock(); this.objectFilter.update(ctx.getObjectRef().getType(), ctx); } @@ -97,7 +93,6 @@ public class ObjectDao { assertNotClosed(); assertNotNull(object); PersistenceContext ctx = createCtx(object); - ctx.setObject(object); ctx.getObjectRef().lock(); this.objectFilter.remove(ctx.getObjectRef().getType(), ctx); } @@ -108,7 +103,6 @@ public class ObjectDao { if (!objects.isEmpty()) { for (T object : objects) { PersistenceContext ctx = createCtx(object); - ctx.setObject(object); ctx.getObjectRef().lock(); this.objectFilter.remove(ctx.getObjectRef().getType(), ctx); } @@ -293,12 +287,12 @@ public class ObjectDao { } } - private PersistenceContext createCtx(T object) { + public PersistenceContext createCtx(T object) { return this.ctxFactoryDelegator. getCtxFactory(object.getClass()).createCtx(this.tx.getObjectRefCache(), object); } - private PersistenceContext createCtx(ObjectRef objectRef) { + public PersistenceContext createCtx(ObjectRef objectRef) { String type = objectRef.getType(); PersistenceContextFactory ctxFactory = this.ctxFactoryDelegator. getCtxFactory(type); return ctxFactory.createCtx(objectRef); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java index ccb808507..50563cb82 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java @@ -34,6 +34,8 @@ public class BookContextFactory implements PersistenceContextFactory { @Override public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, Book t) { IdOfTypeRef objectRef = objectRefCache.getIdOfTypeRef(TestConstants.TYPE_BOOK, t.getId().toString()); - return createCtx(objectRef); + PersistenceContext ctx = createCtx(objectRef); + ctx.setObject(t); + return ctx; } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java index 218c82922..718813d46 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java @@ -27,7 +27,9 @@ public class MyModelContextFactory implements PersistenceContextFactory @Override public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, MyModel t) { IdOfSubTypeRef objectRef = objectRefCache.getIdOfSubTypeRef(TestConstants.TYPE_RES, t.getType(), t.getId()); - return createCtx(objectRef); + PersistenceContext ctx = createCtx(objectRef); + ctx.setObject(t); + return ctx; } @Override From 930e85fdfe251502ccc1c3aea3939ad8670a446f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 23 Aug 2014 20:47:37 +0200 Subject: [PATCH 308/457] [New] exposing FileDao in Transaction so users can bypass tx --- .../java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java | 2 ++ .../eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java index cd3c50de8..0f2efa7e5 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java @@ -54,6 +54,8 @@ public interface PersistenceTransaction extends AutoCloseable { public MetadataDao getMetadataDao(); + public FileDao getFileDao(); + public ObjectReferenceCache getObjectRefCache(); public PersistenceRealm getRealm(); diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index 7d6bc8fe4..053337e60 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -114,6 +114,11 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { return this.metadataDao; } + @Override + public FileDao getFileDao() { + return this.fileDao; + } + @Override public ObjectReferenceCache getObjectRefCache() { return this.realm.getObjectRefCache(); From 7e4adffb4c1b274a9e0af28840342cacb8740fab Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Aug 2014 17:21:29 +0200 Subject: [PATCH 309/457] [Project] set version to 1.0.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e48ed96f9..7fda9e906 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ ch.eitchnet ch.eitchnet.parent - 1.1.0-SNAPSHOT + 1.0.0 ../ch.eitchnet.parent/pom.xml ch.eitchnet.privilege - 0.2.0-SNAPSHOT + 1.0.0-SNAPSHOT jar ch.eitchnet.privilege https://github.com/eitchnet/ch.eitchnet.privilege From c4c5dd01d49fd2f01475748df40121525ecc7c56 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Aug 2014 17:21:40 +0200 Subject: [PATCH 310/457] [Project] set version to 1.0.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f81efd194..4b32d8547 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ ch.eitchnet ch.eitchnet.parent - 1.1.0-SNAPSHOT + 1.0.0 ../ch.eitchnet.parent/pom.xml ch.eitchnet.utils - 0.2.0-SNAPSHOT + 1.0.0-SNAPSHOT jar ch.eitchnet.utils These utils contain project independent helper classes and utilities for reuse From ced1848b7600b8a2372039b5e8c9e76688c237c1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Aug 2014 17:21:59 +0200 Subject: [PATCH 311/457] [Project] set version to 1.0.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b90e5dea4..5f03b1e72 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ ch.eitchnet ch.eitchnet.parent - 1.1.0-SNAPSHOT + 1.0.0 ../ch.eitchnet.parent/pom.xml ch.eitchnet.xmlpers - 0.3.0-SNAPSHOT + 1.0.0-SNAPSHOT jar ch.eitchnet.xmlpers https://github.com/eitchnet/ch.eitchnet.xmlpers From dd9824f1303c4c8b5181cb675bfe1cb83d4b62fb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Aug 2014 18:17:35 +0200 Subject: [PATCH 312/457] [Project] set version to 1.0.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7fda9e906..ad8d85972 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ ch.eitchnet ch.eitchnet.utils - 0.2.0-SNAPSHOT + 1.0.0-SNAPSHOT From 75199bc6b74a3ee5c5ae964457f396adbba09d27 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Aug 2014 18:17:41 +0200 Subject: [PATCH 313/457] [Project] set version to 1.0.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f03b1e72..5cddc9a21 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ ch.eitchnet ch.eitchnet.utils - 0.2.0-SNAPSHOT + 1.0.0-SNAPSHOT From 3f68aa0adc3b47dcbdeb24104384bf9e859a25e4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 25 Aug 2014 22:00:34 +0200 Subject: [PATCH 314/457] [New] Added StringMatchMode to use when matching strings --- .../ch/eitchnet/utils/StringMatchMode.java | 61 +++++++++++++++ .../eitchnet/utils/StringMatchModeTest.java | 75 +++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/StringMatchMode.java create mode 100644 src/test/java/ch/eitchnet/utils/StringMatchModeTest.java diff --git a/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/src/main/java/ch/eitchnet/utils/StringMatchMode.java new file mode 100644 index 000000000..a97e2a016 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/StringMatchMode.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils; + +/** + * @author Robert von Burg + */ +public enum StringMatchMode { + EQUALS_CASE_SENSITIVE(true, true), + EQUALS_CASE_INSENSITIVE(true, false), + CONTAINS_CASE_SENSITIVE(false, true), + CONTAINS_CASE_INSENSITIVE(false, false); + + private final boolean equals; + private final boolean caseSensitve; + + private StringMatchMode(boolean equals, boolean caseSensitive) { + this.equals = equals; + this.caseSensitve = caseSensitive; + } + + /** + * @return the caseSensitve + */ + public boolean isCaseSensitve() { + return this.caseSensitve; + } + + /** + * @return the equals + */ + public boolean isEquals() { + return this.equals; + } + + public boolean matches(String value1, String value2) { + if (!this.isEquals() && !this.isCaseSensitve()) + return value1.toLowerCase().contains(value2.toLowerCase()); + + if (!this.isCaseSensitve()) + return value1.toLowerCase().equals(value2.toLowerCase()); + + if (!this.isEquals()) + return value1.contains(value2); + + return value1.equals(value2); + } +} diff --git a/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java b/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java new file mode 100644 index 000000000..aa80dc33f --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * @author Robert von Burg + * + */ +public class StringMatchModeTest { + + /** + * Test method for {@link ch.eitchnet.utils.StringMatchMode#isCaseSensitve()}. + */ + @Test + public void testIsCaseSensitve() { + assertFalse(StringMatchMode.EQUALS_CASE_INSENSITIVE.isCaseSensitve()); + assertTrue(StringMatchMode.EQUALS_CASE_SENSITIVE.isCaseSensitve()); + assertFalse(StringMatchMode.CONTAINS_CASE_INSENSITIVE.isCaseSensitve()); + assertTrue(StringMatchMode.CONTAINS_CASE_SENSITIVE.isCaseSensitve()); + } + + /** + * Test method for {@link ch.eitchnet.utils.StringMatchMode#isEquals()}. + */ + @Test + public void testIsEquals() { + assertTrue(StringMatchMode.EQUALS_CASE_INSENSITIVE.isEquals()); + assertTrue(StringMatchMode.EQUALS_CASE_SENSITIVE.isEquals()); + assertFalse(StringMatchMode.CONTAINS_CASE_INSENSITIVE.isEquals()); + assertFalse(StringMatchMode.CONTAINS_CASE_SENSITIVE.isEquals()); + } + + /** + * Test method for {@link ch.eitchnet.utils.StringMatchMode#matches(java.lang.String, java.lang.String)}. + */ + @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")); + } +} From aa311f3cb2c58d41642c195e70e2d0442cbefa56 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 25 Aug 2014 22:51:52 +0200 Subject: [PATCH 315/457] [New] Added StringMatchMode to use when matching strings --- src/main/java/ch/eitchnet/utils/StringMatchMode.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/src/main/java/ch/eitchnet/utils/StringMatchMode.java index a97e2a016..4d4de227f 100644 --- a/src/main/java/ch/eitchnet/utils/StringMatchMode.java +++ b/src/main/java/ch/eitchnet/utils/StringMatchMode.java @@ -15,6 +15,8 @@ */ package ch.eitchnet.utils; +import ch.eitchnet.utils.dbc.DBC; + /** * @author Robert von Burg */ @@ -47,6 +49,8 @@ public enum StringMatchMode { } public boolean matches(String value1, String value2) { + DBC.PRE.assertNotNull("value1 must be set!", value1); + DBC.PRE.assertNotNull("value2 must be set!", value2); if (!this.isEquals() && !this.isCaseSensitve()) return value1.toLowerCase().contains(value2.toLowerCase()); From fe047da55e707c3af9ddb2dc5bd1be2101a13da0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 26 Aug 2014 17:20:27 +0200 Subject: [PATCH 316/457] [New] added DateRange.isDate() --- .../eitchnet/utils/collections/DateRange.java | 7 +++++++ .../utils/collections/DateRangeTest.java | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/collections/DateRange.java b/src/main/java/ch/eitchnet/utils/collections/DateRange.java index ac1dc5cf2..a45cb6842 100644 --- a/src/main/java/ch/eitchnet/utils/collections/DateRange.java +++ b/src/main/java/ch/eitchnet/utils/collections/DateRange.java @@ -90,6 +90,13 @@ public class DateRange { 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); if (this.fromDate == null && this.toDate == null) diff --git a/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java b/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java index e8b553f6b..d70658768 100644 --- a/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java +++ b/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java @@ -82,6 +82,23 @@ public class DateRangeTest { 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)}. */ From 8b6410ff2a5d2af07f736af9c6abc7c9c7ebad08 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 28 Aug 2014 21:44:35 +0200 Subject: [PATCH 317/457] [New] Added StringHelper.commaSeparated() and .splitCommaSeparated() --- .../ch/eitchnet/utils/helper/StringHelper.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index ee2755922..cc95d2ae1 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -635,6 +635,24 @@ public class StringHelper { } } + 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(", "); + } + return sb.toString(); + } + + public static String[] splitCommaSeparated(String values) { + String[] split = values.split(","); + for (int i = 0; i < split.length; i++) { + split[i] = split[i].trim(); + } + return split; + } + /** * Return a pseudo unique id which is incremented on each call. The id is initialized from the current time * From b5587d3dc3ffa28338181a9fcfe6937fa6d34747 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 29 Aug 2014 14:22:21 +0200 Subject: [PATCH 318/457] [New] added IoMessageStateObserver --- .../communication/IoMessageStateObserver.java | 48 +++++++++++++++++++ .../communication/IoMessageVisitor.java | 5 ++ .../tcpip/ClientSocketEndpoint.java | 2 + 3 files changed, 55 insertions(+) create mode 100644 src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java diff --git a/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java b/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java new file mode 100644 index 000000000..984fcbc0e --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.communication; + +import ch.eitchnet.communication.IoMessage.State; + +/** + * @author Robert von Burg + */ +public class IoMessageStateObserver implements ConnectionObserver { + + private CommandKey key; + private String messageId; + private State state; + + public IoMessageStateObserver(CommandKey key, String messageId) { + this.key = key; + this.messageId = messageId; + } + + @Override + public void notify(CommandKey key, IoMessage message) { + if (this.key.equals(key) && message.getId().equals(this.messageId)) { + this.state = message.getState(); + + synchronized (this) { + this.notifyAll(); + } + } + } + + public State getState() { + return this.state; + } +} diff --git a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java index c7505ce88..5139afe87 100644 --- a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java @@ -15,6 +15,9 @@ */ package ch.eitchnet.communication; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import ch.eitchnet.communication.console.ConsoleMessageVisitor; /** @@ -32,6 +35,8 @@ import ch.eitchnet.communication.console.ConsoleMessageVisitor; */ public abstract class IoMessageVisitor { + protected static final Logger logger = LoggerFactory.getLogger(IoMessageVisitor.class); + protected CommunicationConnection connection; public void configure(CommunicationConnection connection) { diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java index c58926f3b..0c0a37cc2 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -283,6 +283,8 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { *

    • clearOnConnect - if true, then the after a successful connect the input is cleared by discarding all * available bytes. This can be useful in cases where the channel is clogged with stale data. default is * {@link SocketEndpointConstants#CLEAR_ON_CONNECT}
    • + *
    • connectOnStart - if true, then when the connection is started, the connection to the remote address is + * attempted. default is {@link SocketEndpointConstants#CONNECT_ON_START} * * * @see CommunicationEndpoint#configure(CommunicationConnection, IoMessageVisitor) From 901c2c86f2262ccc7d70c535a91f6648037b20ef Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 8 Sep 2014 13:35:02 +0200 Subject: [PATCH 319/457] [New] added PrivilegeContext.getFlatAllowList() This allows to query all the allows which can be used to define which UIs and buttons to show on a client --- .../eitchnet/privilege/model/PrivilegeContext.java | 14 ++++++++++++++ .../privilege/model/internal/PrivilegeImpl.java | 7 ++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 8cd33eef1..09a282567 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -16,8 +16,10 @@ package ch.eitchnet.privilege.model; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -71,6 +73,18 @@ public class PrivilegeContext { return this.privileges.keySet(); } + public IPrivilege getPrivilege(String privilegeName) { + return this.privileges.get(privilegeName); + } + + public List getFlatAllowList() { + List allowList = new ArrayList<>(); + for (IPrivilege privilege : this.privileges.values()) { + allowList.addAll(privilege.getAllowList()); + } + return allowList; + } + // // business logic // diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index e9d2b937c..3efe3e36a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -15,6 +15,7 @@ */ package ch.eitchnet.privilege.model.internal; +import java.text.MessageFormat; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -76,13 +77,13 @@ public final class PrivilegeImpl implements IPrivilege { throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(policy)) { - throw new PrivilegeException("Policy may not be empty!"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format("Policy may not be empty for Privilege {0}!", name)); //$NON-NLS-1$ } if (denyList == null) { - throw new PrivilegeException("denyList is null!"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format("denyList is null for Privilege {0}!", name)); //$NON-NLS-1$ } if (allowList == null) { - throw new PrivilegeException("allowList is null!"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format("allowList is null for Privilege {0}!", name)); //$NON-NLS-1$ } this.name = name; From 09e6e06078bd3a5ec36b44d6ec6fadab24e68f36 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 13 Sep 2014 18:34:24 +0200 Subject: [PATCH 320/457] [New] Added ConnectionInfo --- .../CommunicationConnection.java | 8 +- .../communication/ConnectionInfo.java | 154 ++++++++++++++++++ 2 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ch/eitchnet/communication/ConnectionInfo.java diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java index 846be6086..2e0180743 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -187,7 +187,7 @@ public class CommunicationConnection implements Runnable { if (this.queueThread != null) { logger.warn(MessageFormat.format("{0}: Already connected!", this.id)); //$NON-NLS-1$ } else { - logger.info(MessageFormat.format("Starting Connection {0}...", this.id)); //$NON-NLS-1$ + logger.info(MessageFormat.format("Starting Connection {0} to {1}...", this.id, this.getRemoteUri())); //$NON-NLS-1$ this.run = true; this.queueThread = new Thread(this, MessageFormat.format("{0}_OUT", this.id)); //$NON-NLS-1$ this.queueThread.start(); @@ -358,10 +358,14 @@ public class CommunicationConnection implements Runnable { } } - public String getUri() { + 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(); diff --git a/src/main/java/ch/eitchnet/communication/ConnectionInfo.java b/src/main/java/ch/eitchnet/communication/ConnectionInfo.java new file mode 100644 index 000000000..d34b4e4e9 --- /dev/null +++ b/src/main/java/ch/eitchnet/communication/ConnectionInfo.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.communication; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * @author Robert von Burg + */ +@XmlRootElement(name = "ConnectionInfo") +@XmlAccessorType(XmlAccessType.FIELD) +public class ConnectionInfo { + + @XmlAttribute(name = "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 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.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; + } +} From 56126c97c2072f8cc637d5b5a239521fcf7ea7e4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 13 Sep 2014 19:01:57 +0200 Subject: [PATCH 321/457] [Minor] code cleanup --- .../ch/eitchnet/communication/ConnectionMessages.java | 2 +- src/main/java/ch/eitchnet/utils/StringMatchMode.java | 4 ++-- .../java/ch/eitchnet/utils/collections/DateRange.java | 10 +++++----- .../java/ch/eitchnet/utils/helper/StringHelper.java | 4 ++-- .../java/ch/eitchnet/utils/StringMatchModeTest.java | 1 + .../ch/eitchnet/utils/collections/DateRangeTest.java | 6 +++--- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java index 18aae76ad..28f6baef1 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionMessages.java @@ -104,7 +104,7 @@ public class ConnectionMessages { if (logger.isDebugEnabled()) { String msg = "{0}: parameter ''{1}'' is not set, using default value ''{2}''"; //$NON-NLS-1$ msg = MessageFormat.format(msg, clazz.getSimpleName(), parameterName, defValue); - Map properties = new HashMap(); + Map properties = new HashMap<>(); logger.warn(MessageFormat.format(msg, properties)); } } diff --git a/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/src/main/java/ch/eitchnet/utils/StringMatchMode.java index 4d4de227f..ebea91029 100644 --- a/src/main/java/ch/eitchnet/utils/StringMatchMode.java +++ b/src/main/java/ch/eitchnet/utils/StringMatchMode.java @@ -49,8 +49,8 @@ public enum StringMatchMode { } public boolean matches(String value1, String value2) { - DBC.PRE.assertNotNull("value1 must be set!", value1); - DBC.PRE.assertNotNull("value2 must be set!", value2); + DBC.PRE.assertNotNull("value1 must be set!", value1); //$NON-NLS-1$ + DBC.PRE.assertNotNull("value2 must be set!", value2); //$NON-NLS-1$ if (!this.isEquals() && !this.isCaseSensitve()) return value1.toLowerCase().contains(value2.toLowerCase()); diff --git a/src/main/java/ch/eitchnet/utils/collections/DateRange.java b/src/main/java/ch/eitchnet/utils/collections/DateRange.java index a45cb6842..3c25b4bc7 100644 --- a/src/main/java/ch/eitchnet/utils/collections/DateRange.java +++ b/src/main/java/ch/eitchnet/utils/collections/DateRange.java @@ -44,8 +44,8 @@ public class DateRange { } private void validate() { - if (toDate != null && fromDate != null) - DBC.INTERIM.assertTrue("From must be before to!", toDate.compareTo(fromDate) >= 0); + if (this.toDate != null && this.fromDate != null) + DBC.INTERIM.assertTrue("From must be before to!", this.toDate.compareTo(this.fromDate) >= 0); //$NON-NLS-1$ } /** @@ -98,7 +98,7 @@ public class DateRange { } public boolean contains(Date date) { - DBC.PRE.assertNotNull("Date must be given!", date); + DBC.PRE.assertNotNull("Date must be given!", date); //$NON-NLS-1$ if (this.fromDate == null && this.toDate == null) return true; @@ -107,7 +107,7 @@ public class DateRange { if (this.toDate != null) { int compare = this.toDate.compareTo(date); - if (toInclusive) + if (this.toInclusive) toContains = compare >= 0; else toContains = compare > 0; @@ -115,7 +115,7 @@ public class DateRange { if (this.fromDate != null) { int compare = this.fromDate.compareTo(date); - if (fromInclusive) + if (this.fromInclusive) fromContains = compare <= 0; else fromContains = compare < 0; diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index cc95d2ae1..526f63f4a 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -640,13 +640,13 @@ public class StringHelper { for (int i = 0; i < values.length; i++) { sb.append(values[i]); if (i < values.length - 1) - sb.append(", "); + sb.append(", "); //$NON-NLS-1$ } return sb.toString(); } public static String[] splitCommaSeparated(String values) { - String[] split = values.split(","); + String[] split = values.split(","); //$NON-NLS-1$ for (int i = 0; i < split.length; i++) { split[i] = split[i].trim(); } diff --git a/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java b/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java index aa80dc33f..c387ac305 100644 --- a/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java +++ b/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java @@ -51,6 +51,7 @@ public class StringMatchModeTest { /** * 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")); diff --git a/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java b/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java index d70658768..5ec0bbc75 100644 --- a/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java +++ b/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java @@ -45,7 +45,7 @@ public class DateRangeTest { } /** - * Test method for {@link ch.eitchnet.utils.collections.DateRange#to(java.util.Date)}. + * Test method for {@link ch.eitchnet.utils.collections.DateRange#to(java.util.Date, boolean)}. */ @Test public void testTo() { @@ -59,7 +59,7 @@ public class DateRangeTest { } /** - * Test method for {@link ch.eitchnet.utils.collections.DateRange#to(java.util.Date)}. + * Test method for {@link ch.eitchnet.utils.collections.DateRange#to(java.util.Date,boolean)}. */ @Test public void testFromTo() { @@ -75,7 +75,7 @@ public class DateRangeTest { @Test public void shouldNotOverlap() { - exception.expect(DbcException.class); + this.exception.expect(DbcException.class); Date from = new Date(10); Date to = new Date(20); DateRange dateRange = new DateRange(); From 2159c68a7197adab100fcf644db4e74660642db0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 14 Sep 2014 11:25:23 +0200 Subject: [PATCH 322/457] [New] Added SIMULATION handling in CommunicationEndpoint and Visitor --- .../CommunicationConnection.java | 13 ++++++------- .../communication/CommunicationEndpoint.java | 4 +++- .../communication/ConnectionInfo.java | 19 +++++++++++++++++++ .../communication/IoMessageVisitor.java | 4 ++++ .../console/ConsoleEndpoint.java | 5 +++++ .../communication/file/FileEndpoint.java | 5 +++++ .../tcpip/ClientSocketEndpoint.java | 5 +++++ .../tcpip/ServerSocketEndpoint.java | 5 +++++ 8 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java index 2e0180743..05c75681f 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -288,7 +288,10 @@ public class CommunicationConnection implements Runnable { message = this.messageQueue.take(); logger.info(MessageFormat.format("Processing message {0}...", message.getId())); //$NON-NLS-1$ - this.endpoint.send(message); + 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) @@ -398,11 +401,7 @@ public class CommunicationConnection implements Runnable { message.setState(State.PENDING, State.PENDING.name()); - if (this.mode == ConnectionMode.SIMULATION) { - message.setState(State.DONE, State.DONE.name()); - done(message); - } else { - this.messageQueue.add(message); - } + this.messageQueue.add(message); + this.messageQueue.add(message); } } diff --git a/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java b/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java index ef0a75813..54579f7b9 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java @@ -25,7 +25,7 @@ public interface CommunicationEndpoint { public void start(); public void stop(); - + public void reset(); public String getLocalUri(); @@ -33,4 +33,6 @@ public interface CommunicationEndpoint { public String getRemoteUri(); public void send(IoMessage message) throws Exception; + + public void simulate(IoMessage message) throws Exception; } diff --git a/src/main/java/ch/eitchnet/communication/ConnectionInfo.java b/src/main/java/ch/eitchnet/communication/ConnectionInfo.java index d34b4e4e9..c5bfd51a3 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionInfo.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionInfo.java @@ -33,6 +33,9 @@ import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.FIELD) public class ConnectionInfo { + @XmlAttribute(name = "id") + private String id; + @XmlAttribute(name = "localUri") private String localUri; @@ -51,6 +54,21 @@ public class ConnectionInfo { @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 */ @@ -143,6 +161,7 @@ public class ConnectionInfo { 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()); diff --git a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java index 5139afe87..a64207ec1 100644 --- a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java @@ -42,4 +42,8 @@ public abstract class IoMessageVisitor { public void configure(CommunicationConnection connection) { this.connection = connection; } + + public void simulate(IoMessage ioMessage) { + // allow for subclasses to implement + } } diff --git a/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java b/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java index 11bdd4e3d..e39916c48 100644 --- a/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java @@ -75,4 +75,9 @@ public class ConsoleEndpoint implements CommunicationEndpoint { this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); } } + + @Override + public void simulate(IoMessage message) throws Exception { + this.send(message); + } } diff --git a/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java b/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java index a320a10d7..edd9b0fd1 100644 --- a/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java @@ -173,6 +173,11 @@ public class FileEndpoint implements CommunicationEndpoint, Runnable { } } + @Override + public void simulate(IoMessage message) throws Exception { + this.messageVisitor.simulate(message); + } + @Override public void run() { diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java index 0c0a37cc2..4d7945ad2 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -497,6 +497,11 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { 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 { diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java index 8cc5b71ec..e6f3e02e1 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -582,6 +582,11 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { } } + @Override + public void simulate(IoMessage message) throws Exception { + this.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$ From e3dab98b3ff012054242eebbea523a1091d3b722 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 14 Sep 2014 12:19:34 +0200 Subject: [PATCH 323/457] [Minor] code cleanup --- .../privilege/handler/EncryptionHandler.java | 2 +- .../handler/XmlPersistenceHandler.java | 2 +- .../eitchnet/privilege/model/UserState.java | 2 +- .../privilege/model/internal/User.java | 4 ++-- .../privilege/test/PrivilegeTest.java | 21 ++++++++++--------- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java index 186789f4d..59eddcfbe 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -40,7 +40,7 @@ public interface EncryptionHandler { * @return the hash of the string after converting */ public String convertToHash(String string); - + /** * Converts a given byte array, e.g. a password to a hash which is defined by the concrete implementation * diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index dc21d5b91..fe9745bc5 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -196,7 +196,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // validate that role exists if (getRole(roleName) == null) { String msg = "Role {0} does not exist referenced by user {1}"; - msg = MessageFormat.format(msg, roleName, user.getUsername()); //$NON-NLS-1$ + msg = MessageFormat.format(msg, roleName, user.getUsername()); throw new PrivilegeException(msg); } } diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index 4922ff67e..2272002d3 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -48,7 +48,7 @@ public enum UserState { * the user has automatically expired through a predefined time */ EXPIRED, - + /** * This is the System user state which is special and thus exempted from normal uses */ diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index f54ccb630..c81ad1ee8 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -78,8 +78,8 @@ public final class User { * @param propertyMap * a {@link Map} containing string value pairs of properties for this user */ - public User(String userId, String username, String password, String firstname, String lastname, UserState userState, - Set roles, Locale locale, Map propertyMap) { + public User(String userId, String username, String password, String firstname, String lastname, + UserState userState, Set roles, Locale locale, Map propertyMap) { if (StringHelper.isEmpty(userId)) { throw new PrivilegeException("No UserId defined!"); //$NON-NLS-1$ diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 3968c6e25..25804e155 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -185,8 +185,8 @@ public class PrivilegeTest { } public void testFailAuthenticationNOk() throws Exception { - exception.expect(AccessDeniedException.class); - exception.expectMessage("blabla"); + this.exception.expect(AccessDeniedException.class); + this.exception.expectMessage("blabla"); try { login(ADMIN, ArraysHelper.copyOf(PASS_BAD)); } finally { @@ -195,8 +195,8 @@ public class PrivilegeTest { } public void testFailAuthenticationPWNull() throws Exception { - exception.expect(PrivilegeException.class); - exception.expectMessage("blabla"); + this.exception.expect(PrivilegeException.class); + this.exception.expectMessage("blabla"); try { login(ADMIN, null); } finally { @@ -250,8 +250,8 @@ public class PrivilegeTest { */ @Test public void testPerformSystemRestrictableFailPrivilege() throws Exception { - exception.expect(PrivilegeException.class); - exception + this.exception.expect(PrivilegeException.class); + this.exception .expectMessage("User system_admin does not have Privilege ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); try { // create the action to be performed as a system user @@ -269,8 +269,9 @@ public class PrivilegeTest { */ @Test public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { - exception.expect(PrivilegeException.class); - exception.expectMessage("User system_admin2 does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable"); + this.exception.expect(PrivilegeException.class); + this.exception + .expectMessage("User system_admin2 does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -287,8 +288,8 @@ public class PrivilegeTest { */ @Test public void testLoginSystemUser() throws Exception { - exception.expect(AccessDeniedException.class); - exception.expectMessage("User system_admin is a system user and may not login!"); + this.exception.expect(AccessDeniedException.class); + this.exception.expectMessage("User system_admin is a system user and may not login!"); try { login(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); } finally { From bf72c969054056c3db6acf16d1a58ed74d45de83 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 14 Sep 2014 12:19:41 +0200 Subject: [PATCH 324/457] [Minor] code cleanup --- .../communication/CommunicationConnection.java | 4 ++-- .../communication/IoMessageStateObserver.java | 2 +- .../eitchnet/communication/chat/ChatClient.java | 2 +- .../eitchnet/communication/chat/ChatServer.java | 2 +- .../communication/console/ConsoleEndpoint.java | 4 ++-- .../tcpip/ServerSocketEndpoint.java | 2 +- .../tcpip/SocketEndpointConstants.java | 2 +- .../java/ch/eitchnet/fileserver/FileClient.java | 4 ++-- .../java/ch/eitchnet/utils/StringMatchMode.java | 6 +++--- .../communication/ConsoleEndpointTest.java | 2 +- .../utils/objectfilter/ObjectFilterTest.java | 16 ++++++++-------- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java index 05c75681f..a98b0edf4 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -170,7 +170,7 @@ public class CommunicationConnection implements Runnable { public void configure() { this.messageVisitor.configure(this); this.endpoint.configure(this, this.messageVisitor); - this.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.name()); + notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.name()); } public void start() { @@ -187,7 +187,7 @@ public class CommunicationConnection implements Runnable { 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, this.getRemoteUri())); //$NON-NLS-1$ + 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(); diff --git a/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java b/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java index 984fcbc0e..cf7d11a54 100644 --- a/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java +++ b/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java @@ -37,7 +37,7 @@ public class IoMessageStateObserver implements ConnectionObserver { this.state = message.getState(); synchronized (this) { - this.notifyAll(); + notifyAll(); } } } diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatClient.java b/src/main/java/ch/eitchnet/communication/chat/ChatClient.java index 51b2c4823..5f0807281 100644 --- a/src/main/java/ch/eitchnet/communication/chat/ChatClient.java +++ b/src/main/java/ch/eitchnet/communication/chat/ChatClient.java @@ -86,7 +86,7 @@ public class ChatClient implements ConnectionObserver, ConnectionStateObserver { this.connected = newState == ConnectionState.CONNECTED || newState == ConnectionState.IDLE || newState == ConnectionState.WORKING; synchronized (this) { - this.notifyAll(); + notifyAll(); } } } diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatServer.java b/src/main/java/ch/eitchnet/communication/chat/ChatServer.java index d94409626..fbd9b3bde 100644 --- a/src/main/java/ch/eitchnet/communication/chat/ChatServer.java +++ b/src/main/java/ch/eitchnet/communication/chat/ChatServer.java @@ -87,7 +87,7 @@ public class ChatServer implements ConnectionObserver, ConnectionStateObserver { this.connected = newState == ConnectionState.CONNECTED || newState == ConnectionState.IDLE || newState == ConnectionState.WORKING; synchronized (this) { - this.notifyAll(); + notifyAll(); } } } diff --git a/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java b/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java index e39916c48..26bcf14e8 100644 --- a/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java @@ -75,9 +75,9 @@ public class ConsoleEndpoint implements CommunicationEndpoint { this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); } } - + @Override public void simulate(IoMessage message) throws Exception { - this.send(message); + send(message); } } diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java index e6f3e02e1..5acc85e09 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -584,7 +584,7 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { @Override public void simulate(IoMessage message) throws Exception { - this.send(message); + send(message); } @Override diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java b/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java index 74ccbdc0a..df7173e46 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java @@ -63,7 +63,7 @@ public class SocketEndpointConstants { * Default is to connect on start of the connection */ public static final boolean CONNECT_ON_START = true; - + /** * Default is to not close after sending */ diff --git a/src/main/java/ch/eitchnet/fileserver/FileClient.java b/src/main/java/ch/eitchnet/fileserver/FileClient.java index 3fc356054..0924c8689 100644 --- a/src/main/java/ch/eitchnet/fileserver/FileClient.java +++ b/src/main/java/ch/eitchnet/fileserver/FileClient.java @@ -48,8 +48,8 @@ public interface FileClient { 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 + * 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 * diff --git a/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/src/main/java/ch/eitchnet/utils/StringMatchMode.java index ebea91029..d096a0c53 100644 --- a/src/main/java/ch/eitchnet/utils/StringMatchMode.java +++ b/src/main/java/ch/eitchnet/utils/StringMatchMode.java @@ -51,13 +51,13 @@ public enum StringMatchMode { 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 (!this.isEquals() && !this.isCaseSensitve()) + if (!isEquals() && !isCaseSensitve()) return value1.toLowerCase().contains(value2.toLowerCase()); - if (!this.isCaseSensitve()) + if (!isCaseSensitve()) return value1.toLowerCase().equals(value2.toLowerCase()); - if (!this.isEquals()) + if (!isEquals()) return value1.contains(value2); return value1.equals(value2); diff --git a/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java b/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java index 9a7e41bc0..c7090924f 100644 --- a/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java +++ b/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java @@ -50,7 +50,7 @@ public class ConsoleEndpointTest extends AbstractEndpointTest { public void testConsoleEndpoint() throws InterruptedException { this.connection.start(); - + CommandKey key = CommandKey.key(CONNECTION_ID, "logger"); //$NON-NLS-1$ TestIoMessage msg = createTestMessage(key, CONNECTION_ID); diff --git a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java index 70b6c98f7..e1126d25b 100644 --- a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java +++ b/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java @@ -100,9 +100,9 @@ public class ObjectFilterTest { try { filter.add(myObj); - fail("Should have failed adding twice!"); + fail("Should have failed adding twice!"); } catch (RuntimeException e) { - assertEquals("Stale State exception: Invalid + after +", e.getMessage()); + assertEquals("Stale State exception: Invalid + after +", e.getMessage()); } testAssertions(filter, 1, 1, 1, 0, 0); @@ -118,9 +118,9 @@ public class ObjectFilterTest { try { filter.remove(myObj); - fail("Should have failed removing twice!"); + fail("Should have failed removing twice!"); } catch (RuntimeException e) { - assertEquals("Stale State exception: Invalid - after -", e.getMessage()); + assertEquals("Stale State exception: Invalid - after -", e.getMessage()); } testAssertions(filter, 1, 1, 0, 0, 1); @@ -158,9 +158,9 @@ public class ObjectFilterTest { try { filter.add(myObj); - fail("Should have failed add after modify"); + fail("Should have failed add after modify"); } catch (RuntimeException e) { - assertEquals("Stale State exception: Invalid + after +=", e.getMessage()); + assertEquals("Stale State exception: Invalid + after +=", e.getMessage()); } testAssertions(filter, 1, 1, 0, 1, 0); @@ -186,9 +186,9 @@ public class ObjectFilterTest { try { filter.update(myObj); - fail("Should have failed modify after remove"); + fail("Should have failed modify after remove"); } catch (RuntimeException e) { - assertEquals("Stale State exception: Invalid += after -", e.getMessage()); + assertEquals("Stale State exception: Invalid += after -", e.getMessage()); } testAssertions(filter, 1, 1, 0, 0, 1); From 3fbf73fd15f0d93406160f10990f4761ceacba5d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 14 Sep 2014 12:19:49 +0200 Subject: [PATCH 325/457] [Minor] code cleanup --- .../eitchnet/xmlpers/api/ParserFactory.java | 1 - .../xmlpers/api/PersistenceManager.java | 7 +- .../xmlpers/objref/LockableObject.java | 6 +- .../eitchnet/xmlpers/objref/SubTypeRef.java | 2 +- .../java/javanet/staxutils/Indentation.java | 45 +- .../staxutils/IndentingXMLStreamWriter.java | 500 +++++++++--------- .../helpers/StreamWriterDelegate.java | 216 ++++---- .../ch/eitchnet/xmlpers/test/LockingTest.java | 8 +- .../ch/eitchnet/xmlpers/test/XmlTestMain.java | 2 +- .../xmlpers/test/impl/MyModelDomParser.java | 2 +- .../xmlpers/test/impl/MyModelSaxParser.java | 2 +- .../xmlpers/test/model/ModelBuilder.java | 2 +- .../eitchnet/xmlpers/test/model/MyModel.java | 9 +- 13 files changed, 394 insertions(+), 408 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java b/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java index b1ba612cf..003b1236d 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java @@ -15,7 +15,6 @@ */ package ch.eitchnet.xmlpers.api; - public interface ParserFactory { public DomParser getDomParser(); diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java index 2790a394c..e19b71521 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java @@ -15,7 +15,6 @@ */ package ch.eitchnet.xmlpers.api; - /** * @author Robert von Burg * @@ -23,10 +22,10 @@ package ch.eitchnet.xmlpers.api; public interface PersistenceManager { public static final String DEFAULT_REALM = "defaultRealm"; //$NON-NLS-1$ - + public PersistenceContextFactoryDelegator getCtxFactory(); - + public PersistenceTransaction openTx(); - + public PersistenceTransaction openTx(String realm); } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java index 760b8234f..545de62d7 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java @@ -48,11 +48,11 @@ public class LockableObject { if (!this.lock.tryLock(tryLockTime, TimeUnit.MILLISECONDS)) { String msg = "Failed to acquire lock after {0} for {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, StringHelper.formatMillisecondsDuration(tryLockTime), this.toString()); + msg = MessageFormat.format(msg, StringHelper.formatMillisecondsDuration(tryLockTime), toString()); throw new XmlPersistenceException(msg); } if (logger.isDebugEnabled()) - logger.debug("locked " + this.toString()); //$NON-NLS-1$ + logger.debug("locked " + toString()); //$NON-NLS-1$ } catch (InterruptedException e) { throw new XmlPersistenceException("Thread interrupted: " + e.getMessage(), e); //$NON-NLS-1$ } @@ -64,6 +64,6 @@ public class LockableObject { public void unlock() { this.lock.unlock(); if (logger.isDebugEnabled()) - logger.debug("unlocking " + this.toString()); //$NON-NLS-1$ + logger.debug("unlocking " + toString()); //$NON-NLS-1$ } } diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java index f77e274ef..ed792d5f5 100644 --- a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java +++ b/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java @@ -72,7 +72,7 @@ public class SubTypeRef extends ObjectRef { public File getPath(PathBuilder pathBuilder) { return pathBuilder.getSubTypePath(this.type, this.subType); } - + @Override public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ diff --git a/src/main/java/javanet/staxutils/Indentation.java b/src/main/java/javanet/staxutils/Indentation.java index e7f554b92..844513789 100644 --- a/src/main/java/javanet/staxutils/Indentation.java +++ b/src/main/java/javanet/staxutils/Indentation.java @@ -1,39 +1,36 @@ package javanet.staxutils; /** - * Characters that represent line breaks and indentation. These are represented - * as String-valued JavaBean properties. + * 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. */ + /** 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); + /** + * 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(); + /** The characters used for one level of indentation. */ + String getIndent(); - /** - * "\n"; the normalized representation of end-of-line in XML. - */ - public static final String NORMAL_END_OF_LINE = "\n"; + /** + * "\n"; the normalized representation of end-of-line in XML. + */ + public static final String NORMAL_END_OF_LINE = "\n"; - /** - * Set the characters that introduce a new line. The default is - * {@link #NORMAL_END_OF_LINE}. - * {@link IndentingXMLStreamWriter#getLineSeparator}() is a popular - * alternative. - */ - public void setNewLine(String newLine); + /** + * 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(); + /** The characters that introduce a new line. */ + String getNewLine(); } diff --git a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java index aa9ffc3b6..f4af62c62 100644 --- a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java +++ b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java @@ -37,9 +37,8 @@ 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: + * A filter that indents an XML stream. To apply it, construct a filter that contains another {@link XMLStreamWriter}, + * which you pass to the constructor. Then call methods of the filter instead of the contained stream. For example: * *
        * {@link XMLStreamWriter} stream = ...
      @@ -49,22 +48,19 @@ import javax.xml.stream.XMLStreamWriter;
        * 
      * *

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

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

      - * Indentation can be adjusted by setting the newLine and indent properties. But - * set them to whitespace only, for best results. Non-whitespace is apt to cause - * problems, for example when this class attempts to insert newLine before the + * Indentation can be adjusted by setting the newLine and indent properties. But set them to whitespace only, for best + * results. Non-whitespace is apt to cause problems, for example when this class attempts to insert newLine before the * root element. * * @author John Kristian @@ -72,307 +68,303 @@ import javax.xml.stream.XMLStreamWriter; @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); - } + public IndentingXMLStreamWriter(XMLStreamWriter out) { + this(out, DEFAULT_INDENT, NORMAL_END_OF_LINE); + } - /** How deeply nested the current scope is. The root element is depth 1. */ - private int depth = 0; // document scope + public IndentingXMLStreamWriter(XMLStreamWriter out, String indent) { + this(out, indent, NORMAL_END_OF_LINE); + } - /** stack[depth] indicates what's been written into the current scope. */ - private int[] stack = new int[] { 0, 0, 0, 0 }; // nothing written yet + public IndentingXMLStreamWriter(XMLStreamWriter out, String indent, String newLine) { + super(out); + setIndent(indent); + setNewLine(newLine); + } - private static final int WROTE_MARKUP = 1; + /** How deeply nested the current scope is. The root element is depth 1. */ + private int depth = 0; // document scope - private static final int WROTE_DATA = 2; + /** stack[depth] indicates what's been written into the current scope. */ + private int[] stack = new int[] { 0, 0, 0, 0 }; // nothing written yet - private String indent = DEFAULT_INDENT; + private static final int WROTE_MARKUP = 1; - private String newLine = NORMAL_END_OF_LINE; + private static final int WROTE_DATA = 2; - /** newLine followed by copies of indent. */ - private char[] linePrefix = null; + private String indent = DEFAULT_INDENT; - @Override + 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; - } - } + if (!indent.equals(this.indent)) { + this.indent = indent; + this.linePrefix = null; + } + } - @Override + @Override public String getIndent() { - return this.indent; - } + return this.indent; + } - @Override + @Override public void setNewLine(String newLine) { - if (!newLine.equals(this.newLine)) { - this.newLine = newLine; - this.linePrefix = null; - } - } + 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; - } + /** + * @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 + @Override public String getNewLine() { - return this.newLine; - } + return this.newLine; + } - @Override + @Override public void writeStartDocument() throws XMLStreamException { - beforeMarkup(); - this.out.writeStartDocument(); - afterMarkup(); - } + beforeMarkup(); + this.out.writeStartDocument(); + afterMarkup(); + } - @Override + @Override public void writeStartDocument(String version) throws XMLStreamException { - beforeMarkup(); - this.out.writeStartDocument(version); - afterMarkup(); - } + beforeMarkup(); + this.out.writeStartDocument(version); + afterMarkup(); + } - @Override + @Override public void writeStartDocument(String encoding, String version) throws XMLStreamException { - beforeMarkup(); - this.out.writeStartDocument(encoding, version); - afterMarkup(); - } + beforeMarkup(); + this.out.writeStartDocument(encoding, version); + afterMarkup(); + } - @Override + @Override public void writeDTD(String dtd) throws XMLStreamException { - beforeMarkup(); - this.out.writeDTD(dtd); - afterMarkup(); - } + beforeMarkup(); + this.out.writeDTD(dtd); + afterMarkup(); + } - @Override + @Override public void writeProcessingInstruction(String target) throws XMLStreamException { - beforeMarkup(); - this.out.writeProcessingInstruction(target); - afterMarkup(); - } + beforeMarkup(); + this.out.writeProcessingInstruction(target); + afterMarkup(); + } - @Override + @Override public void writeProcessingInstruction(String target, String data) throws XMLStreamException { - beforeMarkup(); - this.out.writeProcessingInstruction(target, data); - afterMarkup(); - } + beforeMarkup(); + this.out.writeProcessingInstruction(target, data); + afterMarkup(); + } - @Override + @Override public void writeComment(String data) throws XMLStreamException { - beforeMarkup(); - this.out.writeComment(data); - afterMarkup(); - } + beforeMarkup(); + this.out.writeComment(data); + afterMarkup(); + } - @Override + @Override public void writeEmptyElement(String localName) throws XMLStreamException { - beforeMarkup(); - this.out.writeEmptyElement(localName); - afterMarkup(); - } + beforeMarkup(); + this.out.writeEmptyElement(localName); + afterMarkup(); + } - @Override + @Override public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { - beforeMarkup(); - this.out.writeEmptyElement(namespaceURI, localName); - afterMarkup(); - } + 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 writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + beforeMarkup(); + this.out.writeEmptyElement(prefix, localName, namespaceURI); + afterMarkup(); + } - @Override + @Override public void writeStartElement(String localName) throws XMLStreamException { - beforeStartElement(); - this.out.writeStartElement(localName); - afterStartElement(); - } + beforeStartElement(); + this.out.writeStartElement(localName); + afterStartElement(); + } - @Override + @Override public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { - beforeStartElement(); - this.out.writeStartElement(namespaceURI, localName); - afterStartElement(); - } + 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 writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + beforeStartElement(); + this.out.writeStartElement(prefix, localName, namespaceURI); + afterStartElement(); + } - @Override + @Override public void writeCharacters(String text) throws XMLStreamException { - this.out.writeCharacters(text); - afterData(); - } + this.out.writeCharacters(text); + afterData(); + } - @Override + @Override public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { - this.out.writeCharacters(text, start, len); - afterData(); - } + this.out.writeCharacters(text, start, len); + afterData(); + } - @Override + @Override public void writeCData(String data) throws XMLStreamException { - this.out.writeCData(data); - afterData(); - } + this.out.writeCData(data); + afterData(); + } - @Override + @Override public void writeEntityRef(String name) throws XMLStreamException { - this.out.writeEntityRef(name); - afterData(); - } + this.out.writeEntityRef(name); + afterData(); + } - @Override + @Override public void writeEndElement() throws XMLStreamException { - beforeEndElement(); - this.out.writeEndElement(); - afterEndElement(); - } + beforeEndElement(); + this.out.writeEndElement(); + afterEndElement(); + } - @Override + @Override public void writeEndDocument() throws XMLStreamException { - try { - while (this.depth > 0) { - writeEndElement(); // indented - } - } catch (Exception ignored) { - ignored.printStackTrace(); - } - this.out.writeEndDocument(); - afterEndDocument(); - } + 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(); - } - } - } + /** 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 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; - } + /** 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 - } + /** 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; - } + /** 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(); - } - } - } + /** 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 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 - } + /** 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); - } - } + /** Write a line separator followed by indentation. */ + protected void writeNewLine(int indentation) throws XMLStreamException { + final int newLineLength = getNewLine().length(); + final int prefixLength = newLineLength + (getIndent().length() * indentation); + if (prefixLength > 0) { + if (this.linePrefix == null) { + this.linePrefix = (getNewLine() + getIndent()).toCharArray(); + } + while (prefixLength > this.linePrefix.length) { + // make linePrefix longer: + char[] newPrefix = new char[newLineLength + ((this.linePrefix.length - newLineLength) * 2)]; + System.arraycopy(this.linePrefix, 0, newPrefix, 0, this.linePrefix.length); + System.arraycopy(this.linePrefix, newLineLength, newPrefix, this.linePrefix.length, + this.linePrefix.length - newLineLength); + this.linePrefix = newPrefix; + } + this.out.writeCharacters(this.linePrefix, 0, prefixLength); + } + } } diff --git a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java index 49e70d88e..9922aafa7 100644 --- a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java +++ b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java @@ -36,182 +36,178 @@ 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. + * Abstract class for writing filtered XML streams. This class provides methods that merely delegate to the contained + * stream. Subclasses should override some of these methods, and may also provide additional methods and fields. * * @author John Kristian */ public abstract class StreamWriterDelegate implements XMLStreamWriter { - protected StreamWriterDelegate(XMLStreamWriter out) { - this.out = out; - } + protected StreamWriterDelegate(XMLStreamWriter out) { + this.out = out; + } - protected XMLStreamWriter out; + protected XMLStreamWriter out; - @Override + @Override public Object getProperty(String name) throws IllegalArgumentException { - return this.out.getProperty(name); - } + return this.out.getProperty(name); + } - @Override + @Override public NamespaceContext getNamespaceContext() { - return this.out.getNamespaceContext(); - } + return this.out.getNamespaceContext(); + } - @Override + @Override public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { - this.out.setNamespaceContext(context); - } + this.out.setNamespaceContext(context); + } - @Override + @Override public void setDefaultNamespace(String uri) throws XMLStreamException { - this.out.setDefaultNamespace(uri); - } + this.out.setDefaultNamespace(uri); + } - @Override + @Override public void writeStartDocument() throws XMLStreamException { - this.out.writeStartDocument(); - } + this.out.writeStartDocument(); + } - @Override + @Override public void writeStartDocument(String version) throws XMLStreamException { - this.out.writeStartDocument(version); - } + this.out.writeStartDocument(version); + } - @Override + @Override public void writeStartDocument(String encoding, String version) throws XMLStreamException { - this.out.writeStartDocument(encoding, version); - } + this.out.writeStartDocument(encoding, version); + } - @Override + @Override public void writeDTD(String dtd) throws XMLStreamException { - this.out.writeDTD(dtd); - } + this.out.writeDTD(dtd); + } - @Override + @Override public void writeProcessingInstruction(String target) throws XMLStreamException { - this.out.writeProcessingInstruction(target); - } + this.out.writeProcessingInstruction(target); + } - @Override + @Override public void writeProcessingInstruction(String target, String data) throws XMLStreamException { - this.out.writeProcessingInstruction(target, data); - } + this.out.writeProcessingInstruction(target, data); + } - @Override + @Override public void writeComment(String data) throws XMLStreamException { - this.out.writeComment(data); - } + this.out.writeComment(data); + } - @Override + @Override public void writeEmptyElement(String localName) throws XMLStreamException { - this.out.writeEmptyElement(localName); - } + this.out.writeEmptyElement(localName); + } - @Override + @Override public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { - this.out.writeEmptyElement(namespaceURI, localName); - } + 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 writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + this.out.writeEmptyElement(prefix, localName, namespaceURI); + } - @Override + @Override public void writeStartElement(String localName) throws XMLStreamException { - this.out.writeStartElement(localName); - } + this.out.writeStartElement(localName); + } - @Override + @Override public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { - this.out.writeStartElement(namespaceURI, localName); - } + 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 writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + this.out.writeStartElement(prefix, localName, namespaceURI); + } - @Override + @Override public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { - this.out.writeDefaultNamespace(namespaceURI); - } + this.out.writeDefaultNamespace(namespaceURI); + } - @Override + @Override public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { - this.out.writeNamespace(prefix, namespaceURI); - } + this.out.writeNamespace(prefix, namespaceURI); + } - @Override + @Override public String getPrefix(String uri) throws XMLStreamException { - return this.out.getPrefix(uri); - } + return this.out.getPrefix(uri); + } - @Override + @Override public void setPrefix(String prefix, String uri) throws XMLStreamException { - this.out.setPrefix(prefix, uri); - } + this.out.setPrefix(prefix, uri); + } - @Override + @Override public void writeAttribute(String localName, String value) throws XMLStreamException { - this.out.writeAttribute(localName, value); - } + 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 namespaceURI, String localName, String value) throws XMLStreamException { + this.out.writeAttribute(namespaceURI, localName, value); + } - @Override + @Override public void writeAttribute(String prefix, String namespaceURI, String localName, String value) - throws XMLStreamException { - this.out.writeAttribute(prefix, namespaceURI, localName, value); - } + throws XMLStreamException { + this.out.writeAttribute(prefix, namespaceURI, localName, value); + } - @Override + @Override public void writeCharacters(String text) throws XMLStreamException { - this.out.writeCharacters(text); - } + this.out.writeCharacters(text); + } - @Override + @Override public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { - this.out.writeCharacters(text, start, len); - } + this.out.writeCharacters(text, start, len); + } - @Override + @Override public void writeCData(String data) throws XMLStreamException { - this.out.writeCData(data); - } + this.out.writeCData(data); + } - @Override + @Override public void writeEntityRef(String name) throws XMLStreamException { - this.out.writeEntityRef(name); - } + this.out.writeEntityRef(name); + } - @Override + @Override public void writeEndElement() throws XMLStreamException { - this.out.writeEndElement(); - } + this.out.writeEndElement(); + } - @Override + @Override public void writeEndDocument() throws XMLStreamException { - this.out.writeEndDocument(); - } + this.out.writeEndDocument(); + } - @Override + @Override public void flush() throws XMLStreamException { - this.out.flush(); - } + this.out.flush(); + } - @Override + @Override public void close() throws XMLStreamException { - this.out.close(); - } + this.out.close(); + } } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java index 68de2d5a9..906d61a4c 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -60,7 +60,7 @@ public class LockingTest extends AbstractPersistenceTest { properties.setProperty(PersistenceConstants.PROP_LOCK_TIME_MILLIS, Long.toString(500L)); setup(properties); - this.waitForWorkersTime = LockableObject.getLockTime() + this.getWaitForWorkersTime() + 300L; + this.waitForWorkersTime = LockableObject.getLockTime() + getWaitForWorkersTime() + 300L; } @Test @@ -128,10 +128,10 @@ public class LockingTest extends AbstractPersistenceTest { private int runWorkers(List workers) throws InterruptedException { - this.setRun(true); + setRun(true); for (AbstractWorker worker : workers) { - worker.join(this.getWaitForWorkersTime() + 2000L); + worker.join(getWaitForWorkersTime() + 2000L); } int nrOfSuccess = 0; @@ -169,7 +169,7 @@ public class LockingTest extends AbstractPersistenceTest { public void run() { logger.info("Waiting for ok to work..."); //$NON-NLS-1$ - while (!LockingTest.this.isRun()) { + while (!isRun()) { try { Thread.sleep(10L); } catch (InterruptedException e) { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java index f05e83215..690cf4e6f 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java @@ -47,8 +47,8 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.utils.exceptions.XmlException; import ch.eitchnet.utils.helper.XmlHelper; import ch.eitchnet.xmlpers.test.model.ModelBuilder; -import ch.eitchnet.xmlpers.test.model.MyParameter; import ch.eitchnet.xmlpers.test.model.MyModel; +import ch.eitchnet.xmlpers.test.model.MyParameter; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java index f7ede8f27..30859879e 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java @@ -23,8 +23,8 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.test.model.MyParameter; import ch.eitchnet.xmlpers.test.model.MyModel; +import ch.eitchnet.xmlpers.test.model.MyParameter; import ch.eitchnet.xmlpers.util.DomUtil; public class MyModelDomParser implements DomParser { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java index 1e8efcc2b..858be533a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java @@ -23,8 +23,8 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.SaxParser; import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; -import ch.eitchnet.xmlpers.test.model.MyParameter; import ch.eitchnet.xmlpers.test.model.MyModel; +import ch.eitchnet.xmlpers.test.model.MyParameter; class MyModelSaxParser extends DefaultHandler implements SaxParser { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java index 229382476..be4d9ff78 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -46,7 +46,7 @@ public class ModelBuilder { public static MyModel createResource() { return createResource(RES_ID, RES_NAME, RES_TYPE); } - + public static MyModel createResource(String id) { return createResource(id, RES_NAME, RES_TYPE); } diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java b/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java index f61a0fa62..2bac6fdf5 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java @@ -73,7 +73,8 @@ public class MyModel { } /** - * @param id the id to set + * @param id + * the id to set */ public void setId(String id) { this.id = id; @@ -87,7 +88,8 @@ public class MyModel { } /** - * @param name the name to set + * @param name + * the name to set */ public void setName(String name) { this.name = name; @@ -101,7 +103,8 @@ public class MyModel { } /** - * @param type the type to set + * @param type + * the type to set */ public void setType(String type) { this.type = type; From a70b4309860cd3da8f22b20b4589973c115bb238 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 19 Sep 2014 21:12:03 +0200 Subject: [PATCH 326/457] [Minor] set parent version to 1.0.0-SNAPSHOT --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ad8d85972..09a997b1a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,11 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0 + 1.0.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml ch.eitchnet.privilege - 1.0.0-SNAPSHOT jar ch.eitchnet.privilege https://github.com/eitchnet/ch.eitchnet.privilege From 54092c87d4a5e3eaf0c4cae894367cfafaecad15 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 19 Sep 2014 21:12:07 +0200 Subject: [PATCH 327/457] [Minor] set parent version to 1.0.0-SNAPSHOT --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4b32d8547..0b2e5780e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,11 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0 + 1.0.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml ch.eitchnet.utils - 1.0.0-SNAPSHOT jar ch.eitchnet.utils These utils contain project independent helper classes and utilities for reuse From 9d87fded75a2789b88bce8ee102174b1150c5959 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 19 Sep 2014 21:12:10 +0200 Subject: [PATCH 328/457] [Minor] set parent version to 1.0.0-SNAPSHOT --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5cddc9a21..34082d1d2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,11 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0 + 1.0.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml ch.eitchnet.xmlpers - 1.0.0-SNAPSHOT jar ch.eitchnet.xmlpers https://github.com/eitchnet/ch.eitchnet.xmlpers From cf1a6135350c6097aad14bfc98a93fdfe5f4d697 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 19 Sep 2014 21:53:09 +0200 Subject: [PATCH 329/457] [Minor] cleanup --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 34082d1d2..613eb07f3 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,6 @@ - org.apache.maven.plugins maven-eclipse-plugin From afcb7c41fc57548f76857a4dd2b868ca0d5b042b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 20 Sep 2014 00:35:00 +0200 Subject: [PATCH 330/457] [Project] clean up --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0b2e5780e..d784a611d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + + 4.0.0 From c8816472fcd89225178b66fad782ad49f83f181d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 20 Sep 2014 00:35:00 +0200 Subject: [PATCH 331/457] [Project] clean up --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 09a997b1a..1da7a2503 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + + 4.0.0 From c3fe4bdec64d1eb56c28f9df0e19a7c7bbc570c7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 20 Sep 2014 00:35:00 +0200 Subject: [PATCH 332/457] [Project] clean up --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 613eb07f3..1c3042f59 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + + 4.0.0 From 5a2b62bd19f4f6132d0d075ec0db03d17e2fcf79 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 26 Sep 2014 17:18:35 +0200 Subject: [PATCH 333/457] [Minor] changed error message on failed to parse of ISO8601 --- src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java index 327e67b2c..9f66894a1 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java @@ -291,7 +291,7 @@ public class ISO8601 implements DateFormat { return cal.getTime(); } - String msg = "Input string " + s + " cannot be parsed to date."; + String msg = "Input string '" + s + "' cannot be parsed to date."; throw new IllegalArgumentException(msg); } } From aa16887d674876a160bb8002a8d7899c85b2f7b1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 26 Sep 2014 18:23:23 +0200 Subject: [PATCH 334/457] [New] Added new Certificate.getProperty()-method --- .../ch/eitchnet/privilege/model/Certificate.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 9cb3d9ada..51f264583 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -117,6 +117,18 @@ public final class Certificate implements Serializable { return this.propertyMap; } + /** + * Returns the property with the given key + * + * @param key + * the key for which the property is to be returned + * + * @return the value of the property with the given key, or null if it does not exist + */ + public String getProperty(String key) { + return this.propertyMap.get(key); + } + /** * Returns a mutable {@link Map} for storing session relevant data * From 67271d611ee66d67477843ca7fc388e5fb50750c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 27 Sep 2014 12:23:38 +0200 Subject: [PATCH 335/457] [New] Added Certificate.getLastAccess() and PrivilegeHandler.checkPassword() --- .../handler/DefaultPrivilegeHandler.java | 102 +++++++++++------- .../privilege/handler/PrivilegeHandler.java | 14 +++ .../eitchnet/privilege/model/Certificate.java | 17 +++ 3 files changed, 97 insertions(+), 36 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index e3ec04f6f..1a74f217e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -622,42 +622,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException(msg); } - // and validate the password - validatePassword(password); - - // we only work with hashed passwords - String passwordHash = this.encryptionHandler.convertToHash(password); - - // get user object - User user = this.persistenceHandler.getUser(username); - // no user means no authentication - if (user == null) { - String msg = MessageFormat.format("There is no user defined with the username {0}", username); //$NON-NLS-1$ - throw new AccessDeniedException(msg); - } - - // make sure not a system user - they may not login in - if (user.getUserState() == UserState.SYSTEM) { - String msg = "User {0} is a system user and may not login!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, username); - throw new AccessDeniedException(msg); - } - - // validate password - String pwHash = user.getPassword(); - if (pwHash == null) - throw new AccessDeniedException(MessageFormat.format( - "User {0} has no password and may not login!", username)); //$NON-NLS-1$ - if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ - - // validate if user is allowed to login - // this also capture the trying to login of SYSTEM user - if (user.getUserState() != UserState.ENABLED) { - String msg = "User {0} does not have state {1} and can not login!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, username, UserState.ENABLED); - throw new AccessDeniedException(msg); - } + // check the password + User user = checkCredentialsAndUserState(username, password); // validate user has at least one role Set userRoles = user.getRoles(); @@ -696,6 +662,60 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return certificate; } + /** + * Checks the credentials and validates that the user may log in. + * + * @param username + * the username of the {@link User} to check against + * @param password + * the password of this user + * + * @return the {@link User} if the credentials are valid and the user may login + * + * @throws AccessDeniedException + * if anything is wrong with the credentials or the user state + */ + private User checkCredentialsAndUserState(String username, byte[] password) throws AccessDeniedException { + + // and validate the password + validatePassword(password); + + // we only work with hashed passwords + String passwordHash = this.encryptionHandler.convertToHash(password); + + // get user object + User user = this.persistenceHandler.getUser(username); + // no user means no authentication + if (user == null) { + String msg = MessageFormat.format("There is no user defined with the username {0}", username); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } + + // make sure not a system user - they may not login in + if (user.getUserState() == UserState.SYSTEM) { + String msg = "User {0} is a system user and may not login!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username); + throw new AccessDeniedException(msg); + } + + // validate password + String pwHash = user.getPassword(); + if (pwHash == null) + throw new AccessDeniedException(MessageFormat.format( + "User {0} has no password and may not login!", username)); //$NON-NLS-1$ + if (!pwHash.equals(passwordHash)) + throw new AccessDeniedException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ + + // validate if user is allowed to login + // this also capture the trying to login of SYSTEM user + if (user.getUserState() != UserState.ENABLED) { + String msg = "User {0} does not have state {1} and can not login!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username, UserState.ENABLED); + throw new AccessDeniedException(msg); + } + return user; + } + /** * Builds a {@link PrivilegeContext} for the given {@link User} and its {@link Certificate} * @@ -801,6 +821,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // everything is ok } + @Override + public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException { + try { + isCertificateValid(certificate); + checkCredentialsAndUserState(certificate.getUsername(), password); + } finally { + clearPassword(password); + } + } + @Override public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException { diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index d527fd171..cf472d41e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -351,6 +351,20 @@ public interface PrivilegeHandler { */ public void isCertificateValid(Certificate certificate) throws PrivilegeException; + /** + * Checks that the given password belongs to the given {@link Certificate}. If it doesn't, then a + * {@link PrivilegeException} is thrown + * + * @param certificate + * the certificate for which to check the password + * @param password + * the password to check against the user from the certificate + * + * @throws PrivilegeException + * if the certificate is invalid or the password does not match + */ + public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException; + /** * Returns the {@link PrivilegeContext} for the given {@link Certificate}. The {@link PrivilegeContext} is an * encapsulated state of a user's privileges so that for the duration of a user's call, the user can perform their diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 51f264583..6e48ae4ec 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -45,6 +45,7 @@ public final class Certificate implements Serializable { private final String authToken; private Locale locale; + private long lastAccess; private Map propertyMap; private Map sessionDataMap; @@ -197,6 +198,21 @@ public final class Certificate implements Serializable { return this.authToken; } + /** + * @return the lastAccess + */ + public long getLastAccess() { + return this.lastAccess; + } + + /** + * @param lastAccess + * the lastAccess to set + */ + public void setLastAccess(long lastAccess) { + this.lastAccess = lastAccess; + } + /** * Returns a string representation of this object displaying its concrete type and its values * @@ -223,6 +239,7 @@ public final class Certificate implements Serializable { builder.append(", locale="); builder.append(this.locale); + builder.append("]"); return builder.toString(); } From 906d24d02bbb4c668bd6c97338c1c813d36cda70 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 4 Oct 2014 23:56:23 +0200 Subject: [PATCH 336/457] [New] Extended FormatFactory to have parse*()-methods --- .../eitchnet/utils/iso8601/FormatFactory.java | 40 ++ .../utils/iso8601/ISO8601Duration.java | 528 +++++++++--------- .../utils/iso8601/ISO8601FormatFactory.java | 198 ++++--- 3 files changed, 413 insertions(+), 353 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java index 6dbf336f2..e728ebe38 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java @@ -70,6 +70,16 @@ public interface FormatFactory { */ 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()} * @@ -99,4 +109,34 @@ public interface FormatFactory { * @return the floating point formatted as a string */ public String formatFloat(double value); + + /** + * Parses a date using {@link #getDateFormat()} + * + * @param date + * the string to parse to date + * + * @return the date + */ + public Date parseDate(String date); + + /** + * Parses a duration using {@link #getDateFormat()} + * + * @param duration + * the string to parse to duration + * + * @return the duration + */ + public long parseDuration(String duration); + + /** + * Parses a work time duration using {@link #getDateFormat()} + * + * @param worktime + * the string duration to parse to work time + * + * @return the work time + */ + public long parseWorktime(String worktime); } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java index 9d4ae3320..67c147e5c 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java @@ -1,264 +1,264 @@ -/* - * Copyright 2013 Martin Smock - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.iso8601; - -/** - *

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

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

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

      - *

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

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

      - * Thus, a duration of 86401000 (one day and one second) will add the representation of one day if unit is DAY (1D) - * and return 1000 as the remainder with respect of this unit. If the given unit is HOUR, then this function adds - * 24H to the {@link StringBuilder}, and returns 1000 as the remainder. - * - * @param sb - * The {@link StringBuilder} to add the given duration with the right unit - * @param duration - * The duration to add - * @param unit - * The unit of this duration - * @return The remainder of the given duration, modulo the time unit. - */ - private long formatTimeDuration(StringBuilder sb, long duration, TimeDuration unit) { - - long remainder = duration; - if (unit.equals(TimeDuration.SECOND) || remainder >= unit.duration()) { - - long quantity = remainder / unit.duration(); - remainder = remainder % unit.duration(); - sb.append(quantity); - - if (unit.equals(TimeDuration.SECOND)) { - long millis = remainder; - if (millis == 0) { - // to not have the decimal point - } else if (millis > 99) { - sb.append("." + millis); - } else if (millis > 9) { - sb.append(".0" + millis); - } else { - sb.append(".00" + millis); - } - } - - sb.append(unit.isoChar); - } - - return remainder; - } - - /** - * Formats the given time duration to a pseudo ISO 8601 duration string - * - * @param duration - * @return String the duration formatted as a ISO8601 duration string - */ - @Override - public String format(long duration) { - - // XXX this is a preliminary help to solve the situation where this method sometimes returns P - if (duration < 0l) - throw new RuntimeException("A duration can not be negative!"); - - if (duration == 0l) - return "PT0S"; - - 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(); - } - -} +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.iso8601; + +/** + *

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

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

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

      + *

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

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

      + * Thus, a duration of 86401000 (one day and one second) will add the representation of one day if unit is DAY (1D) + * and return 1000 as the remainder with respect of this unit. If the given unit is HOUR, then this function adds + * 24H to the {@link StringBuilder}, and returns 1000 as the remainder. + * + * @param sb + * The {@link StringBuilder} to add the given duration with the right unit + * @param duration + * The duration to add + * @param unit + * The unit of this duration + * @return The remainder of the given duration, modulo the time unit. + */ + private long formatTimeDuration(StringBuilder sb, long duration, TimeDuration unit) { + + long remainder = duration; + if (unit.equals(TimeDuration.SECOND) || remainder >= unit.duration()) { + + long quantity = remainder / unit.duration(); + remainder = remainder % unit.duration(); + sb.append(quantity); + + if (unit.equals(TimeDuration.SECOND)) { + long millis = remainder; + if (millis == 0) { + // to not have the decimal point + } else if (millis > 99) { + sb.append("." + millis); + } else if (millis > 9) { + sb.append(".0" + millis); + } else { + sb.append(".00" + millis); + } + } + + sb.append(unit.isoChar); + } + + return remainder; + } + + /** + * Formats the given time duration to a pseudo ISO 8601 duration string + * + * @param duration + * @return String the duration formatted as a ISO8601 duration string + */ + @Override + public String format(long duration) { + + // XXX this is a preliminary help to solve the situation where this method sometimes returns P + if (duration < 0l) + throw new RuntimeException("A duration can not be negative!"); + + if (duration == 0l) + return "PT0S"; + + StringBuilder sb = new StringBuilder(); + sb.append('P'); + + long remainder = formatTimeDuration(sb, duration, TimeDuration.YEAR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MONTH); + remainder = formatTimeDuration(sb, remainder, TimeDuration.DAY); + if (remainder > 0) { + sb.append('T'); + remainder = formatTimeDuration(sb, remainder, TimeDuration.HOUR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MINUTE); + remainder = formatTimeDuration(sb, remainder, TimeDuration.SECOND); + } + return sb.toString(); + } + +} diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java index c8ea878c7..2cc57f670 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java @@ -1,89 +1,109 @@ -/* - * Copyright 2013 Martin Smock - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.iso8601; - -import java.util.Date; - -import ch.eitchnet.utils.helper.MathHelper; - -/** - * Default factory for date formats used for serialization. - * - * @author Martin Smock - */ -public class ISO8601FormatFactory implements FormatFactory { - - private static ISO8601FormatFactory instance = new ISO8601FormatFactory(); - - /** - * the singleton constructor - */ - private ISO8601FormatFactory() { - // singleton - } - - /** - * @return the instance - */ - public static ISO8601FormatFactory getInstance() { - return instance; - } - - @Override - public ISO8601 getDateFormat() { - return new ISO8601(); - } - - @Override - public ISO8601Duration getDurationFormat() { - return new ISO8601Duration(); - } - - @Override - public ISO8601Worktime getWorktimeFormat() { - return new ISO8601Worktime(); - } - - @Override - public ISO8601 getXmlDateFormat() { - return new ISO8601(); - } - - @Override - public ISO8601Duration getXmlDurationFormat() { - return new ISO8601Duration(); - } - - @Override - public String formatDate(Date date) { - return getDateFormat().format(date); - } - - @Override - public String 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)); - } -} +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.iso8601; + +import java.util.Date; + +import ch.eitchnet.utils.helper.MathHelper; + +/** + * Default factory for date formats used for serialization. + * + * @author Martin Smock + */ +public class ISO8601FormatFactory implements FormatFactory { + + private static ISO8601FormatFactory instance = new ISO8601FormatFactory(); + + /** + * the singleton constructor + */ + private ISO8601FormatFactory() { + // singleton + } + + /** + * @return the instance + */ + public static ISO8601FormatFactory getInstance() { + return instance; + } + + @Override + public ISO8601 getDateFormat() { + return new ISO8601(); + } + + @Override + public ISO8601Duration getDurationFormat() { + return new ISO8601Duration(); + } + + @Override + public ISO8601Worktime getWorktimeFormat() { + return new ISO8601Worktime(); + } + + @Override + public ISO8601 getXmlDateFormat() { + return new ISO8601(); + } + + @Override + public ISO8601Duration getXmlDurationFormat() { + return new ISO8601Duration(); + } + + @Override + public String formatDate(Date date) { + return getDateFormat().format(date); + } + + @Override + public String formatDate(long date) { + return getDateFormat().format(date); + } + + @Override + public String formatDuration(long duration) { + return getDurationFormat().format(duration); + } + + @Override + public String formatWorktime(long worktime) { + return getDurationFormat().format(worktime); + } + + @Override + public String formatFloat(double value) { + return Double.toString(MathHelper.toPrecision(value)); + } + + @Override + public Date parseDate(String date) { + return getDateFormat().parse(date); + } + + @Override + public long parseDuration(String duration) { + return getDurationFormat().parse(duration); + } + + @Override + public long parseWorktime(String worktime) { + return getDurationFormat().parse(worktime); + } +} From 84a24feb894568b6e4820f11819d4cafa603e0ac Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 14 Nov 2014 00:04:07 +0100 Subject: [PATCH 337/457] [New] Added package 'db' with utils for schema CUD DbSchemaVersionCheck is used in combination with DbConnectionInfo to create schemas from SQL scripts. Using DbConnectionCheck.checkSchemaVersion() schemas are created or migrated. There is also functionality to drop schemas --- .../ch/eitchnet/db/DbConnectionCheck.java | 78 +++++ .../java/ch/eitchnet/db/DbConnectionInfo.java | 132 ++++++++ src/main/java/ch/eitchnet/db/DbConstants.java | 32 ++ .../java/ch/eitchnet/db/DbDriverLoader.java | 49 +++ src/main/java/ch/eitchnet/db/DbException.java | 39 +++ .../java/ch/eitchnet/db/DbMigrationState.java | 23 ++ .../ch/eitchnet/db/DbSchemaVersionCheck.java | 306 ++++++++++++++++++ 7 files changed, 659 insertions(+) create mode 100644 src/main/java/ch/eitchnet/db/DbConnectionCheck.java create mode 100644 src/main/java/ch/eitchnet/db/DbConnectionInfo.java create mode 100644 src/main/java/ch/eitchnet/db/DbConstants.java create mode 100644 src/main/java/ch/eitchnet/db/DbDriverLoader.java create mode 100644 src/main/java/ch/eitchnet/db/DbException.java create mode 100644 src/main/java/ch/eitchnet/db/DbMigrationState.java create mode 100644 src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java diff --git a/src/main/java/ch/eitchnet/db/DbConnectionCheck.java b/src/main/java/ch/eitchnet/db/DbConnectionCheck.java new file mode 100644 index 000000000..9177757ee --- /dev/null +++ b/src/main/java/ch/eitchnet/db/DbConnectionCheck.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.db; + +import java.sql.Connection; +import java.sql.DriverManager; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + */ +public class DbConnectionCheck { + + private static final Logger logger = LoggerFactory.getLogger(DbConnectionCheck.class); + private Map connetionInfoMap; + + /** + * @param connetionInfoMap + */ + public DbConnectionCheck(Map connetionInfoMap) { + this.connetionInfoMap = connetionInfoMap; + } + + /** + * Checks the connectivity to each of the configured {@link DbConnectionInfo} + * + * @throws DbException + */ + public void checkConnections() throws DbException { + Collection values = this.connetionInfoMap.values(); + for (DbConnectionInfo connectionInfo : values) { + + String url = connectionInfo.getUrl(); + String username = connectionInfo.getUsername(); + String password = connectionInfo.getPassword(); + + logger.info("Checking connection " + username + "@" + url); + + try (Connection con = DriverManager.getConnection(url, username, password); + 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 URL {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, url, e.getMessage()); + throw new DbException(msg, e); + } + } + + logger.info("All connections OK"); + } +} diff --git a/src/main/java/ch/eitchnet/db/DbConnectionInfo.java b/src/main/java/ch/eitchnet/db/DbConnectionInfo.java new file mode 100644 index 000000000..391bb8d3f --- /dev/null +++ b/src/main/java/ch/eitchnet/db/DbConnectionInfo.java @@ -0,0 +1,132 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.db; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.text.MessageFormat; + +import ch.eitchnet.utils.dbc.DBC; + +/** + * @author Robert von Burg + */ +public class DbConnectionInfo { + + private String realm; + private String url; + private String username; + private String password; + + public DbConnectionInfo(String realm, String url) { + DBC.PRE.assertNotEmpty("Realm must be set!", realm); //$NON-NLS-1$ + DBC.PRE.assertNotEmpty("Url must be set!", url); //$NON-NLS-1$ + this.realm = realm; + this.url = url; + } + + /** + * @return the realm + */ + public String getRealm() { + return this.realm; + } + + /** + * @param realm + * the realm to set + */ + public void setRealm(String realm) { + this.realm = realm; + } + + /** + * @return the url + */ + public String getUrl() { + return this.url; + } + + /** + * @param url + * the url to set + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * @return the username + */ + public String getUsername() { + return this.username; + } + + /** + * @param username + * the username to set + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return the password + */ + public String getPassword() { + return this.password; + } + + /** + * @param password + * the password to set + */ + public void setPassword(String password) { + this.password = password; + } + + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("DbConnectionInfo [realm="); + builder.append(this.realm); + builder.append(", url="); + builder.append(this.url); + builder.append(", username="); + builder.append(this.username); + builder.append(", password=***"); + builder.append("]"); + return builder.toString(); + } + + /** + * @return a {@link Connection} + * + * @throws DbException + */ + public Connection openConnection() throws DbException { + try { + Connection connection = DriverManager.getConnection(this.url, this.username, this.password); + connection.setAutoCommit(false); + return connection; + } catch (SQLException e) { + String msg = MessageFormat.format("Failed to get a connection for {0} due to {1}", this, e.getMessage()); //$NON-NLS-1$ + throw new DbException(msg, e); + } + } +} diff --git a/src/main/java/ch/eitchnet/db/DbConstants.java b/src/main/java/ch/eitchnet/db/DbConstants.java new file mode 100644 index 000000000..ceee7a85e --- /dev/null +++ b/src/main/java/ch/eitchnet/db/DbConstants.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.db; + +/** + * @author Robert von Burg + */ +public class DbConstants { + + public static final String PROP_DB_URL = "db.url"; //$NON-NLS-1$ + public static final String PROP_DB_USERNAME = "db.username"; //$NON-NLS-1$ + public static final String PROP_DB_PASSWORD = "db.password"; //$NON-NLS-1$ + public static final String PROP_ALLOW_SCHEMA_CREATION = "allowSchemaCreation"; + public static final String PROP_ALLOW_SCHEMA_MIGRATION = "allowSchemaMigration"; + public static final String PROP_ALLOW_SCHEMA_DROP = "allowSchemaDrop"; + public static final String PROP_ALLOW_DATA_INIT_ON_SCHEMA_CREATE = "allowDataInitOnSchemaCreate"; + public static final String PROP_DB_VERSION = "db_version"; + public static final String RESOURCE_DB_VERSION = "/{0}_db_version.properties"; +} diff --git a/src/main/java/ch/eitchnet/db/DbDriverLoader.java b/src/main/java/ch/eitchnet/db/DbDriverLoader.java new file mode 100644 index 000000000..a824cce1a --- /dev/null +++ b/src/main/java/ch/eitchnet/db/DbDriverLoader.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.db; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.text.MessageFormat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + */ +public class DbDriverLoader { + + private static final Logger logger = LoggerFactory.getLogger(DbDriverLoader.class); + + public static void loadDriverForConnection(DbConnectionInfo connectionInfo) throws DbException { + Driver driver; + try { + driver = DriverManager.getDriver(connectionInfo.getUrl()); + } catch (SQLException e) { + String msg = "Failed to load DB driver for URL {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connectionInfo.getUrl(), e.getMessage()); + throw new DbException(msg, e); + } + + String compliant = driver.jdbcCompliant() ? "" : "non"; //$NON-NLS-1$ //$NON-NLS-2$ + String msg = "Realm {0}: Using {1} JDBC compliant Driver {2}.{3}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, connectionInfo.getRealm(), compliant, driver.getMajorVersion(), + driver.getMinorVersion()); + logger.info(msg); + } +} diff --git a/src/main/java/ch/eitchnet/db/DbException.java b/src/main/java/ch/eitchnet/db/DbException.java new file mode 100644 index 000000000..f1c6bb942 --- /dev/null +++ b/src/main/java/ch/eitchnet/db/DbException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.db; + +/** + * @author Robert von Burg + */ +public class DbException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * @param message + */ + public DbException(String message) { + super(message); + } + + /** + * @param message + * @param cause + */ + public DbException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/ch/eitchnet/db/DbMigrationState.java b/src/main/java/ch/eitchnet/db/DbMigrationState.java new file mode 100644 index 000000000..f937ef344 --- /dev/null +++ b/src/main/java/ch/eitchnet/db/DbMigrationState.java @@ -0,0 +1,23 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.db; + +/** + * @author Robert von Burg + */ +public enum DbMigrationState { + NOTHING, CREATED, MIGRATED, DROPPED_CREATED; +} diff --git a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java new file mode 100644 index 000000000..119729988 --- /dev/null +++ b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java @@ -0,0 +1,306 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.db; + +import static ch.eitchnet.db.DbConstants.PROP_DB_VERSION; +import static ch.eitchnet.db.DbConstants.RESOURCE_DB_VERSION; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.DriverManager; +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.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +@SuppressWarnings(value = "nls") +public class DbSchemaVersionCheck { + + private static final Logger logger = LoggerFactory.getLogger(DbSchemaVersionCheck.class); + private String scriptPrefix; + private Class ctxClass; + private boolean allowSchemaCreation; + private boolean allowSchemaMigration; + private boolean allowSchemaDrop; + private Map dbMigrationStates; + + /** + * @param scriptPrefix + * @param ctxClass + * @param allowSchemaCreation + * @param allowSchemaDrop + */ + public DbSchemaVersionCheck(String scriptPrefix, Class ctxClass, boolean allowSchemaCreation, + boolean allowSchemaMigration, boolean allowSchemaDrop) { + + DBC.PRE.assertNotEmpty("scriptPrefix may not be empty!", scriptPrefix); + DBC.PRE.assertNotNull("ctxClass may not be null!", ctxClass); + + this.scriptPrefix = scriptPrefix; + this.ctxClass = ctxClass; + this.allowSchemaCreation = allowSchemaCreation; + this.allowSchemaMigration = allowSchemaMigration; + this.allowSchemaDrop = allowSchemaDrop; + this.dbMigrationStates = new HashMap<>(); + } + + /** + * @return the dbMigrationStates + */ + public Map getDbMigrationStates() { + return this.dbMigrationStates; + } + + public void checkSchemaVersion(Map connectionInfoMap) throws DbException { + for (DbConnectionInfo connectionInfo : connectionInfoMap.values()) { + DbMigrationState dbMigrationState = checkSchemaVersion(connectionInfo); + dbMigrationStates.put(connectionInfo.getRealm(), dbMigrationState); + } + } + + /** + * Returns true if the schema existed or was only migrated, false if the schema was created + * + * @param connectionInfo + * + * @return true if the schema existed or was only migrated, false if the schema was created + * + * @throws DbException + */ + public DbMigrationState checkSchemaVersion(DbConnectionInfo connectionInfo) throws DbException { + String realm = connectionInfo.getRealm(); + String url = connectionInfo.getUrl(); + String username = connectionInfo.getUsername(); + String password = connectionInfo.getPassword(); + + logger.info(MessageFormat.format("[{0}] Checking Schema version for: {1}@{2}", realm, username, url)); + + DbMigrationState migrationType; + + try (Connection con = DriverManager.getConnection(url, username, password); + Statement st = con.createStatement();) { + + String expectedDbVersion = getExpectedDbVersion(this.scriptPrefix, this.ctxClass); + + // first see if we have any schema + String msg = "select table_schema, table_name, table_type from information_schema.tables where table_name=''{0}'';"; + String checkSchemaExistsSql = MessageFormat.format(msg, PROP_DB_VERSION); + try (ResultSet rs = st.executeQuery(checkSchemaExistsSql)) { + if (!rs.next()) { + migrationType = createSchema(realm, expectedDbVersion, st); + } else { + migrationType = checkCurrentVersion(realm, st, expectedDbVersion); + } + } + + return migrationType; + + } catch (SQLException e) { + String msg = "Failed to open DB connection to URL {0} due to: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, url, e.getMessage()); + throw new DbException(msg, e); + } + } + + /** + * @param realm + * @param st + * @param expectedDbVersion + * + * @return + * + * @throws SQLException + * @throws DbException + */ + public DbMigrationState checkCurrentVersion(String realm, Statement st, String expectedDbVersion) + throws SQLException, DbException { + try (ResultSet rs = st.executeQuery("select id, version from db_version order by id desc;")) { + if (!rs.next()) { + return createSchema(realm, expectedDbVersion, st); + } else { + String currentVersion = rs.getString(2); + if (expectedDbVersion.equals(currentVersion)) { + String msg = "[{0}] Schema version {1} is the current version. No changes needed."; + msg = MessageFormat.format(msg, realm, currentVersion); + logger.info(msg); + return DbMigrationState.NOTHING; + } else { + String msg = "[{0}] Schema version is not current. Need to upgrade from {1} to {2}"; + msg = MessageFormat.format(msg, realm, currentVersion, expectedDbVersion); + logger.warn(msg); + return migrateSchema(realm, expectedDbVersion, st); + } + } + } + } + + public static String getExpectedDbVersion(String prefix, Class ctxClass) throws DbException { + Properties dbVersionProps = new Properties(); + + String dbVersionPropFile = MessageFormat.format(RESOURCE_DB_VERSION, prefix); + + 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 dbVersion; + } + + /** + * @param scriptPrefix + * @param classLoader + * @param dbVersion + * @param type + * + * @return + * + * @throws DbException + */ + public static String getSql(String scriptPrefix, Class ctxClass, String 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 DbMigrationState createSchema(String realm, String dbVersion, Statement st) throws DbException { + + if (!this.allowSchemaCreation) { + String msg = "[{0}] No schema exists, or is not valid. Schema generation is disabled, thus can not continue!"; + msg = MessageFormat.format(msg, realm); + throw new DbException(msg); + } + + logger.info(MessageFormat.format("[{0}] Creating initial schema...", realm)); + + String sql = getSql(this.scriptPrefix, this.ctxClass, dbVersion, "initial"); + try { + 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}] Successfully created schema for version {1}", realm, dbVersion)); + return DbMigrationState.CREATED; + } + + /** + * @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(String realm, String dbVersion, Statement st) throws DbException { + + if (!this.allowSchemaDrop) { + String msg = "[{0}] Dropping Schema is disabled, but is required to upgrade current schema..."; + msg = MessageFormat.format(msg, realm); + throw new DbException(msg); + } + + logger.info(MessageFormat.format("[{0}] Dropping existing schema...", realm)); + + String sql = getSql(this.scriptPrefix, this.ctxClass, dbVersion, "drop"); + try { + 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); + } + } + + /** + * 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 + * @param st + * the open database {@link Statement} to which the SQL statements will be written + * + * @return true if the schema was recreated, false if it was simply migrated + * + * @throws DbException + */ + public DbMigrationState migrateSchema(String realm, String dbVersion, Statement st) throws DbException { + + if (!this.allowSchemaMigration) { + String msg = "[{0}] Schema is not valid. Schema migration is disabled, thus can not continue!"; + msg = MessageFormat.format(msg, realm); + throw new DbException(msg); + } + + logger.info(MessageFormat.format("[{0}] Migrating schema...", realm)); + + String sql = getSql(this.scriptPrefix, this.ctxClass, dbVersion, "migration"); + try { + 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}] Successfully migrated schema to version {1}", realm, dbVersion)); + return DbMigrationState.MIGRATED; + } +} From 3f6f9950f856dd995ca1d3581aabe30ba3db910e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 28 Nov 2014 22:20:50 +0100 Subject: [PATCH 338/457] [New] Created new exception formatting method when only message needed --- .../eitchnet/utils/helper/StringHelper.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 526f63f4a..c6306e61c 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -578,6 +578,36 @@ public class StringHelper { 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) { + StringBuilder sb = new StringBuilder(); + sb.append(t.getMessage()); + sb.append("\n"); + + appendCause(sb, t); + return sb.toString(); + } + + private static void appendCause(StringBuilder sb, Throwable e) { + Throwable cause = e.getCause(); + if (cause == null) + return; + + sb.append("cause:\n"); + sb.append(cause.getMessage()); + sb.append("\n"); + + if (cause.getCause() != null) + appendCause(sb, cause.getCause()); + } + /** * Simply returns true if the value is null, or empty * From 87239afa2a5c39ea24f2bb235280a56c6f14f6ac Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 31 Dec 2014 16:38:00 +0100 Subject: [PATCH 339/457] [New] added certificate.getUserRoles() --- .../handler/DefaultPrivilegeHandler.java | 10 +++++----- .../ch/eitchnet/privilege/model/Certificate.java | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 1a74f217e..185c79adb 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -135,7 +135,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set selRoles = selectorRep.getRoles(); Map selPropertyMap = selectorRep.getProperties(); - List result = new ArrayList(); + List result = new ArrayList<>(); List allUsers = this.persistenceHandler.getAllUsers(); for (User user : allUsers) { @@ -342,7 +342,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); // copy existing privileges Set existingPrivilegeNames = role.getPrivilegeNames(); - Map privilegeMap = new HashMap(existingPrivilegeNames.size() + 1); + Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); for (String name : existingPrivilegeNames) { IPrivilege privilege = role.getPrivilege(name); privilegeMap.put(name, privilege); @@ -383,7 +383,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - Set newRoles = new HashSet(currentRoles); + Set newRoles = new HashSet<>(currentRoles); newRoles.add(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), @@ -640,7 +640,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create a new certificate, with details of the user certificate = new Certificate(sessionId, System.currentTimeMillis(), username, user.getFirstname(), - user.getLastname(), authToken, user.getLocale(), new HashMap(user.getProperties())); + user.getLastname(), authToken, user.getLocale(), userRoles, new HashMap<>(user.getProperties())); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); @@ -1085,7 +1085,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - null, null, authToken, user.getLocale(), new HashMap(user.getProperties())); + null, null, authToken, user.getLocale(), user.getRoles(), new HashMap<>(user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 6e48ae4ec..a479f2bd4 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; @@ -47,6 +48,7 @@ public final class Certificate implements Serializable { private Locale locale; private long lastAccess; + private Set userRoles; private Map propertyMap; private Map sessionDataMap; @@ -70,12 +72,14 @@ public final class Certificate implements Serializable { * the authentication token defining the users unique session and is a private field of this certificate. * @param locale * the users {@link Locale} + * @param userRoles + * the user's roles * @param propertyMap * a {@link Map} containing string value pairs of properties for the logged in user. These properties can * be edited and can be used for the user to change settings of this session */ public Certificate(String sessionId, long loginTime, String username, String firstname, String lastname, - String authToken, Locale locale, Map propertyMap) { + String authToken, Locale locale, Set userRoles, Map propertyMap) { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { @@ -106,9 +110,19 @@ public final class Certificate implements Serializable { else this.propertyMap = Collections.unmodifiableMap(propertyMap); + this.userRoles = Collections.unmodifiableSet(userRoles); this.sessionDataMap = new HashMap<>(); } + /** + * Returns the set or roles this user has + * + * @return the user's roles + */ + public Set getUserRoles() { + return userRoles; + } + /** * Returns the {@link User User's} property map. The map is immutable * From 07b574dba13ae7ca1000737dc799f6fd3885bd35 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 9 Jan 2015 12:32:18 +0100 Subject: [PATCH 340/457] [Major] refactored DbSchemaVersionCheck and added version parsing - Now we can use Version to see if current version is greater than - Fixed bug where version checking didn't check for app - TODO is still open to handle migration with intermediary steps --- .../ch/eitchnet/db/DbSchemaVersionCheck.java | 216 +++++--- src/main/java/ch/eitchnet/utils/Version.java | 491 ++++++++++++++++++ .../java/ch/eitchnet/utils/VersionTest.java | 78 +++ 3 files changed, 701 insertions(+), 84 deletions(-) create mode 100644 src/main/java/ch/eitchnet/utils/Version.java create mode 100644 src/test/java/ch/eitchnet/utils/VersionTest.java diff --git a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java index 119729988..08935f24f 100644 --- a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java +++ b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -33,6 +34,7 @@ import java.util.Properties; 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; @@ -43,7 +45,7 @@ import ch.eitchnet.utils.helper.FileHelper; public class DbSchemaVersionCheck { private static final Logger logger = LoggerFactory.getLogger(DbSchemaVersionCheck.class); - private String scriptPrefix; + private String app; private Class ctxClass; private boolean allowSchemaCreation; private boolean allowSchemaMigration; @@ -51,18 +53,18 @@ public class DbSchemaVersionCheck { private Map dbMigrationStates; /** - * @param scriptPrefix + * @param app * @param ctxClass * @param allowSchemaCreation * @param allowSchemaDrop */ - public DbSchemaVersionCheck(String scriptPrefix, Class ctxClass, boolean allowSchemaCreation, + public DbSchemaVersionCheck(String app, Class ctxClass, boolean allowSchemaCreation, boolean allowSchemaMigration, boolean allowSchemaDrop) { - DBC.PRE.assertNotEmpty("scriptPrefix may not be empty!", scriptPrefix); + DBC.PRE.assertNotEmpty("app may not be empty!", app); DBC.PRE.assertNotNull("ctxClass may not be null!", ctxClass); - this.scriptPrefix = scriptPrefix; + this.app = app; this.ctxClass = ctxClass; this.allowSchemaCreation = allowSchemaCreation; this.allowSchemaMigration = allowSchemaMigration; @@ -101,22 +103,27 @@ public class DbSchemaVersionCheck { logger.info(MessageFormat.format("[{0}] Checking Schema version for: {1}@{2}", realm, username, url)); - DbMigrationState migrationType; + Version expectedDbVersion = getExpectedDbVersion(this.app, this.ctxClass); - try (Connection con = DriverManager.getConnection(url, username, password); - Statement st = con.createStatement();) { + try (Connection con = DriverManager.getConnection(url, username, password)) { - String expectedDbVersion = getExpectedDbVersion(this.scriptPrefix, this.ctxClass); + // get current version + Version currentVersion = getCurrentVersion(con, this.app); + DbMigrationState migrationType = detectMigrationState(realm, expectedDbVersion, currentVersion); - // first see if we have any schema - String msg = "select table_schema, table_name, table_type from information_schema.tables where table_name=''{0}'';"; - String checkSchemaExistsSql = MessageFormat.format(msg, PROP_DB_VERSION); - try (ResultSet rs = st.executeQuery(checkSchemaExistsSql)) { - if (!rs.next()) { - migrationType = createSchema(realm, expectedDbVersion, st); - } else { - migrationType = checkCurrentVersion(realm, st, expectedDbVersion); - } + 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; } return migrationType; @@ -128,9 +135,39 @@ public class DbSchemaVersionCheck { } } + /** + * @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(0, 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(0, app); + ResultSet rs = st.executeQuery(); + if (rs.next()) + currentVersion = Version.valueOf(rs.getString(2)); + } + + return currentVersion; + } + /** * @param realm - * @param st * @param expectedDbVersion * * @return @@ -138,32 +175,43 @@ public class DbSchemaVersionCheck { * @throws SQLException * @throws DbException */ - public DbMigrationState checkCurrentVersion(String realm, Statement st, String expectedDbVersion) + public DbMigrationState detectMigrationState(String realm, Version expectedDbVersion, Version currentVersion) throws SQLException, DbException { - try (ResultSet rs = st.executeQuery("select id, version from db_version order by id desc;")) { - if (!rs.next()) { - return createSchema(realm, expectedDbVersion, st); - } else { - String currentVersion = rs.getString(2); - if (expectedDbVersion.equals(currentVersion)) { - String msg = "[{0}] Schema version {1} is the current version. No changes needed."; - msg = MessageFormat.format(msg, realm, currentVersion); - logger.info(msg); - return DbMigrationState.NOTHING; - } else { - String msg = "[{0}] Schema version is not current. Need to upgrade from {1} to {2}"; - msg = MessageFormat.format(msg, realm, currentVersion, expectedDbVersion); - logger.warn(msg); - return migrateSchema(realm, expectedDbVersion, st); - } - } + + // 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}] Schema version {1} is the current version. No changes needed."; + msg = MessageFormat.format(msg, realm, currentVersion); + logger.info(msg); + return DbMigrationState.NOTHING; + } else if (compare > 0) { + String msg = "[{0}] Schema version is not current. Need to upgrade from {1} to {2}"; + msg = MessageFormat.format(msg, realm, currentVersion, expectedDbVersion); + logger.warn(msg); + return DbMigrationState.MIGRATED; } + + throw new DbException("Current version " + currentVersion + " is later than expected version " + + expectedDbVersion); } - public static String getExpectedDbVersion(String prefix, Class ctxClass) throws DbException { + /** + * @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, prefix); + String dbVersionPropFile = MessageFormat.format(RESOURCE_DB_VERSION, app); try (InputStream stream = ctxClass.getResourceAsStream(dbVersionPropFile);) { DBC.PRE.assertNotNull( @@ -177,7 +225,8 @@ public class DbSchemaVersionCheck { 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 dbVersion; + + return Version.valueOf(dbVersion); } /** @@ -190,7 +239,7 @@ public class DbSchemaVersionCheck { * * @throws DbException */ - public static String getSql(String scriptPrefix, Class ctxClass, String dbVersion, String type) + 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);) { @@ -216,7 +265,7 @@ public class DbSchemaVersionCheck { * * @throws DbException */ - public DbMigrationState createSchema(String realm, String dbVersion, Statement st) throws DbException { + public DbMigrationState createSchema(Connection con, String realm, Version dbVersion) throws DbException { if (!this.allowSchemaCreation) { String msg = "[{0}] No schema exists, or is not valid. Schema generation is disabled, thus can not continue!"; @@ -226,8 +275,9 @@ public class DbSchemaVersionCheck { logger.info(MessageFormat.format("[{0}] Creating initial schema...", realm)); - String sql = getSql(this.scriptPrefix, this.ctxClass, dbVersion, "initial"); - try { + 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); @@ -238,6 +288,41 @@ public class DbSchemaVersionCheck { return DbMigrationState.CREATED; } + /** + * 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 DbMigrationState migrateSchema(Connection con, String realm, Version dbVersion) throws DbException { + + if (!this.allowSchemaMigration) { + String msg = "[{0}] Schema is not valid. Schema migration is disabled, thus can not continue!"; + msg = MessageFormat.format(msg, realm); + throw new DbException(msg); + } + + logger.info(MessageFormat.format("[{0}] Migrating schema...", 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}] Successfully migrated schema to version {1}", realm, dbVersion)); + return DbMigrationState.MIGRATED; + } + /** * @param realm * the realm for which the schema must be dropped (a {@link DbConnectionInfo} must exist for it) @@ -248,7 +333,7 @@ public class DbSchemaVersionCheck { * * @throws DbException */ - public void dropSchema(String realm, String dbVersion, Statement st) throws DbException { + public void dropSchema(Connection con, String realm, Version dbVersion) throws DbException { if (!this.allowSchemaDrop) { String msg = "[{0}] Dropping Schema is disabled, but is required to upgrade current schema..."; @@ -258,49 +343,12 @@ public class DbSchemaVersionCheck { logger.info(MessageFormat.format("[{0}] Dropping existing schema...", realm)); - String sql = getSql(this.scriptPrefix, this.ctxClass, dbVersion, "drop"); - try { + 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); } } - - /** - * 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 - * @param st - * the open database {@link Statement} to which the SQL statements will be written - * - * @return true if the schema was recreated, false if it was simply migrated - * - * @throws DbException - */ - public DbMigrationState migrateSchema(String realm, String dbVersion, Statement st) throws DbException { - - if (!this.allowSchemaMigration) { - String msg = "[{0}] Schema is not valid. Schema migration is disabled, thus can not continue!"; - msg = MessageFormat.format(msg, realm); - throw new DbException(msg); - } - - logger.info(MessageFormat.format("[{0}] Migrating schema...", realm)); - - String sql = getSql(this.scriptPrefix, this.ctxClass, dbVersion, "migration"); - try { - 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}] Successfully migrated schema to version {1}", realm, dbVersion)); - return DbMigrationState.MIGRATED; - } } diff --git a/src/main/java/ch/eitchnet/utils/Version.java b/src/main/java/ch/eitchnet/utils/Version.java new file mode 100644 index 000000000..d8abaf340 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/Version.java @@ -0,0 +1,491 @@ +package ch.eitchnet.utils; + +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * This class has been adapted from org.osgi.framework.Version + * + * Version identifier. + * + *

      + * Version identifiers have four components. + *

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

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

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

      + * + *

      + * {@code Version} objects are immutable. + *

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

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

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

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

      + * See {@code Version(String)} for the format of the version string. + * + * @param version + * String representation of the version identifier. Leading and trailing whitespace will be ignored. + * + * @return A {@code Version} object representing the version identifier. If {@code version} is {@code null} or the + * empty string then {@code emptyVersion} will be returned. + * + * @throws IllegalArgumentException + * If {@code version} is improperly formatted. + */ + public static Version valueOf(String version) { + if (version == null) { + return emptyVersion; + } + + version = version.trim(); + if (version.length() == 0) { + return emptyVersion; + } + + return new Version(version); + } + + /** + * 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; + } + + public boolean isOsgiStyle() { + return this.osgiStyle; + } + + /** + * Returns the string representation of this version identifier. + * + *

      + * The format of the version string will be {@code major.minor.micro} if qualifier is the empty string or + * {@code major.minor.micro.qualifier} otherwise. + * + * @return The string representation of this version identifier. + */ + @Override + public String toString() { + if (this.versionString != null) { + return this.versionString; + } + 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; + } else { + return MAVEN_SNAPSHOT_QUALIFIER; + } + } + return this.qualifier; + } + + /** + * Returns a hash code value for the object. + * + * @return An integer which is a hash code value for this object. + */ + @Override + public int hashCode() { + return (this.major << 24) + (this.minor << 16) + (this.micro << 8) + this.qualifier.hashCode(); + } + + /** + * Compares this {@code Version} object to another object. + * + *

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

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

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

      + * A version is considered to be equal to another version if the major, minor and micro components are equal + * and the qualifier component is equal (using {@code String.compareTo}). + * + * @param other + * The {@code Version} object to be compared. + * @return A negative integer, zero, or a positive integer if this version is less than, equal to, or greater than + * the specified {@code Version} object. + * @throws ClassCastException + * If the specified object is not a {@code Version} object. + */ + @Override + public int compareTo(final Version other) { + if (other == this) + return 0; + + int result = this.major - other.major; + if (result != 0) + return result; + + result = this.minor - other.minor; + if (result != 0) + return result; + + result = this.micro - other.micro; + if (result != 0) + return result; + + return this.qualifier.compareTo(other.qualifier); + } + + /** + * @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(); + } + + public boolean isSnapshot() { + return MAVEN_SNAPSHOT_QUALIFIER.equals(this.qualifier) || OSGI_SNAPSHOT_QUALIFIER.equals(this.qualifier); + } +} diff --git a/src/test/java/ch/eitchnet/utils/VersionTest.java b/src/test/java/ch/eitchnet/utils/VersionTest.java new file mode 100644 index 000000000..bd3c7304e --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/VersionTest.java @@ -0,0 +1,78 @@ +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); + } + + @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"); + } +} From fc7c2d6600f0635faea4782ae6242b6d0236e22e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 9 Jan 2015 12:39:23 +0100 Subject: [PATCH 341/457] [Major] refactored DbSchemaVersionCheck and added version parsing - Now we can use Version to see if current version is greater than - Fixed bug where version checking didn't check for app - TODO is still open to handle migration with intermediary steps --- src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java index 08935f24f..7ee747190 100644 --- a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java +++ b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java @@ -148,7 +148,7 @@ public class DbSchemaVersionCheck { // 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(0, PROP_DB_VERSION); + st.setString(1, PROP_DB_VERSION); if (!st.executeQuery().next()) return null; } @@ -157,7 +157,7 @@ public class DbSchemaVersionCheck { sql = "select id, version from db_version where app = ? order by id desc;"; Version currentVersion = null; try (PreparedStatement st = con.prepareStatement(sql)) { - st.setString(0, app); + st.setString(1, app); ResultSet rs = st.executeQuery(); if (rs.next()) currentVersion = Version.valueOf(rs.getString(2)); From 5a05bc835d76be02c41a54f16d4493155da4669d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 9 Jan 2015 12:59:45 +0100 Subject: [PATCH 342/457] [Minor] Added logging of realm and app when checking db schema --- .../ch/eitchnet/db/DbSchemaVersionCheck.java | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java index 7ee747190..d4c260219 100644 --- a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java +++ b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java @@ -101,7 +101,8 @@ public class DbSchemaVersionCheck { String username = connectionInfo.getUsername(); String password = connectionInfo.getPassword(); - logger.info(MessageFormat.format("[{0}] Checking Schema version for: {1}@{2}", realm, username, url)); + logger.info(MessageFormat.format("[{0}:{1}] Checking Schema version for: {2}@{3}", this.app, realm, username, + url)); Version expectedDbVersion = getExpectedDbVersion(this.app, this.ctxClass); @@ -185,19 +186,19 @@ public class DbSchemaVersionCheck { // otherwise parse the version int compare = expectedDbVersion.compareTo(currentVersion); if (compare == 0) { - String msg = "[{0}] Schema version {1} is the current version. No changes needed."; - msg = MessageFormat.format(msg, realm, currentVersion); + 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}] Schema version is not current. Need to upgrade from {1} to {2}"; - msg = MessageFormat.format(msg, realm, currentVersion, expectedDbVersion); + 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("Current version " + currentVersion + " is later than expected version " - + expectedDbVersion); + throw new DbException(MessageFormat.format("[{0}:{1}]Current version {2} is later than expected version {3}", + this.app, realm, currentVersion, expectedDbVersion)); } /** @@ -265,15 +266,15 @@ public class DbSchemaVersionCheck { * * @throws DbException */ - public DbMigrationState createSchema(Connection con, String realm, Version dbVersion) throws DbException { + public void createSchema(Connection con, String realm, Version dbVersion) throws DbException { if (!this.allowSchemaCreation) { - String msg = "[{0}] No schema exists, or is not valid. Schema generation is disabled, thus can not continue!"; - msg = MessageFormat.format(msg, realm); + 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}] Creating initial schema...", realm)); + logger.info(MessageFormat.format("[{0}:{1}] Creating initial schema...", this.app, realm)); String sql = getSql(this.app, this.ctxClass, dbVersion, "initial"); @@ -284,8 +285,8 @@ public class DbSchemaVersionCheck { throw new DbException("Failed to execute schema generation SQL: " + e.getMessage(), e); } - logger.info(MessageFormat.format("[{0}] Successfully created schema for version {1}", realm, dbVersion)); - return DbMigrationState.CREATED; + logger.info(MessageFormat.format("[{0}:{1}] Successfully created schema with version {2}", this.app, realm, + dbVersion)); } /** @@ -301,15 +302,15 @@ public class DbSchemaVersionCheck { * * @throws DbException */ - public DbMigrationState migrateSchema(Connection con, String realm, Version dbVersion) throws DbException { + public void migrateSchema(Connection con, String realm, Version dbVersion) throws DbException { if (!this.allowSchemaMigration) { - String msg = "[{0}] Schema is not valid. Schema migration is disabled, thus can not continue!"; - msg = MessageFormat.format(msg, realm); + 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}] Migrating schema...", realm)); + 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()) { @@ -319,8 +320,8 @@ public class DbSchemaVersionCheck { throw new DbException("Failed to execute schema migration SQL: " + e.getMessage(), e); } - logger.info(MessageFormat.format("[{0}] Successfully migrated schema to version {1}", realm, dbVersion)); - return DbMigrationState.MIGRATED; + logger.info(MessageFormat.format("[{0}:{1}] Successfully migrated schema to version {2}", this.app, realm, + dbVersion)); } /** @@ -336,12 +337,12 @@ public class DbSchemaVersionCheck { public void dropSchema(Connection con, String realm, Version dbVersion) throws DbException { if (!this.allowSchemaDrop) { - String msg = "[{0}] Dropping Schema is disabled, but is required to upgrade current schema..."; - msg = MessageFormat.format(msg, realm); + 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}] Dropping existing schema...", realm)); + 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()) { @@ -350,5 +351,8 @@ public class DbSchemaVersionCheck { 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)); } } From 7c4fdbaed757fa2d82249d5fa09e2db13b082cea Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 15 Jan 2015 09:05:37 +0100 Subject: [PATCH 343/457] [Major] Refactorings in communication package - Renaming of CommunicationConnection.notify to handleNewMessage() - added transient properties map to IoMessage - Fixed bug in updating of observers in CommunicationConnection --- .../CommunicationConnection.java | 20 +- .../ch/eitchnet/communication/IoMessage.java | 52 ++ .../communication/file/FileEndpoint.java | 502 +++++++++--------- .../tcpip/ServerSocketEndpoint.java | 2 +- 4 files changed, 313 insertions(+), 263 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java index a98b0edf4..4d5c6f5d2 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -246,7 +246,7 @@ public class CommunicationConnection implements Runnable { * * @param message */ - public void notify(IoMessage 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 @@ -255,6 +255,10 @@ public class CommunicationConnection implements Runnable { if (message.getState().compareTo(State.ACCEPTED) < 0) message.setState(State.ACCEPTED, StringHelper.DASH); + notifyObservers(message); + } + + public void notifyObservers(IoMessage message) { List observers; synchronized (this.connectionObservers) { List list = this.connectionObservers.getList(message.getKey()); @@ -328,7 +332,8 @@ public class CommunicationConnection implements Runnable { } /** - * Called when the message has been handled + * Called when an outgoing message has been handled. This method logs the message state and then notifies all + * observers * * @param message */ @@ -349,16 +354,9 @@ public class CommunicationConnection implements Runnable { default: logger.error(MessageFormat.format("Unhandled state for message {0}", message.toString())); //$NON-NLS-1$ break; + } - } - synchronized (this.connectionObservers) { - List observers = this.connectionObservers.getList(message.getKey()); - if (observers != null) { - for (ConnectionObserver observer : observers) { - observer.notify(message.getKey(), message); - } - } - } + notifyObservers(message); } public String getRemoteUri() { diff --git a/src/main/java/ch/eitchnet/communication/IoMessage.java b/src/main/java/ch/eitchnet/communication/IoMessage.java index 554a8d144..b8d95fb1b 100644 --- a/src/main/java/ch/eitchnet/communication/IoMessage.java +++ b/src/main/java/ch/eitchnet/communication/IoMessage.java @@ -16,10 +16,25 @@ package ch.eitchnet.communication; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +/** + *

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

      + * + *

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

      + * + * @author Robert von Burg + */ public class IoMessage { private final String id; @@ -28,7 +43,13 @@ public class IoMessage { private Date updated; private State state; private String stateMsg; + private Map parameters; + /** + * @param id + * @param key + * @param connectionId + */ public IoMessage(String id, CommandKey key, String connectionId) { this.id = id; this.key = key; @@ -36,6 +57,7 @@ public class IoMessage { this.state = State.CREATED; this.stateMsg = StringHelper.DASH; this.updated = new Date(); + this.parameters = new HashMap<>(); } /** @@ -101,6 +123,36 @@ public class IoMessage { this.updated = new Date(); } + /** + * 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 + */ + public Object removeParam(String key) { + return this.parameters.remove(key); + } + + /** + * @return the set of parameter keys + */ + public Set getParamKeys() { + return this.parameters.keySet(); + } + @SuppressWarnings("nls") @Override public String toString() { diff --git a/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java b/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java index edd9b0fd1..459bf36ab 100644 --- a/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java @@ -1,251 +1,251 @@ -/* - * Copyright 2014 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.communication.file; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.text.MessageFormat; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.communication.CommunicationConnection; -import ch.eitchnet.communication.CommunicationEndpoint; -import ch.eitchnet.communication.ConnectionException; -import ch.eitchnet.communication.ConnectionMessages; -import ch.eitchnet.communication.ConnectionState; -import ch.eitchnet.communication.IoMessage; -import ch.eitchnet.communication.IoMessageVisitor; -import ch.eitchnet.communication.StreamMessageVisitor; -import ch.eitchnet.utils.helper.StringHelper; - -/** - * An {@link CommunicationEndpoint} which writes and/or reads from a designated file - * - * @author Robert von Burg - */ -public class FileEndpoint implements CommunicationEndpoint, Runnable { - - public static final String ENDPOINT_MODE = "endpointMode"; //$NON-NLS-1$ - public static final String INBOUND_FILENAME = "inboundFilename"; //$NON-NLS-1$ - public static final String OUTBOUND_FILENAME = "outboundFilename"; //$NON-NLS-1$ - public static final long POLL_TIME = 1000l; - - private static final Logger logger = LoggerFactory.getLogger(FileEndpoint.class); - - private CommunicationConnection connection; - - private FileEndpointMode endpointMode; - private String inboundFilename; - private String outboundFilename; - private Thread thread; - private boolean run = false; - private StreamMessageVisitor messageVisitor; - - /** - * {@link FileEndpoint} needs the following parameters on the configuration to be initialized - *
        - *
      • outboundFilename: the file name where the {@link IoMessage} contents are written to. The value may contain - * {@link System#getProperty(String)} place holders which will be evaluated
      • - *
      - */ - @Override - public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { - this.connection = connection; - - ConnectionMessages.assertLegalMessageVisitor(this.getClass(), StreamMessageVisitor.class, messageVisitor); - this.messageVisitor = (StreamMessageVisitor) messageVisitor; - - configure(); - } - - private void configure() { - Map parameters = this.connection.getParameters(); - - String endpointModeS = parameters.get(ENDPOINT_MODE); - if (StringHelper.isEmpty(endpointModeS)) { - throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); - } - try { - this.endpointMode = FileEndpointMode.valueOf(endpointModeS); - } catch (Exception e) { - throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); - } - - if (this.endpointMode.isRead()) { - this.inboundFilename = parameters.get(INBOUND_FILENAME); - if (StringHelper.isEmpty(this.inboundFilename)) { - throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, INBOUND_FILENAME, - this.inboundFilename); - } - } - - if (this.endpointMode.isWrite()) { - this.outboundFilename = parameters.get(OUTBOUND_FILENAME); - if (StringHelper.isEmpty(this.outboundFilename)) { - throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, OUTBOUND_FILENAME, - this.outboundFilename); - } - } - } - - @Override - public String getLocalUri() { - return new File(this.inboundFilename).getAbsolutePath(); - } - - @Override - public String getRemoteUri() { - return new File(this.outboundFilename).getAbsolutePath(); - } - - @Override - public void start() { - if (this.endpointMode.isRead()) { - this.thread = new Thread(this, new File(this.inboundFilename).getName()); - this.run = true; - this.thread.start(); - } - this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); - } - - @Override - public void stop() { - stopThread(); - this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); - } - - @Override - public void reset() { - stopThread(); - configure(); - this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); - } - - private void stopThread() { - this.run = false; - if (this.thread != null) { - try { - this.thread.interrupt(); - this.thread.join(2000l); - } catch (Exception e) { - logger.error(MessageFormat.format("Error while interrupting thread: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ - } - - this.thread = null; - } - } - - @Override - public void send(IoMessage message) throws Exception { - if (!this.endpointMode.isWrite()) { - String msg = "FileEnpoint mode is {0} and thus write is not allowed!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, this.endpointMode); - throw new ConnectionException(msg); - } - - this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); - - // open the stream - try (FileOutputStream outputStream = new FileOutputStream(this.outboundFilename, false)) { - - // write the message using the visitor - this.messageVisitor.visit(outputStream, message); - - } finally { - this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); - } - } - - @Override - public void simulate(IoMessage message) throws Exception { - this.messageVisitor.simulate(message); - } - - @Override - public void run() { - - File file = new File(this.inboundFilename); - - long lastModified = 0l; - - logger.info("Starting..."); //$NON-NLS-1$ - while (this.run) { - - try { - - if (file.canRead()) { - long tmpModified = file.lastModified(); - if (tmpModified > lastModified) { - - logger.info(MessageFormat.format("Handling file {0}", file.getAbsolutePath())); //$NON-NLS-1$ - - this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); - - // file is changed - lastModified = tmpModified; - - // read the file - handleFile(file); - - this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); - } - } - - if (this.run) { - this.connection.notifyStateChange(ConnectionState.WAITING, ConnectionState.WAITING.toString()); - try { - synchronized (this) { - this.wait(POLL_TIME); - } - } catch (InterruptedException e) { - this.run = false; - logger.info("Interrupted!"); //$NON-NLS-1$ - } - } - - } catch (Exception e) { - logger.error(MessageFormat.format("Error reading file: {0}", file.getAbsolutePath())); //$NON-NLS-1$ - logger.error(e.getMessage(), e); - - this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); - } - } - } - - /** - * Reads the file and handle using {@link StreamMessageVisitor} - * - * @param file - * the {@link File} to read - */ - protected void handleFile(File file) throws Exception { - - try (InputStream inputStream = new FileInputStream(file)) { - - // convert the object to an integration message - IoMessage message = this.messageVisitor.visit(inputStream); - - // and forward to the connection - if (message != null) { - this.connection.notify(message); - } - } - } -} +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.communication.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.communication.CommunicationConnection; +import ch.eitchnet.communication.CommunicationEndpoint; +import ch.eitchnet.communication.ConnectionException; +import ch.eitchnet.communication.ConnectionMessages; +import ch.eitchnet.communication.ConnectionState; +import ch.eitchnet.communication.IoMessage; +import ch.eitchnet.communication.IoMessageVisitor; +import ch.eitchnet.communication.StreamMessageVisitor; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * An {@link CommunicationEndpoint} which writes and/or reads from a designated file + * + * @author Robert von Burg + */ +public class FileEndpoint implements CommunicationEndpoint, Runnable { + + public static final String ENDPOINT_MODE = "endpointMode"; //$NON-NLS-1$ + public static final String INBOUND_FILENAME = "inboundFilename"; //$NON-NLS-1$ + public static final String OUTBOUND_FILENAME = "outboundFilename"; //$NON-NLS-1$ + public static final long POLL_TIME = 1000l; + + private static final Logger logger = LoggerFactory.getLogger(FileEndpoint.class); + + private CommunicationConnection connection; + + private FileEndpointMode endpointMode; + private String inboundFilename; + private String outboundFilename; + private Thread thread; + private boolean run = false; + private StreamMessageVisitor messageVisitor; + + /** + * {@link FileEndpoint} needs the following parameters on the configuration to be initialized + *
        + *
      • outboundFilename: the file name where the {@link IoMessage} contents are written to. The value may contain + * {@link System#getProperty(String)} place holders which will be evaluated
      • + *
      + */ + @Override + public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { + this.connection = connection; + + ConnectionMessages.assertLegalMessageVisitor(this.getClass(), StreamMessageVisitor.class, messageVisitor); + this.messageVisitor = (StreamMessageVisitor) messageVisitor; + + configure(); + } + + private void configure() { + Map parameters = this.connection.getParameters(); + + String endpointModeS = parameters.get(ENDPOINT_MODE); + if (StringHelper.isEmpty(endpointModeS)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); + } + try { + this.endpointMode = FileEndpointMode.valueOf(endpointModeS); + } catch (Exception e) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); + } + + if (this.endpointMode.isRead()) { + this.inboundFilename = parameters.get(INBOUND_FILENAME); + if (StringHelper.isEmpty(this.inboundFilename)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, INBOUND_FILENAME, + this.inboundFilename); + } + } + + if (this.endpointMode.isWrite()) { + this.outboundFilename = parameters.get(OUTBOUND_FILENAME); + if (StringHelper.isEmpty(this.outboundFilename)) { + throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, OUTBOUND_FILENAME, + this.outboundFilename); + } + } + } + + @Override + public String getLocalUri() { + return new File(this.inboundFilename).getAbsolutePath(); + } + + @Override + public String getRemoteUri() { + return new File(this.outboundFilename).getAbsolutePath(); + } + + @Override + public void start() { + if (this.endpointMode.isRead()) { + this.thread = new Thread(this, new File(this.inboundFilename).getName()); + this.run = true; + this.thread.start(); + } + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + + @Override + public void stop() { + stopThread(); + this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); + } + + @Override + public void reset() { + stopThread(); + configure(); + this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); + } + + private void stopThread() { + this.run = false; + if (this.thread != null) { + try { + this.thread.interrupt(); + this.thread.join(2000l); + } catch (Exception e) { + logger.error(MessageFormat.format("Error while interrupting thread: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ + } + + this.thread = null; + } + } + + @Override + public void send(IoMessage message) throws Exception { + if (!this.endpointMode.isWrite()) { + String msg = "FileEnpoint mode is {0} and thus write is not allowed!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, this.endpointMode); + throw new ConnectionException(msg); + } + + this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); + + // open the stream + try (FileOutputStream outputStream = new FileOutputStream(this.outboundFilename, false)) { + + // write the message using the visitor + this.messageVisitor.visit(outputStream, message); + + } finally { + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + } + + @Override + public void simulate(IoMessage message) throws Exception { + this.messageVisitor.simulate(message); + } + + @Override + public void run() { + + File file = new File(this.inboundFilename); + + long lastModified = 0l; + + logger.info("Starting..."); //$NON-NLS-1$ + while (this.run) { + + try { + + if (file.canRead()) { + long tmpModified = file.lastModified(); + if (tmpModified > lastModified) { + + logger.info(MessageFormat.format("Handling file {0}", file.getAbsolutePath())); //$NON-NLS-1$ + + this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); + + // file is changed + lastModified = tmpModified; + + // read the file + handleFile(file); + + this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); + } + } + + if (this.run) { + this.connection.notifyStateChange(ConnectionState.WAITING, ConnectionState.WAITING.toString()); + try { + synchronized (this) { + this.wait(POLL_TIME); + } + } catch (InterruptedException e) { + this.run = false; + logger.info("Interrupted!"); //$NON-NLS-1$ + } + } + + } catch (Exception e) { + logger.error(MessageFormat.format("Error reading file: {0}", file.getAbsolutePath())); //$NON-NLS-1$ + logger.error(e.getMessage(), e); + + this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); + } + } + } + + /** + * Reads the file and handle using {@link StreamMessageVisitor} + * + * @param file + * the {@link File} to read + */ + protected void handleFile(File file) throws Exception { + + try (InputStream inputStream = new FileInputStream(file)) { + + // convert the object to an integration message + IoMessage message = this.messageVisitor.visit(inputStream); + + // and forward to the connection + if (message != null) { + this.connection.handleNewMessage(message); + } + } + } +} diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java index 5acc85e09..55d5edfe6 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -555,7 +555,7 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { // read and write from the connected server socket IoMessage message = this.messageVisitor.visit(this.inputStream, this.outputStream); if (message != null) { - this.connection.notify(message); + this.connection.handleNewMessage(message); } } From fe1216eb12b41d052cb69d53a582992afa831c51 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 15 Jan 2015 09:06:13 +0100 Subject: [PATCH 344/457] [Minor] Fixed returned ISO8601 duration for duration = 0 is now P0D --- src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java index 67c147e5c..61788c737 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java @@ -244,7 +244,7 @@ public class ISO8601Duration implements DurationFormat { throw new RuntimeException("A duration can not be negative!"); if (duration == 0l) - return "PT0S"; + return "P0D"; StringBuilder sb = new StringBuilder(); sb.append('P'); From f0421ba7db69c8a5d832d674d3db2753fb24281f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Jan 2015 13:57:53 +0100 Subject: [PATCH 345/457] [New] Added missing IoMessage.getParam() --- src/main/java/ch/eitchnet/communication/IoMessage.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/IoMessage.java b/src/main/java/ch/eitchnet/communication/IoMessage.java index b8d95fb1b..45d2ba77e 100644 --- a/src/main/java/ch/eitchnet/communication/IoMessage.java +++ b/src/main/java/ch/eitchnet/communication/IoMessage.java @@ -123,6 +123,11 @@ public class IoMessage { this.updated = new Date(); } + @SuppressWarnings("unchecked") + public T getParam(String key) { + return (T) this.parameters.get(key); + } + /** * Add a transient parameter to this message * @@ -142,8 +147,9 @@ public class IoMessage { * The give of the parameter to be removed * @return the removed value, or null if the object didn't exist */ - public Object removeParam(String key) { - return this.parameters.remove(key); + @SuppressWarnings("unchecked") + public T removeParam(String key) { + return (T) this.parameters.remove(key); } /** From cde6eb652ec2c12ce22c8cb21a16589d56f8a49f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Jan 2015 13:58:15 +0100 Subject: [PATCH 346/457] [Minor] add exception to log if observer updates fail --- .../java/ch/eitchnet/communication/CommunicationConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java index 4d5c6f5d2..46db7c6ba 100644 --- a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java +++ b/src/main/java/ch/eitchnet/communication/CommunicationConnection.java @@ -273,7 +273,7 @@ public class CommunicationConnection implements Runnable { 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())); + logger.error(MessageFormat.format(msg, message.getKey(), message.getId()), e); } } From 401052a5ea5844673615ba4c255b6faa96000adc Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 23 Jan 2015 10:23:38 +0100 Subject: [PATCH 347/457] [New] Added ConnectionMode.isSimulation() --- .../communication/ConnectionMode.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMode.java b/src/main/java/ch/eitchnet/communication/ConnectionMode.java index fbc44a768..59d5d89d6 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionMode.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionMode.java @@ -38,18 +38,35 @@ 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, + OFF { + 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, + ON { + 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; + SIMULATION { + public boolean isSimulation() { + return true; + } + }; + + /** + * @return true if the current mode is simulation, false otherwise + */ + public abstract boolean isSimulation(); } From 97936b03fe86ce421bbfe61efbd51a49da1f0868 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 1 Feb 2015 13:14:49 +0100 Subject: [PATCH 348/457] [New] added es(), ei(), cs(), and ci() to StringMatchMode - these are short method names for the different match modes --- .../ch/eitchnet/utils/StringMatchMode.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/src/main/java/ch/eitchnet/utils/StringMatchMode.java index d096a0c53..aec70ad28 100644 --- a/src/main/java/ch/eitchnet/utils/StringMatchMode.java +++ b/src/main/java/ch/eitchnet/utils/StringMatchMode.java @@ -62,4 +62,64 @@ public enum StringMatchMode { return value1.equals(value2); } + + /** + *

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

      + * + *

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

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

      + * Returns {@link #EQUALS_CASE_INSENSITIVE} + *

      + * + *

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

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

      + * Returns {@link #CONTAINS_CASE_SENSITIVE} + *

      + * + *

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

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

      + * Returns {@link #CONTAINS_CASE_INSENSITIVE} + *

      + * + *

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

      + * + * @return {@link #CONTAINS_CASE_INSENSITIVE} + */ + public static StringMatchMode ci() { + return CONTAINS_CASE_INSENSITIVE; + } } From e1a3a4fca8458e5aa1c4cd57db26bb0a2852f26b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 6 Feb 2015 22:26:03 +0100 Subject: [PATCH 349/457] [Minor] catch NPE in rollback --- .../impl/DefaultPersistenceTransaction.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java index 053337e60..1590aedba 100644 --- a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java @@ -149,13 +149,15 @@ public class DefaultPersistenceTransaction implements PersistenceTransaction { long txDuration = end - this.startTime; long closeDuration = end - start; - this.txResult.clear(); - this.txResult.setState(this.state); - this.txResult.setStartTime(this.startTimeDate); - this.txResult.setTxDuration(txDuration); - this.txResult.setCloseDuration(closeDuration); - this.txResult.setRealm(this.realm.getRealmName()); - this.txResult.setModificationByKey(Collections. emptyMap()); + if (this.txResult != null) { + this.txResult.clear(); + this.txResult.setState(this.state); + this.txResult.setStartTime(this.startTimeDate); + this.txResult.setTxDuration(txDuration); + this.txResult.setCloseDuration(closeDuration); + this.txResult.setRealm(this.realm.getRealmName()); + this.txResult.setModificationByKey(Collections. emptyMap()); + } } } From d87517e4c2a3e9507b2661cc1737031761d99b61 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 7 Feb 2015 19:29:38 +0100 Subject: [PATCH 350/457] [New] added Version.add(int, int, int) --- src/main/java/ch/eitchnet/utils/Version.java | 135 ++++++++++-------- .../java/ch/eitchnet/utils/VersionTest.java | 25 ++++ 2 files changed, 104 insertions(+), 56 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/Version.java b/src/main/java/ch/eitchnet/utils/Version.java index d8abaf340..e3507bbfb 100644 --- a/src/main/java/ch/eitchnet/utils/Version.java +++ b/src/main/java/ch/eitchnet/utils/Version.java @@ -40,22 +40,23 @@ import ch.eitchnet.utils.helper.StringHelper; *

      * *

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

      */ public class Version implements Comparable { + + public static final String SEPARATOR = "."; + public static final String OSGI_QUALIFIER_SEPARATOR = "."; + public static final String MAVEN_QUALIFIER_SEPARATOR = "-"; + + public static final String MAVEN_SNAPSHOT_QUALIFIER = "SNAPSHOT"; + public static final String OSGI_SNAPSHOT_QUALIFIER = "qualifier"; + private final int major; private final int minor; private final int micro; private final String qualifier; - private static final String SEPARATOR = "."; - private static final String OSGI_QUALIFIER_SEPARATOR = "."; - private static final String MAVEN_QUALIFIER_SEPARATOR = "-"; - - private static final String MAVEN_SNAPSHOT_QUALIFIER = "SNAPSHOT"; - private static final String OSGI_SNAPSHOT_QUALIFIER = "qualifier"; - private transient String versionString; private boolean osgiStyle; @@ -313,56 +314,36 @@ public class Version implements Comparable { 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; } /** - * Returns the string representation of this version identifier. - * - *

      - * The format of the version string will be {@code major.minor.micro} if qualifier is the empty string or - * {@code major.minor.micro.qualifier} otherwise. - * - * @return The string representation of this version identifier. + * @return true if this version is for a snapshot version, i.e. ends with {@link #MAVEN_SNAPSHOT_QUALIFIER} or + * {@link #OSGI_SNAPSHOT_QUALIFIER} */ - @Override - public String toString() { - if (this.versionString != null) { - return this.versionString; - } - 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; - } else { - return MAVEN_SNAPSHOT_QUALIFIER; - } - } - return this.qualifier; + public boolean isSnapshot() { + return MAVEN_SNAPSHOT_QUALIFIER.equals(this.qualifier) || OSGI_SNAPSHOT_QUALIFIER.equals(this.qualifier); } /** @@ -460,6 +441,52 @@ public class Version implements Comparable { return this.qualifier.compareTo(other.qualifier); } + /** + * Returns the string representation of this version identifier. + * + *

      + * The format of the version string will be {@code major.minor.micro} if qualifier is the empty string or + * {@code major.minor.micro.qualifier} otherwise. + * + * @return The string representation of this version identifier. + */ + @Override + public String toString() { + if (this.versionString == null) + this.versionString = toString(this.osgiStyle); + return this.versionString; + } + + private String toString(final boolean withOsgiStyle) { + int q = this.qualifier.length(); + StringBuilder result = new StringBuilder(20 + q); + result.append(this.major); + result.append(SEPARATOR); + result.append(this.minor); + result.append(SEPARATOR); + result.append(this.micro); + if (q > 0) { + if (withOsgiStyle) { + result.append(OSGI_QUALIFIER_SEPARATOR); + } else { + result.append(MAVEN_QUALIFIER_SEPARATOR); + } + result.append(createQualifier(withOsgiStyle)); + } + return result.toString(); + } + + private String createQualifier(boolean withOsgiStyle) { + if (this.qualifier.equals(MAVEN_SNAPSHOT_QUALIFIER) || this.qualifier.equals(OSGI_SNAPSHOT_QUALIFIER)) { + if (withOsgiStyle) { + return OSGI_SNAPSHOT_QUALIFIER; + } else { + return MAVEN_SNAPSHOT_QUALIFIER; + } + } + return this.qualifier; + } + /** * @return This version represented in a maven compatible form. */ @@ -484,8 +511,4 @@ public class Version implements Comparable { result.append(this.minor); return result.toString(); } - - public boolean isSnapshot() { - return MAVEN_SNAPSHOT_QUALIFIER.equals(this.qualifier) || OSGI_SNAPSHOT_QUALIFIER.equals(this.qualifier); - } } diff --git a/src/test/java/ch/eitchnet/utils/VersionTest.java b/src/test/java/ch/eitchnet/utils/VersionTest.java index bd3c7304e..6e4708d80 100644 --- a/src/test/java/ch/eitchnet/utils/VersionTest.java +++ b/src/test/java/ch/eitchnet/utils/VersionTest.java @@ -75,4 +75,29 @@ public class VersionTest { 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()); + } } From 8861db85091eea43f06ed2a480360ba670d3c30e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Feb 2015 12:04:49 +0100 Subject: [PATCH 351/457] [New] added MapOfLists.isEmpty() and MapOfMaps.isEmpty() --- src/main/java/ch/eitchnet/utils/collections/MapOfLists.java | 4 ++++ src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java b/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java index dc64e2c79..e40c9a849 100644 --- a/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java +++ b/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java @@ -117,4 +117,8 @@ public class MapOfLists { return 0; return list.size(); } + + public boolean isEmpty() { + return this.mapOfLists.isEmpty(); + } } diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java index 7e829ee0b..724caa53d 100644 --- a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java +++ b/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java @@ -165,4 +165,8 @@ public class MapOfMaps { return 0; return map.size(); } + + public boolean isEmpty() { + return this.mapOfMaps.isEmpty(); + } } From 811423eee69e7e0d3a2e7d44d67b13ab329ba828 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 9 Feb 2015 00:35:01 +0100 Subject: [PATCH 352/457] [Bugfix] fixed not adding firstname and last name to sys user cert --- .../ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 185c79adb..b8cd2e411 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1085,7 +1085,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - null, null, authToken, user.getLocale(), user.getRoles(), new HashMap<>(user.getProperties())); + user.getFirstname(), user.getLastname(), authToken, user.getLocale(), user.getRoles(), new HashMap<>( + user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); From 72d470fd26350a7b1c79a0e8da8f29c1b9026e05 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 11 Feb 2015 23:01:37 +0100 Subject: [PATCH 353/457] [Minor] added more tests for Version --- .../java/ch/eitchnet/utils/VersionTest.java | 210 +++++++++--------- 1 file changed, 107 insertions(+), 103 deletions(-) diff --git a/src/test/java/ch/eitchnet/utils/VersionTest.java b/src/test/java/ch/eitchnet/utils/VersionTest.java index 6e4708d80..5381a6f8f 100644 --- a/src/test/java/ch/eitchnet/utils/VersionTest.java +++ b/src/test/java/ch/eitchnet/utils/VersionTest.java @@ -1,103 +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); - } - - @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()); - } -} +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()); + } +} From e9277f975e5ef28b47f2001a4f9c035c947562b2 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 13 Feb 2015 11:45:34 +0100 Subject: [PATCH 354/457] [Minor] changed logging of ClientSocketEndpoint connection timeout --- .../eitchnet/communication/tcpip/ClientSocketEndpoint.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java index 4d7945ad2..316eff4de 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -212,9 +212,8 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { this.closed = true; this.connection.notifyStateChange(ConnectionState.DISCONNECTED, null); } catch (Exception e) { - String msg = "Error while connecting to {0}:{1}"; //$NON-NLS-1$ - logger.error(MessageFormat.format(msg, this.remoteInputAddressS, Integer.toString(this.remoteInputPort))); - logger.error(e.getMessage(), 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()); } } From ccd43acf0a915e89620efa7094c9e9e1b4cf1a63 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 13 Feb 2015 11:46:47 +0100 Subject: [PATCH 355/457] [Minor] changed logging of ServerSocketEndpoint connection timeout --- .../eitchnet/communication/tcpip/ServerSocketEndpoint.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java index 55d5edfe6..57f6399ab 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java @@ -230,9 +230,8 @@ public class ServerSocketEndpoint implements CommunicationEndpoint, Runnable { logger.warn("Socket closed!"); //$NON-NLS-1$ this.connection.notifyStateChange(ConnectionState.DISCONNECTED, null); } else { - String msg = "Error while opening socket for inbound connection {0}"; //$NON-NLS-1$ - logger.error(MessageFormat.format(msg, this.connection.getId())); - logger.error(e.getMessage(), e); + 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()); } From 1279ed63f330519ec272b8ee489ed3843a8c7ea3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 13 Feb 2015 12:55:12 +0100 Subject: [PATCH 356/457] [Minor] added some minor JavaDoc --- .../communication/IoMessageVisitor.java | 2 +- .../tcpip/SocketMessageVisitor.java | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java index a64207ec1..b5a08f9da 100644 --- a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java @@ -22,7 +22,7 @@ import ch.eitchnet.communication.console.ConsoleMessageVisitor; /** *

      - * Visitors to read and write {@link IoMessage} using different kind of endpoints. Different entpoints will require + * 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}. *

      diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java b/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java index 1c9610820..52f535300 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java @@ -17,10 +17,16 @@ package ch.eitchnet.communication.tcpip; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.net.Socket; import ch.eitchnet.communication.IoMessage; import ch.eitchnet.communication.IoMessageVisitor; +/** + * This {@link IoMessageVisitor} implements and endpoint connecting to a {@link Socket}. + * + * @author Robert von Burg + */ public abstract class SocketMessageVisitor extends IoMessageVisitor { protected final String connectionId; @@ -33,8 +39,24 @@ public abstract class SocketMessageVisitor extends IoMessageVisitor { 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; } From 109cfd4df4d5163dcc3e7fd2dbff9195d382fac1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 13 Feb 2015 15:30:00 +0100 Subject: [PATCH 357/457] [Bugfix] fixed situation where connection is not closed on exception - on timeouts the underlying socket doesn't know that the connection is ok, and thus we have exceptions, so we must make sure we close them properly --- .../eitchnet/communication/tcpip/ClientSocketEndpoint.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java index 316eff4de..7ea688be1 100644 --- a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java @@ -213,7 +213,9 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { 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()); + logger.error( + MessageFormat.format(msg, this.remoteInputAddressS, Integer.toString(this.remoteInputPort)), + e.getMessage()); this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); } } @@ -521,12 +523,13 @@ public class ClientSocketEndpoint implements CommunicationEndpoint { 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) { + if (this.closeAfterSend && !this.closed) { closeConnection(); } } From a28dec47fdafd4c732361318f1eb9e5f99d43e75 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 2 Mar 2015 13:43:06 +0100 Subject: [PATCH 358/457] [Project] Bumped version to 1.1.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1da7a2503..62f7a2fc6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml From 10554b53d38a8c11bb26f4fd8131776909093b3f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 2 Mar 2015 13:43:06 +0100 Subject: [PATCH 359/457] [Project] Bumped version to 1.1.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1c3042f59..1f768c0e9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml From 09966937c904113002d09e419c70b5945a761a4c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 2 Mar 2015 13:43:06 +0100 Subject: [PATCH 360/457] [Project] Bumped version to 1.1.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d784a611d..9839d0b9a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml From 5b462c6140c63b3931a003a70cfe1b594efaaacf Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Mon, 2 Mar 2015 19:34:55 +0100 Subject: [PATCH 361/457] [Minor] updated version to 1.0.0 from utils --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 62f7a2fc6..5e53ce875 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.1.0-SNAPSHOT + 1.0.0 ../ch.eitchnet.parent/pom.xml @@ -31,7 +31,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0.0-SNAPSHOT + 1.0.0 From b5fac57dd8dfb14b1836c563c70ef87a21ef50e7 Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Mon, 2 Mar 2015 19:46:13 +0100 Subject: [PATCH 362/457] [Minor] updated version to 1.1.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5e53ce875..98994412d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0 + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml @@ -31,7 +31,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0.0 + 1.1.0-SNAPSHOT From c1d77fee38c825158d3094d009b782cc9b31dfa9 Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Mon, 2 Mar 2015 20:30:40 +0100 Subject: [PATCH 363/457] [Project] fixed version of utils --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1f768c0e9..de63a547e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.1.0-SNAPSHOT + 1.0.0 ../ch.eitchnet.parent/pom.xml @@ -38,7 +38,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0.0-SNAPSHOT + 1.0.0 From 7b6282dd2395ddc760ac89f7ebf33fc9ea4c1d88 Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Mon, 2 Mar 2015 20:37:43 +0100 Subject: [PATCH 364/457] [Project] updated utils version --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index de63a547e..f5dd19ec4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0 + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml @@ -38,7 +38,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0.0 + 1.1.0-SNAPSHOT From dc7ff5582011c9188085db441a4ce75520747299 Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Mon, 2 Mar 2015 20:42:41 +0100 Subject: [PATCH 365/457] [Project] updated to 1.0.0 utils --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f5dd19ec4..de63a547e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.1.0-SNAPSHOT + 1.0.0 ../ch.eitchnet.parent/pom.xml @@ -38,7 +38,7 @@ ch.eitchnet ch.eitchnet.utils - 1.1.0-SNAPSHOT + 1.0.0 From 34c85825bfe16aa91ff319a5abe0feb11350a58b Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Mon, 2 Mar 2015 21:36:33 +0100 Subject: [PATCH 366/457] [Project] updated version to 1.1.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index de63a547e..f5dd19ec4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0 + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml @@ -38,7 +38,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0.0 + 1.1.0-SNAPSHOT From 83740b59e21e356ac3f4e3439cd038b7f4b9a073 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Mar 2015 22:42:17 +0100 Subject: [PATCH 367/457] [Minor] ch.eitchnet.utils version is a propery for easier versioning --- pom.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 98994412d..51d2ba92f 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,10 @@ https://github.com/eitchnet/ch.eitchnet.privilege 2011 + + 1.1.0-SNAPSHOT + + Github Issues https://github.com/eitchnet/ch.eitchnet.privilege/issues @@ -31,7 +35,7 @@ ch.eitchnet ch.eitchnet.utils - 1.1.0-SNAPSHOT + ${eitchnet.utils.version} From 2371cd78533b34484b050fd01fbfe2044bd0c94c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Mar 2015 22:42:49 +0100 Subject: [PATCH 368/457] [Minor] ch.eitchnet.utils version is a propery for easier versioning --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f5dd19ec4..4374bde4f 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ UTF-8 + 1.1.0-SNAPSHOT @@ -38,7 +39,7 @@ ch.eitchnet ch.eitchnet.utils - 1.1.0-SNAPSHOT + ${eitchnet.utils.version} From 07f009b7ff7cba427e4f0508da65f8d9b04db2f4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 13:36:49 +0100 Subject: [PATCH 369/457] [New] Added XmlKeyValue for key value pairs in JAXB --- .../ch/eitchnet/utils/xml/XmlKeyValue.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java diff --git a/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java b/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java new file mode 100644 index 000000000..f813b230c --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java @@ -0,0 +1,73 @@ +package ch.eitchnet.utils.xml; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.xml.bind.annotation.XmlAttribute; + +public class XmlKeyValue { + + @XmlAttribute(name = "key") + private String key; + @XmlAttribute(name = "value") + private String value; + + public XmlKeyValue(String key, String value) { + this.key = key; + this.value = value; + } + + public XmlKeyValue() { + // no-arg constructor for JAXB + } + + /** + * @return the key + */ + public String getKey() { + return this.key; + } + + /** + * @param key + * the key to set + */ + public void setKey(String key) { + this.key = key; + } + + /** + * @return the value + */ + public String getValue() { + return this.value; + } + + /** + * @param value + * the value to set + */ + public void setValue(String value) { + this.value = value; + } + + public static List valueOf(Map map) { + List keyValues = new ArrayList<>(map.size()); + for (Entry entry : map.entrySet()) { + keyValues.add(new XmlKeyValue(entry.getKey(), entry.getValue())); + } + return keyValues; + } + + public static Map toMap(List values) { + Map propertyMap = new HashMap<>(values.size()); + for (XmlKeyValue xmlKeyValue : values) { + propertyMap.put(xmlKeyValue.getKey(), xmlKeyValue.getValue()); + } + + return propertyMap; + } +} \ No newline at end of file From 5940a345d4f374aede973fb6d218a4199c8bfb4d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 13:38:15 +0100 Subject: [PATCH 370/457] [Major] refactoring Privilege - All reps are now JAXB enabled - replaced addOrReplace with add and repalace methods - added some more tests --- config/PrivilegeModel.xml | 2 + .../handler/DefaultPrivilegeHandler.java | 279 +++++++++++++++--- .../privilege/handler/PersistenceHandler.java | 24 +- .../privilege/handler/PrivilegeHandler.java | 145 +++++++-- .../handler/XmlPersistenceHandler.java | 26 +- .../eitchnet/privilege/model/Certificate.java | 22 +- .../privilege/model/PrivilegeRep.java | 25 +- .../ch/eitchnet/privilege/model/RoleRep.java | 62 +++- .../ch/eitchnet/privilege/model/UserRep.java | 70 ++++- .../privilege/test/PrivilegeTest.java | 145 +++++++-- 10 files changed, 700 insertions(+), 100 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 1ab9708d6..7ae0fd879 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -50,6 +50,8 @@ true + + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index b8cd2e411..6a2fa51cb 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -17,13 +17,17 @@ package ch.eitchnet.privilege.handler; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +45,7 @@ import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.StringHelper; /** *

      @@ -108,7 +113,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private boolean autoPersistOnPasswordChange; @Override - public RoleRep getRole(String roleName) { + public RoleRep getRole(Certificate certificate, String roleName) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + Role role = this.persistenceHandler.getRole(roleName); if (role == null) return null; @@ -116,7 +125,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public UserRep getUser(String username) { + public UserRep getUser(Certificate certificate, String username) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + User user = this.persistenceHandler.getUser(username); if (user == null) return null; @@ -124,7 +137,45 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public List queryUsers(UserRep selectorRep) { + public Map getPolicyDefs(Certificate certificate) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + Map policyDef = new HashMap<>(this.policyMap.size()); + for (Entry> entry : this.policyMap.entrySet()) { + policyDef.put(entry.getKey(), entry.getValue().getName()); + } + return policyDef; + } + + @Override + public List getUsers(Certificate certificate) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + Stream usersStream = this.persistenceHandler.getAllUsers().stream(); + List users = usersStream.map(u -> u.asUserRep()).collect(Collectors.toList()); + return users; + } + + @Override + public List getRoles(Certificate certificate) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + Stream rolesStream = this.persistenceHandler.getAllRoles().stream(); + List roles = rolesStream.map(r -> r.asRoleRep()).collect(Collectors.toList()); + return roles; + } + + @Override + public List queryUsers(Certificate certificate, UserRep selectorRep) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); String selUserId = selectorRep.getUserId(); String selUsername = selectorRep.getUsername(); @@ -264,33 +315,28 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return roles.containsAll(selectionRoles); } - @Override - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // create new role from RoleRep - Role role = new Role(roleRep); - - // validate policy if not null - validatePolicies(role); - - // delegate to persistence handler - this.persistenceHandler.addOrReplaceRole(role); - } - /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.UserRep, byte[]) */ @Override - public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) { + public void addUser(Certificate certificate, UserRep userRep, byte[] password) { try { // validate who is doing this assertIsPrivilegeAdmin(certificate); + // first validate user + userRep.validate(); + + validateRolesExist(userRep); + + // validate user does not already exist + if (this.persistenceHandler.getUser(userRep.getUsername()) != null) { + String msg = "User {0} can not be added as it already exists!"; + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + String passwordHash = null; if (password != null) { @@ -302,18 +348,173 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), - userRep.getLastname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), - userRep.getProperties()); + User user = createUser(userRep, passwordHash); // delegate to persistence handler - this.persistenceHandler.addOrReplaceUser(user); + this.persistenceHandler.addUser(user); } finally { clearPassword(password); } } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.UserRep, byte[]) + */ + @Override + public void replaceUser(Certificate certificate, UserRep userRep, byte[] password) { + try { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + // first validate user + userRep.validate(); + + validateRolesExist(userRep); + + // validate user exists + if (this.persistenceHandler.getUser(userRep.getUsername()) == null) { + String msg = "User {0} can not be replaced as it does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + + String passwordHash = null; + if (password != null) { + + // validate password meets basic requirements + validatePassword(password); + + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } + + User user = createUser(userRep, passwordHash); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(user); + + } finally { + clearPassword(password); + } + } + + private void validateRolesExist(UserRep userRep) { + // validate all roles exist + for (String role : userRep.getRoles()) { + if (this.persistenceHandler.getRole(role) == null) { + String msg = "Can not add user {0} as role {1} does not exist!"; + msg = MessageFormat.format(msg, userRep.getUsername(), role); + throw new PrivilegeException(msg); + } + } + } + + private User createUser(UserRep userRep, String passwordHash) { + User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), + userRep.getLastname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), + userRep.getProperties()); + return user; + } + + @Override + public void updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + // get existing user + User user = this.persistenceHandler.getUser(userRep.getUsername()); + if (user == null) { + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", userRep.getUsername())); //$NON-NLS-1$ + } + + // if nothing to do, then stop + if (StringHelper.isEmpty(userRep.getFirstname()) && StringHelper.isEmpty(userRep.getLastname()) + && userRep.getLocale() == null + && (userRep.getProperties() == null || userRep.getProperties().isEmpty())) { + throw new PrivilegeException(MessageFormat.format( + "All updateable fields are empty for update of user {0}", userRep.getUsername())); //$NON-NLS-1$ + } + + String userId = user.getUserId(); + String username = user.getUsername(); + String password = user.getPassword(); + String firstname = user.getFirstname(); + String lastname = user.getLastname(); + UserState userState = user.getUserState(); + Set roles = user.getRoles(); + Locale locale = user.getLocale(); + Map propertyMap = user.getProperties(); + + // get updated fields + if (StringHelper.isNotEmpty(userRep.getFirstname())) + firstname = userRep.getFirstname(); + if (StringHelper.isNotEmpty(userRep.getLastname())) + lastname = userRep.getLastname(); + if (userRep.getLocale() != null) + locale = userRep.getLocale(); + if (userRep.getProperties() != null && !userRep.getProperties().isEmpty()) + propertyMap = userRep.getProperties(); + + // create new user + user = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(user); + } + + @Override + public void addRole(Certificate certificate, RoleRep roleRep) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + // first validate role + roleRep.validate(); + + // validate role does not exist + if (this.persistenceHandler.getRole(roleRep.getName()) != null) { + String msg = MessageFormat.format("Can not add role {0} as it already exists!", roleRep.getName()); + throw new PrivilegeException(msg); + } + + // create new role from RoleRep + Role role = new Role(roleRep); + + // validate policy if not null + validatePolicies(role); + + // delegate to persistence handler + this.persistenceHandler.addRole(role); + } + + @Override + public void replaceRole(Certificate certificate, RoleRep roleRep) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + // first validate role + roleRep.validate(); + + // validate role does exist + if (this.persistenceHandler.getRole(roleRep.getName()) == null) { + String msg = MessageFormat.format("Can not replace role {0} as it does not exist!", roleRep.getName()); + throw new PrivilegeException(msg); + } + + // create new role from RoleRep + Role role = new Role(roleRep); + + // validate policy if not null + validatePolicies(role); + + // delegate to persistence handler + this.persistenceHandler.replaceRole(role); + } + @Override public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { @@ -353,7 +554,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role newRole = new Role(role.getName(), privilegeMap); // delegate role replacement to persistence handler - this.persistenceHandler.addOrReplaceRole(newRole); + this.persistenceHandler.replaceRole(newRole); } @Override @@ -377,7 +578,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // validate that role exists - if (getRole(roleName) == null) { + if (this.persistenceHandler.getRole(roleName) == null) { String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ throw new PrivilegeException(msg); } @@ -390,7 +591,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } @Override @@ -424,7 +625,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role newRole = new Role(role.getName(), newPrivileges); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceRole(newRole); + this.persistenceHandler.replaceRole(newRole); } @Override @@ -433,6 +634,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this assertIsPrivilegeAdmin(certificate); + // validate no user is using this role + Set roles = new HashSet<>(Arrays.asList(roleName)); + UserRep selector = new UserRep(null, null, null, null, null, roles, null, null); + List usersWithRole = queryUsers(certificate, selector); + if (!usersWithRole.isEmpty()) { + String usersS = usersWithRole.stream().map(UserRep::getUsername).collect(Collectors.joining(", ")); + String msg = "The role {0} can not be removed as the following {1} user have the role assigned: {2}"; + msg = MessageFormat.format(msg, roleName, usersWithRole.size(), usersS); + throw new PrivilegeException(msg); + } + // delegate role removal to persistence handler Role removedRole = this.persistenceHandler.removeRole(roleName); @@ -470,7 +682,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } @Override @@ -488,7 +700,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // return user rep if it was removed return removedUser.asUserRep(); - } @Override @@ -508,7 +719,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } @Override @@ -528,7 +739,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } /** @@ -572,7 +783,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); // perform automatic persisting, if enabled if (this.autoPersistOnPasswordChange) { @@ -601,7 +812,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), state, user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } /** diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index 85b009a33..c3fd0fdc6 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -95,20 +95,36 @@ public interface PersistenceHandler { public Role removeRole(String roleName); /** - * Adds a {@link User} object to the underlying database. If the {@link User} already exists, it is replaced + * Adds a {@link User} object to the underlying database * * @param user * the {@link User} object to add */ - public void addOrReplaceUser(User user); + public void addUser(User user); /** - * Adds a {@link Role} object to the underlying database. If the {@link Role} already exists, it is replaced + * Replaces the existing {@link User} object in the underlying database + * + * @param user + * the {@link User} object to add + */ + public void replaceUser(User user); + + /** + * Adds a {@link Role} object to the underlying database * * @param role * the {@link User} object to add */ - public void addOrReplaceRole(Role role); + public void addRole(Role role); + + /** + * Replaces the {@link Role} object in the underlying database + * + * @param role + * the {@link User} object to add + */ + public void replaceRole(Role role); /** * Informs this {@link PersistenceHandler} to persist any changes which need to be saved diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index cf472d41e..15d16d686 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -17,6 +17,7 @@ package ch.eitchnet.privilege.handler; import java.util.List; import java.util.Locale; +import java.util.Map; import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; @@ -29,6 +30,7 @@ import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * The {@link PrivilegeHandler} is the centrally exposed API for accessing the privilege library. It exposes all needed @@ -36,7 +38,6 @@ import ch.eitchnet.privilege.model.internal.User; * an action * * @author Robert von Burg - * */ public interface PrivilegeHandler { @@ -48,33 +49,69 @@ public interface PrivilegeHandler { /** * Returns a {@link UserRep} for the given username * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username * the name of the {@link UserRep} to return * * @return the {@link UserRep} for the given username, or null if it was not found */ - public UserRep getUser(String username); + public UserRep getUser(Certificate certificate, String username); /** * Returns a {@link RoleRep} for the given roleName * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param roleName * the name of the {@link RoleRep} to return * * @return the {@link RoleRep} for the given roleName, or null if it was not found */ - public RoleRep getRole(String roleName); + public RoleRep getRole(Certificate certificate, String roleName); + + /** + * Returns the map of {@link PrivilegePolicy} definitions + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return the map of {@link PrivilegePolicy} definitions + */ + public Map getPolicyDefs(Certificate certificate); + + /** + * Returns all {@link RoleRep RoleReps} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return the list of {@link RoleRep RoleReps} + */ + public List getRoles(Certificate certificate); + + /** + * Returns all {@link UserRep UserReps} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return the list of {@link UserRep UserReps} + */ + public List getUsers(Certificate certificate); /** * Method to query {@link UserRep} which meet the criteria set in the given {@link UserRep}. Null fields mean the - * fields are not relevant. + * fields are irrelevant. * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param selectorRep * the {@link UserRep} to use as criteria selection * * @return a list of {@link UserRep}s which fit the given criteria */ - public List queryUsers(UserRep selectorRep); + public List queryUsers(Certificate certificate, UserRep selectorRep); /** * Removes the user with the given username @@ -125,7 +162,7 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * if there is anything wrong with this certificate or the role is still in use by a user */ public RoleRep removeRole(Certificate certificate, String roleName) throws AccessDeniedException, PrivilegeException; @@ -150,7 +187,7 @@ public interface PrivilegeHandler { /** *

      - * Adds a new user, or replaces the user with the information from this {@link UserRep} if the user already exists + * Adds a new user with the information from this {@link UserRep} *

      * *

      @@ -170,13 +207,71 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * if there is anything wrong with this certificate or the user already exists */ - public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) - throws AccessDeniedException, PrivilegeException; + public void addUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, + PrivilegeException; /** - * Adds a new role, or replaces the role with the information from this {@link RoleRep} if the role already exists + *

      + * Updates the fields for the user with the given user. All fields on the given {@link UserRep} which are non-null + * will be updated on the existing user. The username on the given {@link UserRep} must be set and correspond to an + * existing user. + *

      + * + * The following fields are considered updateable: + *
        + *
      • {@link UserRep#getFirstname()}
      • + *
      • {@link UserRep#getLastname()}
      • + *
      • {@link UserRep#getLocale()}
      • + *
      • {@link UserRep#getProperties()} - the existing properties will be replaced with the given properties
      • + *
      + * + *

      + * Any other fields will be ignored + *

      + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param userRep + * the {@link UserRep} with the fields set to their new values + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the user does not exist + */ + public void updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException; + + /** + *

      + * Replaces the existing user with the information from this {@link UserRep} if the user already exists + *

      + * + *

      + * If the password given is null, then the user is created, but can not not login! Otherwise the password must meet + * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(byte[])} + *

      + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param userRep + * the {@link UserRep} containing the information to replace the existing {@link User} + * @param password + * the password of the new user. If the password is null, then this is accepted but the user can not + * login, otherwise the password must be validated against + * {@link PrivilegeHandler#validatePassword(byte[])} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the user does not exist + */ + public void replaceUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, + PrivilegeException; + + /** + * Adds a new role with the information from this {@link RoleRep} * * @param certificate * the {@link Certificate} of the user which has the privilege to perform this action @@ -186,10 +281,24 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * if there is anything wrong with this certificate or if the role already exists */ - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, - PrivilegeException; + public void addRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; + + /** + * Replaces the existing role with the information from this {@link RoleRep} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param roleRep + * the {@link RoleRep} containing the information to replace the existing {@link Role} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the role does not exist + */ + public void replaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; /** * Adds the role with the given roleName to the {@link User} with the given username @@ -204,25 +313,25 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * if there is anything wrong with this certificate or if the role does not exist */ public void addRoleToUser(Certificate certificate, String username, String roleName) throws AccessDeniedException, PrivilegeException; /** - * Adds the {@link PrivilegeRep} to the {@link Role} with the given roleName + * Adds the {@link PrivilegeRep} to the {@link Role} with the given roleName or replaces it, if it already exists * * @param certificate * the {@link Certificate} of the user which has the privilege to perform this action * @param roleName * the roleName of the {@link Role} to which the privilege should be added * @param privilegeRep - * the representation of the {@link IPrivilege} which should be added to the {@link Role} + * the representation of the {@link IPrivilege} which should be added or replaced on the {@link Role} * * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * if there is anything wrong with this certificate or the role does not exist */ public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) throws AccessDeniedException, PrivilegeException; diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index fe9745bc5..420edcf3c 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -94,13 +94,35 @@ public class XmlPersistenceHandler implements PersistenceHandler { } @Override - public void addOrReplaceUser(User user) { + public void addUser(User user) { + if (this.userMap.containsKey(user.getUsername())) + throw new IllegalStateException(MessageFormat.format("The user {0} already exists!", user.getUsername())); this.userMap.put(user.getUsername(), user); this.userMapDirty = true; } @Override - public void addOrReplaceRole(Role role) { + public void replaceUser(User user) { + if (!this.userMap.containsKey(user.getUsername())) + throw new IllegalStateException(MessageFormat.format( + "The user {0} can not be replaced as it does not exiset!", user.getUsername())); + this.userMap.put(user.getUsername(), user); + this.userMapDirty = true; + } + + @Override + public void addRole(Role role) { + if (this.userMap.containsKey(role.getName())) + throw new IllegalStateException(MessageFormat.format("The role {0} already exists!", role.getName())); + this.roleMap.put(role.getName(), role); + this.roleMapDirty = true; + } + + @Override + public void replaceRole(Role role) { + if (!this.userMap.containsKey(role)) + throw new IllegalStateException(MessageFormat.format( + "The role {0} can not be replaced as it does not exiset!", role.getName())); this.roleMap.put(role.getName(), role); this.roleMapDirty = true; } diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index a479f2bd4..cf9538c3c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -45,13 +45,13 @@ public final class Certificate implements Serializable { private final String lastname; private final String authToken; + private final Set userRoles; + private final Map propertyMap; + private final Map sessionDataMap; + private Locale locale; private long lastAccess; - private Set userRoles; - private Map propertyMap; - private Map sessionDataMap; - /** * Default constructor initializing with all information needed for this certificate * @@ -120,7 +120,19 @@ public final class Certificate implements Serializable { * @return the user's roles */ public Set getUserRoles() { - return userRoles; + return this.userRoles; + } + + /** + * Returns true if the user of this certificate has the given role + * + * @param role + * the role to check for + * + * @return true if the user of this certificate has the given role + */ + public boolean hasRole(String role) { + return this.userRoles.contains(role); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index e2c1a3a39..d0edd8119 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -18,6 +18,12 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Set; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.model.internal.Role; @@ -31,14 +37,25 @@ import ch.eitchnet.utils.helper.StringHelper; * * @author Robert von Burg */ +@XmlRootElement(name = "Privilege") +@XmlAccessorType(XmlAccessType.NONE) public class PrivilegeRep implements Serializable { private static final long serialVersionUID = 1L; + @XmlAttribute(name = "name") private String name; + + @XmlAttribute(name = "policy") private String policy; + + @XmlAttribute(name = "allAllowed") private boolean allAllowed; + + @XmlElement(name = "denyList") private Set denyList; + + @XmlElement(name = "allowList") private Set allowList; /** @@ -62,8 +79,14 @@ public class PrivilegeRep implements Serializable { this.allAllowed = allAllowed; this.denyList = denyList; this.allowList = allowList; + } - validate(); + /** + * + */ + @SuppressWarnings("unused") + private PrivilegeRep() { + // no-arg constructor for JAXB } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index c32a226bc..296a7eccb 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -16,8 +16,18 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.utils.helper.StringHelper; @@ -29,11 +39,15 @@ import ch.eitchnet.utils.helper.StringHelper; * * @author Robert von Burg */ +@XmlRootElement(name = "Role") +@XmlAccessorType(XmlAccessType.NONE) public class RoleRep implements Serializable { private static final long serialVersionUID = 1L; + @XmlAttribute(name = "name") private String name; + private Map privilegeMap; /** @@ -45,11 +59,16 @@ public class RoleRep implements Serializable { * the map of privileges granted to this role */ public RoleRep(String name, Map privilegeMap) { - this.name = name; this.privilegeMap = privilegeMap; + } - validate(); + /** + * + */ + @SuppressWarnings("unused") + private RoleRep() { + // no-arg constructor for JAXB } /** @@ -58,6 +77,18 @@ public class RoleRep implements Serializable { public void validate() { if (StringHelper.isEmpty(this.name)) throw new PrivilegeException("name is null"); //$NON-NLS-1$ + + if (this.privilegeMap != null && !this.privilegeMap.isEmpty()) { + for (PrivilegeRep privilege : this.privilegeMap.values()) { + try { + privilege.validate(); + } catch (Exception e) { + String msg = "Privilege {0} is invalid on role {1}"; + msg = MessageFormat.format(msg, privilege.getName(), this.name); + throw new PrivilegeException(msg, e); + } + } + } } /** @@ -82,6 +113,33 @@ public class RoleRep implements Serializable { return this.privilegeMap; } + /** + * Returns the privileges assigned to this Role as a list + * + * @return the privileges assigned to this Role as a list + */ + @XmlElement(name = "privileges") + public List getPrivileges() { + return new ArrayList<>(this.privilegeMap.values()); + } + + /** + * Sets the privileges on this from a list + * + * @param privileges + * the list of privileges to assign to this role + */ + public void setPrivileges(List privileges) { + if (this.privilegeMap == null) + this.privilegeMap = new HashMap<>(privileges.size()); + else + this.privilegeMap.clear(); + + for (PrivilegeRep privilegeRep : privileges) { + this.privilegeMap.put(privilegeRep.getName(), privilegeRep); + } + } + /** * Returns a string representation of this object displaying its concrete type and its values * diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 3ffa0738d..8ccf97b1e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -16,14 +16,22 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.xml.XmlKeyValue; /** * To keep certain details of the {@link User} itself hidden from remote clients and make sure instances are only edited @@ -32,17 +40,33 @@ import ch.eitchnet.utils.helper.StringHelper; * * @author Robert von Burg */ +@XmlRootElement(name = "User") +@XmlAccessorType(XmlAccessType.NONE) public class UserRep implements Serializable { private static final long serialVersionUID = 1L; - private final String userId; + @XmlAttribute(name = "userId") + private String userId; + + @XmlAttribute(name = "username") private String username; + + @XmlAttribute(name = "firstname") private String firstname; + + @XmlAttribute(name = "lastname") private String lastname; + + @XmlAttribute(name = "userState") private UserState userState; - private Set roles; + + @XmlAttribute(name = "locale") private Locale locale; + + @XmlElement(name = "roles") + private Set roles; + private Map propertyMap; /** @@ -75,8 +99,14 @@ public class UserRep implements Serializable { this.roles = roles; this.locale = locale; this.propertyMap = propertyMap; + } - validate(); + /** + * + */ + @SuppressWarnings("unused") + private UserRep() { + // No arg constructor for JAXB } /** @@ -93,16 +123,14 @@ public class UserRep implements Serializable { if (this.userState == null) throw new PrivilegeException("userState is null"); //$NON-NLS-1$ - if (this.userState != UserState.SYSTEM) { - if (StringHelper.isEmpty(this.firstname)) - throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ + if (StringHelper.isEmpty(this.firstname)) + throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ - if (StringHelper.isEmpty(this.lastname)) - throw new PrivilegeException("lastname is null or empty"); //$NON-NLS-1$ - } + if (StringHelper.isEmpty(this.lastname)) + throw new PrivilegeException("lastname is null or empty"); //$NON-NLS-1$ - if (this.roles == null) - throw new PrivilegeException("roles is null"); //$NON-NLS-1$ + if (this.roles == null || this.roles.isEmpty()) + throw new PrivilegeException("roles is null or empty"); //$NON-NLS-1$ } /** @@ -244,6 +272,26 @@ public class UserRep implements Serializable { return this.propertyMap; } + /** + * Returns the string map properties of this user as a list of {@link XmlKeyValue} elements + * + * @return the string map properties of this user as a list of {@link XmlKeyValue} elements + */ + @XmlElement(name = "properties") + public List getPropertiesAsKeyValue() { + return XmlKeyValue.valueOf(this.propertyMap); + } + + /** + * Sets the string map properties of this user from the given list of {@link XmlKeyValue} + * + * @param values + * the list of {@link XmlKeyValue} from which to set the properties + */ + public void setPropertiesAsKeyValue(List values) { + this.propertyMap = XmlKeyValue.toMap(values); + } + /** * Returns a string representation of this object displaying its concrete type and its values * diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 25804e155..6b0122b32 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -16,12 +16,15 @@ package ch.eitchnet.privilege.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import org.junit.AfterClass; @@ -68,6 +71,7 @@ public class PrivilegeTest { private static final String SYSTEM_USER_ADMIN2 = "system_admin2"; private static final byte[] PASS_BOB = "admin1".getBytes(); private static final String ROLE_APP_USER = "AppUser"; + private static final String ROLE_MY = "MyRole"; private static final String ROLE_TEMP = "temp"; private static final String ROLE_USER = "user"; private static final byte[] PASS_DEF = "def".getBytes(); @@ -213,7 +217,7 @@ public class PrivilegeTest { RoleRep roleRep = new RoleRep(ROLE_TEMP, privilegeMap); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.addRole(certificate, roleRep); privilegeHandler.persist(certificate); } finally { logout(); @@ -308,6 +312,117 @@ public class PrivilegeTest { } } + @Test + public void shouldUpdateAdmin() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + // validate name is not yet set + UserRep user = privilegeHandler.getUser(certificate, ADMIN); + assertNotEquals("The", user.getFirstname()); + assertNotEquals("Admin", user.getLastname()); + + // let's add a new user bob + UserRep userRep = new UserRep(null, ADMIN, "The", "Admin", null, null, null, null); + privilegeHandler.updateUser(certificate, userRep); + + user = privilegeHandler.getUser(certificate, ADMIN); + assertEquals("The", user.getFirstname()); + assertEquals("Admin", user.getLastname()); + + } finally { + logout(); + } + } + + @Test + public void shouldFailUpdateInexistantUser() { + exception.expect(PrivilegeException.class); + exception.expectMessage("User bob does not exist"); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + // let's add a new user bob + UserRep userRep = new UserRep(null, BOB, null, null, null, null, null, null); + privilegeHandler.updateUser(certificate, userRep); + } finally { + logout(); + } + } + + @Test + public void shouldFailUpdateAdminNoChanges() { + exception.expect(PrivilegeException.class); + exception.expectMessage("All updateable fields are empty for update of user admin"); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + // let's add a new user bob + UserRep userRep = new UserRep(null, ADMIN, null, null, null, null, null, null); + privilegeHandler.updateUser(certificate, userRep); + } finally { + logout(); + } + } + + @Test + public void shouldQueryUsers() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + UserRep selectorRep = new UserRep(null, ADMIN, null, null, null, null, null, null); + List users = privilegeHandler.queryUsers(certificate, selectorRep); + assertEquals(1, users.size()); + assertEquals(ADMIN, users.get(0).getUsername()); + + } finally { + logout(); + } + } + + @Test + public void shouldQueryUsersByRoles() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + UserRep selectorRep = new UserRep(null, null, null, null, null, new HashSet<>( + Arrays.asList("PrivilegeAdmin")), null, null); + List users = privilegeHandler.queryUsers(certificate, selectorRep); + assertEquals(1, users.size()); + assertEquals(ADMIN, users.get(0).getUsername()); + + } finally { + logout(); + } + } + + @Test + public void shouldQueryUsersByRoles2() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + UserRep selectorRep = new UserRep(null, null, null, null, null, new HashSet<>(Arrays.asList(ROLE_TEMP)), + null, null); + List users = privilegeHandler.queryUsers(certificate, selectorRep); + assertEquals(0, users.size()); + + } finally { + logout(); + } + } + /** * This test performs multiple tests which are dependent on each other as the following is done: *
        @@ -328,7 +443,6 @@ public class PrivilegeTest { *
      • add app role to bob
      • *
      • perform restrictable as bob
      • *
      - * */ @Test public void testUserStory() throws Exception { @@ -336,7 +450,6 @@ public class PrivilegeTest { addBobAsAdmin(); failAuthAsBobNotEnabled(); enableBob(); - failAuthAsBobNoRole(); addRoleUser(); addRoleUserToBob(); authAsBob(); @@ -454,7 +567,7 @@ public class PrivilegeTest { userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + TED); privilegeHandler.persist(certificate); } finally { @@ -487,7 +600,7 @@ public class PrivilegeTest { userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, new HashMap()); certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addUser(certificate, userRep, null); fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate; @@ -526,27 +639,13 @@ public class PrivilegeTest { Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.addRole(certificate, roleRep); privilegeHandler.persist(certificate); } finally { logout(); } } - private void failAuthAsBobNoRole() { - try { - // testFailAuthUserBob - // Will fail as user bob has no role - privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); - fail("User Bob may not authenticate because the user has no role"); - } catch (PrivilegeException e) { - String msg = "User bob does not have any roles defined!"; - assertEquals(msg, e.getMessage()); - } finally { - logout(); - } - } - private void enableBob() { try { // testEnableUserBob @@ -578,10 +677,10 @@ public class PrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, - new HashMap()); + UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet( + Arrays.asList(ROLE_MY)), null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + BOB); // set bob's password From eeb3356372500e6413af613079c7ec71bb1f6e4c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 13:39:27 +0100 Subject: [PATCH 371/457] [Minor] fixed broken test --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index b1db9740e..6d46fc25d 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -169,7 +169,7 @@ public class XmlTest { assertNotNull(roles); assertEquals(3, users.size()); - assertEquals(5, roles.size()); + assertEquals(6, roles.size()); // assert model From 5ef43eaebe670b0cf3f8ea1ccbc251b2c48f3dca Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 20:51:10 +0100 Subject: [PATCH 372/457] [Major] All methods in PrivilegeHandler now return a value after op - also fixed JAXB (un)marshalling of list values on UserRep and RoleRep --- .../handler/DefaultPrivilegeHandler.java | 121 +++++++++++------- .../privilege/handler/PrivilegeHandler.java | 30 +++-- .../handler/XmlPersistenceHandler.java | 6 +- .../privilege/model/PrivilegeRep.java | 5 +- .../ch/eitchnet/privilege/model/RoleRep.java | 38 ++---- .../ch/eitchnet/privilege/model/UserRep.java | 64 +++++++-- .../model/internal/PrivilegeImpl.java | 2 +- .../privilege/model/internal/Role.java | 23 ++-- .../privilege/test/PrivilegeTest.java | 12 +- 9 files changed, 178 insertions(+), 123 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 6a2fa51cb..ba3f28c5f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -184,7 +184,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { UserState selUserState = selectorRep.getUserState(); Locale selLocale = selectorRep.getLocale(); Set selRoles = selectorRep.getRoles(); - Map selPropertyMap = selectorRep.getProperties(); + Map selPropertyMap = selectorRep.getPropertyMap(); List result = new ArrayList<>(); List allUsers = this.persistenceHandler.getAllUsers(); @@ -315,17 +315,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return roles.containsAll(selectionRoles); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep, byte[]) - */ @Override - public void addUser(Certificate certificate, UserRep userRep, byte[] password) { + public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) { try { // validate who is doing this assertIsPrivilegeAdmin(certificate); + // make sure userId is not set + if (StringHelper.isNotEmpty(userRep.getUserId())) { + String msg = "UserId can not be set when adding a new user!"; + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + + // set userId + userRep.setUserId(StringHelper.getUniqueId()); + // first validate user userRep.validate(); @@ -353,17 +358,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.addUser(user); + return user.asUserRep(); + } finally { clearPassword(password); } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep, byte[]) - */ @Override - public void replaceUser(Certificate certificate, UserRep userRep, byte[] password) { + public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) { try { // validate who is doing this @@ -375,11 +378,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateRolesExist(userRep); // validate user exists - if (this.persistenceHandler.getUser(userRep.getUsername()) == null) { + User user = this.persistenceHandler.getUser(userRep.getUsername()); + if (user == null) { String msg = "User {0} can not be replaced as it does not exist!"; throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); } + // validate same userId + if (!user.getUserId().equals(userRep.getUserId())) { + String msg = "UserId of existing user {0} does not match userRep {1}"; + msg = MessageFormat.format(msg, user.getUserId(), userRep.getUserId()); + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + String passwordHash = null; if (password != null) { @@ -390,11 +401,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { passwordHash = this.encryptionHandler.convertToHash(password); } - User user = createUser(userRep, passwordHash); + user = createUser(userRep, passwordHash); // delegate to persistence handler this.persistenceHandler.replaceUser(user); + return user.asUserRep(); + } finally { clearPassword(password); } @@ -414,12 +427,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private User createUser(UserRep userRep, String passwordHash) { User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getLastname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), - userRep.getProperties()); + userRep.getPropertyMap()); return user; } @Override - public void updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException { + public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, + PrivilegeException { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -456,17 +470,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (userRep.getLocale() != null) locale = userRep.getLocale(); if (userRep.getProperties() != null && !userRep.getProperties().isEmpty()) - propertyMap = userRep.getProperties(); + propertyMap = userRep.getPropertyMap(); // create new user user = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); // delegate to persistence handler this.persistenceHandler.replaceUser(user); + + return user.asUserRep(); } @Override - public void addRole(Certificate certificate, RoleRep roleRep) { + public RoleRep addRole(Certificate certificate, RoleRep roleRep) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -488,10 +504,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.addRole(role); + + return role.asRoleRep(); } @Override - public void replaceRole(Certificate certificate, RoleRep roleRep) { + public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -513,10 +531,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.replaceRole(role); + + return role.asRoleRep(); } @Override - public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { + public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -541,6 +561,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new role with the additional privilege IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); + // copy existing privileges Set existingPrivilegeNames = role.getPrivilegeNames(); Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); @@ -548,6 +569,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { IPrivilege privilege = role.getPrivilege(name); privilegeMap.put(name, privilege); } + // add new one privilegeMap.put(newPrivilege.getName(), newPrivilege); @@ -555,10 +577,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate role replacement to persistence handler this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); } @Override - public void addRoleToUser(Certificate certificate, String username, String roleName) { + public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -569,12 +593,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } - // ignore if user already has role + // check that user not already has role Set currentRoles = user.getRoles(); if (currentRoles.contains(roleName)) { String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); //$NON-NLS-1$ - DefaultPrivilegeHandler.logger.error(msg); - return; + throw new PrivilegeException(msg); } // validate that role exists @@ -592,10 +615,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } @Override - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -626,6 +651,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); } @Override @@ -647,16 +674,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate role removal to persistence handler Role removedRole = this.persistenceHandler.removeRole(roleName); - - if (removedRole == null) - return null; + if (removedRole == null) { + String msg = "Can not remove Role {0} because role does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, roleName)); + } // return role rep if it was removed return removedRole.asRoleRep(); } @Override - public void removeRoleFromUser(Certificate certificate, String username, String roleName) { + public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -670,9 +698,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // ignore if user does not have role Set currentRoles = user.getRoles(); if (!currentRoles.contains(roleName)) { - String msg = MessageFormat.format("User {0} does not have role {1}", user, roleName); //$NON-NLS-1$ - logger.error(msg); - return; + String msg = MessageFormat.format("User {0} does not have role {1}", user.getUsername(), roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); } // create new user @@ -683,6 +710,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } @Override @@ -693,17 +722,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user removal to persistence handler User removedUser = this.persistenceHandler.removeUser(username); - - // return user rep if it was removed - if (removedUser == null) - return null; + if (removedUser == null) { + String msg = "Can not remove User {0} because user does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, username)); + } // return user rep if it was removed return removedUser.asUserRep(); } @Override - public void setUserLocale(Certificate certificate, String username, Locale locale) { + public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -720,10 +749,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } @Override - public void setUserName(Certificate certificate, String username, String firstname, String lastname) { + public UserRep setUserName(Certificate certificate, String username, String firstname, String lastname) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -740,12 +771,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, byte[]) - */ @Override public void setUserPassword(Certificate certificate, String username, byte[] password) { try { @@ -796,7 +825,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public void setUserState(Certificate certificate, String username, UserState state) { + public UserRep setUserState(Certificate certificate, String username, UserState state) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -813,14 +842,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, byte[]) - * - * @throws AccessDeniedException - * if the user credentials are not valid - */ @Override public Certificate authenticate(String username, byte[] password) { diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 15d16d686..46c0e0e55 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -146,7 +146,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void removeRoleFromUser(Certificate certificate, String username, String roleName) + public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) throws AccessDeniedException, PrivilegeException; /** @@ -182,7 +182,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) + public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) throws AccessDeniedException, PrivilegeException; /** @@ -209,7 +209,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or the user already exists */ - public void addUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, + public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, PrivilegeException; /** @@ -241,7 +241,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the user does not exist */ - public void updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException; + public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, + PrivilegeException; /** *

      @@ -267,7 +268,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the user does not exist */ - public void replaceUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, + public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, PrivilegeException; /** @@ -283,7 +284,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the role already exists */ - public void addRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; + public RoleRep addRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; /** * Replaces the existing role with the information from this {@link RoleRep} @@ -298,7 +299,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the role does not exist */ - public void replaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; + public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, + PrivilegeException; /** * Adds the role with the given roleName to the {@link User} with the given username @@ -315,8 +317,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the role does not exist */ - public void addRoleToUser(Certificate certificate, String username, String roleName) throws AccessDeniedException, - PrivilegeException; + public UserRep addRoleToUser(Certificate certificate, String username, String roleName) + throws AccessDeniedException, PrivilegeException; /** * Adds the {@link PrivilegeRep} to the {@link Role} with the given roleName or replaces it, if it already exists @@ -333,7 +335,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or the role does not exist */ - public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) + public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) throws AccessDeniedException, PrivilegeException; /** @@ -382,7 +384,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserName(Certificate certificate, String username, String firstname, String lastname) + public UserRep setUserName(Certificate certificate, String username, String firstname, String lastname) throws AccessDeniedException, PrivilegeException; /** @@ -400,8 +402,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserState(Certificate certificate, String username, UserState state) throws AccessDeniedException, - PrivilegeException; + public UserRep setUserState(Certificate certificate, String username, UserState state) + throws AccessDeniedException, PrivilegeException; /** * Changes the {@link Locale} of the user @@ -418,7 +420,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, + public UserRep setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, PrivilegeException; /** diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 420edcf3c..292743a7e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -112,7 +112,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void addRole(Role role) { - if (this.userMap.containsKey(role.getName())) + if (this.roleMap.containsKey(role.getName())) throw new IllegalStateException(MessageFormat.format("The role {0} already exists!", role.getName())); this.roleMap.put(role.getName(), role); this.roleMapDirty = true; @@ -120,9 +120,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void replaceRole(Role role) { - if (!this.userMap.containsKey(role)) + if (!this.roleMap.containsKey(role.getName())) throw new IllegalStateException(MessageFormat.format( - "The role {0} can not be replaced as it does not exiset!", role.getName())); + "The role {0} can not be replaced as it does not exist!", role.getName())); this.roleMap.put(role.getName(), role); this.roleMapDirty = true; } diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index d0edd8119..66dff555b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -16,6 +16,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +import java.util.HashSet; import java.util.Set; import javax.xml.bind.annotation.XmlAccessType; @@ -159,7 +160,7 @@ public class PrivilegeRep implements Serializable { * @return the denyList */ public Set getDenyList() { - return this.denyList; + return this.denyList == null ? new HashSet<>() : this.denyList; } /** @@ -174,7 +175,7 @@ public class PrivilegeRep implements Serializable { * @return the allowList */ public Set getAllowList() { - return this.allowList; + return this.allowList == null ? new HashSet<>() : this.allowList; } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index 296a7eccb..6e449ff97 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -18,9 +18,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -48,19 +46,20 @@ public class RoleRep implements Serializable { @XmlAttribute(name = "name") private String name; - private Map privilegeMap; + @XmlElement(name = "privileges") + private List privileges; /** * Default constructor * * @param name * the name of this role - * @param privilegeMap - * the map of privileges granted to this role + * @param privileges + * the list of privileges granted to this role */ - public RoleRep(String name, Map privilegeMap) { + public RoleRep(String name, List privileges) { this.name = name; - this.privilegeMap = privilegeMap; + this.privileges = privileges; } /** @@ -78,8 +77,8 @@ public class RoleRep implements Serializable { if (StringHelper.isEmpty(this.name)) throw new PrivilegeException("name is null"); //$NON-NLS-1$ - if (this.privilegeMap != null && !this.privilegeMap.isEmpty()) { - for (PrivilegeRep privilege : this.privilegeMap.values()) { + if (this.privileges != null && !this.privileges.isEmpty()) { + for (PrivilegeRep privilege : this.privileges) { try { privilege.validate(); } catch (Exception e) { @@ -106,21 +105,13 @@ public class RoleRep implements Serializable { this.name = name; } - /** - * @return the privilegeMap - */ - public Map getPrivilegeMap() { - return this.privilegeMap; - } - /** * Returns the privileges assigned to this Role as a list * * @return the privileges assigned to this Role as a list */ - @XmlElement(name = "privileges") public List getPrivileges() { - return new ArrayList<>(this.privilegeMap.values()); + return this.privileges == null ? new ArrayList<>() : this.privileges; } /** @@ -130,14 +121,7 @@ public class RoleRep implements Serializable { * the list of privileges to assign to this role */ public void setPrivileges(List privileges) { - if (this.privilegeMap == null) - this.privilegeMap = new HashMap<>(privileges.size()); - else - this.privilegeMap.clear(); - - for (PrivilegeRep privilegeRep : privileges) { - this.privilegeMap.put(privilegeRep.getName(), privilegeRep); - } + this.privileges = privileges; } /** @@ -152,7 +136,7 @@ public class RoleRep implements Serializable { builder.append("RoleRep [name="); builder.append(this.name); builder.append(", privilegeMap="); - builder.append((this.privilegeMap == null ? "null" : this.privilegeMap)); + builder.append((this.privileges == null ? "null" : this.privileges)); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 8ccf97b1e..6300fe96c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -16,6 +16,9 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -67,7 +70,8 @@ public class UserRep implements Serializable { @XmlElement(name = "roles") private Set roles; - private Map propertyMap; + @XmlElement(name = "properties") + private List properties; /** * Default constructor @@ -98,7 +102,7 @@ public class UserRep implements Serializable { this.userState = userState; this.roles = roles; this.locale = locale; - this.propertyMap = propertyMap; + this.properties = propertyMap == null ? new ArrayList<>() : XmlKeyValue.valueOf(propertyMap); } /** @@ -140,6 +144,16 @@ public class UserRep implements Serializable { return this.userId; } + /** + * Set the userId + * + * @param userId + * to set + */ + public void setUserId(String userId) { + this.userId = userId; + } + /** * @return the username */ @@ -239,7 +253,13 @@ public class UserRep implements Serializable { * @return the property with the given key, or null if the property is not defined */ public String getProperty(String key) { - return this.propertyMap.get(key); + if (this.properties == null) + return null; + for (XmlKeyValue keyValue : this.properties) { + if (keyValue.getKey().equals(key)) + return keyValue.getValue(); + } + return null; } /** @@ -251,7 +271,21 @@ public class UserRep implements Serializable { * the value of the property to set */ public void setProperty(String key, String value) { - this.propertyMap.put(key, value); + if (this.properties == null) + this.properties = new ArrayList<>(); + + boolean updated = false; + + for (XmlKeyValue keyValue : this.properties) { + if (keyValue.getKey().equals(key)) { + keyValue.setValue(value); + updated = true; + } + } + + if (!updated) { + this.properties.add(new XmlKeyValue(key, value)); + } } /** @@ -260,7 +294,13 @@ public class UserRep implements Serializable { * @return the {@link Set} of keys of all properties */ public Set getPropertyKeySet() { - return this.propertyMap.keySet(); + if (this.properties == null) + return new HashSet<>(); + Set keySet = new HashSet<>(this.properties.size()); + for (XmlKeyValue keyValue : this.properties) { + keySet.add(keyValue.getKey()); + } + return keySet; } /** @@ -268,8 +308,10 @@ public class UserRep implements Serializable { * * @return the map of properties */ - public Map getProperties() { - return this.propertyMap; + public Map getPropertyMap() { + if (this.properties == null) + return new HashMap<>(); + return XmlKeyValue.toMap(this.properties); } /** @@ -278,8 +320,8 @@ public class UserRep implements Serializable { * @return the string map properties of this user as a list of {@link XmlKeyValue} elements */ @XmlElement(name = "properties") - public List getPropertiesAsKeyValue() { - return XmlKeyValue.valueOf(this.propertyMap); + public List getProperties() { + return this.properties == null ? new ArrayList<>() : this.properties; } /** @@ -288,8 +330,8 @@ public class UserRep implements Serializable { * @param values * the list of {@link XmlKeyValue} from which to set the properties */ - public void setPropertiesAsKeyValue(List values) { - this.propertyMap = XmlKeyValue.toMap(values); + public void setProperties(List values) { + this.properties = values; } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index 3efe3e36a..9cfe3bca5 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -101,7 +101,7 @@ public final class PrivilegeImpl implements IPrivilege { */ public PrivilegeImpl(PrivilegeRep privilegeRep) { this(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), - privilegeRep.getDenyList()); + privilegeRep.getAllowList()); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index b962095b7..6048f672f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -15,9 +15,12 @@ */ package ch.eitchnet.privilege.model.internal; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeException; @@ -78,14 +81,14 @@ public final class Role { throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } - if (roleRep.getPrivilegeMap() == null) { - throw new PrivilegeException("No privileges defined!"); //$NON-NLS-1$ + if (roleRep.getPrivileges() == null) { + throw new PrivilegeException("Privileges may not be null!"); //$NON-NLS-1$ } - // build privileges from reps - Map privilegeMap = new HashMap(roleRep.getPrivilegeMap().size()); - for (String privilegeName : roleRep.getPrivilegeMap().keySet()) { - privilegeMap.put(privilegeName, new PrivilegeImpl(roleRep.getPrivilegeMap().get(privilegeName))); + // build privileges from rep + Map privilegeMap = new HashMap(roleRep.getPrivileges().size()); + for (PrivilegeRep privilege : roleRep.getPrivileges()) { + privilegeMap.put(privilege.getName(), new PrivilegeImpl(privilege)); } this.name = name; @@ -133,11 +136,11 @@ public final class Role { * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - Map privilegeMap = new HashMap(); - for (String privilegeName : this.privilegeMap.keySet()) { - privilegeMap.put(privilegeName, this.privilegeMap.get(privilegeName).asPrivilegeRep()); + List privileges = new ArrayList(); + for (Entry entry : this.privilegeMap.entrySet()) { + privileges.add(entry.getValue().asPrivilegeRep()); } - return new RoleRep(this.name, privilegeMap); + return new RoleRep(this.name, privileges); } /** diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 6b0122b32..0671c4591 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -21,11 +21,11 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import org.junit.AfterClass; import org.junit.Before; @@ -213,8 +213,7 @@ public class PrivilegeTest { try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(ROLE_TEMP, privilegeMap); + RoleRep roleRep = new RoleRep(ROLE_TEMP, new ArrayList<>()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRole(certificate, roleRep); @@ -564,7 +563,7 @@ public class PrivilegeTest { // let's add a new user ted HashSet roles = new HashSet(); roles.add(ROLE_USER); - userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, + userRep = new UserRep(null, TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addUser(certificate, userRep, null); @@ -636,8 +635,7 @@ public class PrivilegeTest { try { // add role user login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); + RoleRep roleRep = new RoleRep(ROLE_USER, new ArrayList()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRole(certificate, roleRep); privilegeHandler.persist(certificate); @@ -677,7 +675,7 @@ public class PrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet( + UserRep userRep = new UserRep(null, BOB, "Bob", "Newman", UserState.NEW, new HashSet( Arrays.asList(ROLE_MY)), null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addUser(certificate, userRep, null); From a3d76d4cd88ffd1fbeb6e9c646db0e55211f6d09 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 21:44:21 +0100 Subject: [PATCH 373/457] [Major] allow user to change their own locale incl. auto persist --- config/Privilege.xml | 2 +- .../handler/DefaultPrivilegeHandler.java | 54 ++++++++----------- .../privilege/handler/PrivilegeHandler.java | 21 -------- 3 files changed, 24 insertions(+), 53 deletions(-) diff --git a/config/Privilege.xml b/config/Privilege.xml index 2c523ead3..1f7f06837 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -5,7 +5,7 @@ - + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index ba3f28c5f..b75969bf0 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -70,7 +70,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * configuration parameter to define automatic persisting on password change */ - private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; //$NON-NLS-1$ + private static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ /** * slf4j logger @@ -108,9 +108,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private boolean initialized; /** - * flag to define if a persist should be performed after a user changes their password + * flag to define if a persist should be performed after a user changes their own data */ - private boolean autoPersistOnPasswordChange; + private boolean autoPersistOnUserChangesData; @Override public RoleRep getRole(Certificate certificate, String roleName) { @@ -734,8 +734,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // check if certificate is for same user, in which case user is changing their own locale + if (certificate.getUsername().equals(username)) { + + // validate the certificate + isCertificateValid(certificate); + + } else { + + // otherwise validate the the certificate is for a privilege admin + assertIsPrivilegeAdmin(certificate); + } // get User User user = this.persistenceHandler.getUser(username); @@ -750,28 +759,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); - return newUser.asUserRep(); - } - - @Override - public UserRep setUserName(Certificate certificate, String username, String firstname, String lastname) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { - throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ + // perform automatic persisting, if enabled + if (this.autoPersistOnUserChangesData) { + this.persistenceHandler.persist(); } - // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, lastname, - user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - return newUser.asUserRep(); } @@ -815,7 +807,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.replaceUser(newUser); // perform automatic persisting, if enabled - if (this.autoPersistOnPasswordChange) { + if (this.autoPersistOnUserChangesData) { this.persistenceHandler.persist(); } @@ -1151,15 +1143,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; - String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE); + String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA); if (autoPersistS == null || autoPersistS.equals(Boolean.FALSE.toString())) { - this.autoPersistOnPasswordChange = false; + this.autoPersistOnUserChangesData = false; } else if (autoPersistS.equals(Boolean.TRUE.toString())) { - this.autoPersistOnPasswordChange = true; - logger.info("Enabling automatic persistence on password change."); //$NON-NLS-1$ + this.autoPersistOnUserChangesData = true; + logger.info("Enabling automatic persistence when user changes their data."); //$NON-NLS-1$ } else { String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE, autoPersistS, Boolean.FALSE); + msg = MessageFormat.format(msg, PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA, autoPersistS, Boolean.FALSE); logger.error(msg); } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 46c0e0e55..adaccd05e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -366,27 +366,6 @@ public interface PrivilegeHandler { public void setUserPassword(Certificate certificate, String username, byte[] password) throws AccessDeniedException, PrivilegeException; - /** - * Changes the name of the user. This changes the first name and the lastname. If either value is null, then that - * value is not changed - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * the username of the {@link User} for which the name is to be changed - * @param firstname - * the new first name - * @param lastname - * the new lastname - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public UserRep setUserName(Certificate certificate, String username, String firstname, String lastname) - throws AccessDeniedException, PrivilegeException; - /** * Changes the {@link UserState} of the user * From 638cebe01e8275c188b9e444576569e6e29ae73e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 12 Mar 2015 13:18:20 +0100 Subject: [PATCH 374/457] [New] Added new Tuple to collections --- .../ch/eitchnet/utils/collections/Tuple.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/collections/Tuple.java diff --git a/src/main/java/ch/eitchnet/utils/collections/Tuple.java b/src/main/java/ch/eitchnet/utils/collections/Tuple.java new file mode 100644 index 000000000..ec61f3338 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/collections/Tuple.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.collections; + +/** + * Simple wrapper for two elements + * + * @author Robert von Burg + */ +public class Tuple { + + private Object first; + private Object second; + + public Tuple() { + // + } + + public Tuple(T first, U second) { + this.first = first; + this.second = second; + } + + @SuppressWarnings("unchecked") + public T getFirst() { + return (T) this.first; + } + + public void setFirst(T first) { + this.first = first; + } + + @SuppressWarnings("unchecked") + public U getSecond() { + return (U) this.second; + } + + public void setSecond(U second) { + this.second = second; + } +} From fa40671b8cc8c1b4f0cefc877d2786edbb77cc88 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 12 Mar 2015 17:32:06 +0100 Subject: [PATCH 375/457] [Major] removed the need for a role PrivilegeAdmin - now use privileges - this solves the situation where a user might be allowed to add a user with a specific role, but not change a role and other such use cases Now there are privileges for every use case with two new PrivilegePolicies: - RoleAccessPrivilege - UserAccessPrivilege both of these policies expect a ch.eitchnet.utils.collections.Tuple as privilege value. The Tuple is a simple wrapper for two values: first and second. Each privilege has its own requirement on the actual values Special privilege actions: - PrivilegeAction -> privilege vlaue: String - Persist (required Allow) - Reload (required Allow) - GetPolicies (required Allow) Role specific privileges: - PrivilegeGetRole -> privilege value: Tuple(null, newRole) - PrivilegeAddRole -> privilege value: Tuple(null, newRole) - PrivilegeRemoveRole -> privilege value: Tuple(null, newRole) - PrivilegeModifyRole -> privilege value: Tuple(oldRole, newRole) Use specific privileges: - PrivilegeGetUser -> privilege value: Tuple(null, newUser) - PrivilegeAddUser -> privilege value: Tuple(null, newUser) - PrivilegeRemoveUser -> privilege value: Tuple(null, newUser) - PrivilegeModifyUser -> privilege value: Tuple(oldUser, newUser) - NOTE: without modifying roles, only fields and properties! - PrivilegeAddRoleToUser -> privilege value: Tuple(oldUser, roleName) - PrivilegeRemoveRoleFromUser -> privilege value: Tuple(oldUser, roleName) --- config/{Privilege.xml => PrivilegeConfig.xml} | 2 + config/PrivilegeModel.xml | 42 +- .../handler/DefaultPrivilegeHandler.java | 671 ++++++++++-------- .../privilege/handler/PrivilegeHandler.java | 118 ++- .../handler/XmlPersistenceHandler.java | 16 - .../privilege/model/PrivilegeContext.java | 28 +- .../privilege/model/SimpleRestrictable.java | 45 ++ .../privilege/policy/DefaultPrivilege.java | 47 +- .../policy/PrivilegePolicyHelper.java | 80 +++ .../privilege/policy/RoleAccessPrivilege.java | 110 +++ .../privilege/policy/UserAccessPrivilege.java | 139 ++++ .../xml/PrivilegeModelSaxReader.java | 17 + .../resources/PrivilegeMessages.properties | 8 +- .../privilege/test/PrivilegeTest.java | 12 +- 14 files changed, 951 insertions(+), 384 deletions(-) rename config/{Privilege.xml => PrivilegeConfig.xml} (79%) create mode 100644 src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java create mode 100644 src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java create mode 100644 src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java create mode 100644 src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java diff --git a/config/Privilege.xml b/config/PrivilegeConfig.xml similarity index 79% rename from config/Privilege.xml rename to config/PrivilegeConfig.xml index 1f7f06837..17fb4ad27 100644 --- a/config/Privilege.xml +++ b/config/PrivilegeConfig.xml @@ -25,6 +25,8 @@ + + \ No newline at end of file diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 7ae0fd879..104b28130 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -43,14 +43,52 @@ - + + + Persist + Reload + GetPolicies + + + + true + + + true + + + true + + + true + + + + true + + + true + + + true + + + true + + + true + + + true + + true - + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index b75969bf0..89fc92884 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -39,12 +39,14 @@ import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.privilege.model.SimpleRestrictable; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.collections.Tuple; import ch.eitchnet.utils.helper.StringHelper; /** @@ -67,10 +69,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class DefaultPrivilegeHandler implements PrivilegeHandler { - /** - * configuration parameter to define automatic persisting on password change - */ - private static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ + /// /** * slf4j logger @@ -115,32 +114,40 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public RoleRep getRole(Certificate certificate, String roleName) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_ROLE); Role role = this.persistenceHandler.getRole(roleName); if (role == null) return null; + + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role))); + return role.asRoleRep(); } @Override public UserRep getUser(Certificate certificate, String username) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); User user = this.persistenceHandler.getUser(username); if (user == null) return null; + + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); return user.asUserRep(); } @Override public Map getPolicyDefs(Certificate certificate) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_GET_POLICIES)); Map policyDef = new HashMap<>(this.policyMap.size()); for (Entry> entry : this.policyMap.entrySet()) { @@ -149,33 +156,60 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return policyDef; } - @Override - public List getUsers(Certificate certificate) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - Stream usersStream = this.persistenceHandler.getAllUsers().stream(); - List users = usersStream.map(u -> u.asUserRep()).collect(Collectors.toList()); - return users; - } - @Override public List getRoles(Certificate certificate) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_ROLE); Stream rolesStream = this.persistenceHandler.getAllRoles().stream(); + + // validate access to each role + // TODO throwing and catching exception ain't cool + rolesStream.filter(role -> { + try { + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role))); + return true; + } catch (AccessDeniedException e) { + return false; + } + }); + List roles = rolesStream.map(r -> r.asRoleRep()).collect(Collectors.toList()); return roles; } + @Override + public List getUsers(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); + + Stream usersStream = this.persistenceHandler.getAllUsers().stream(); + + // validate access to each user + // TODO throwing and catching exception ain't cool + usersStream.filter(user -> { + try { + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); + return true; + } catch (AccessDeniedException e) { + return false; + } + }); + + List users = usersStream.map(u -> u.asUserRep()).collect(Collectors.toList()); + return users; + } + @Override public List queryUsers(Certificate certificate, UserRep selectorRep) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); String selUserId = selectorRep.getUserId(); String selUsername = selectorRep.getUsername(); @@ -190,6 +224,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { List allUsers = this.persistenceHandler.getAllUsers(); for (User user : allUsers) { + // TODO throwing and catching exception ain't cool + try { + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); + } catch (AccessDeniedException e) { + continue; + } + // selections boolean userIdSelected; boolean usernameSelected; @@ -319,8 +360,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) { try { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_ADD_USER); // make sure userId is not set if (StringHelper.isNotEmpty(userRep.getUserId())) { @@ -353,12 +395,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User user = createUser(userRep, passwordHash); + User newUser = createUser(userRep, passwordHash); + + // validate this user may create such a user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_USER, new Tuple(null, newUser))); // delegate to persistence handler - this.persistenceHandler.addUser(user); + this.persistenceHandler.addUser(newUser); - return user.asUserRep(); + return newUser.asUserRep(); } finally { clearPassword(password); @@ -369,8 +414,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) { try { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); // first validate user userRep.validate(); @@ -378,16 +424,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateRolesExist(userRep); // validate user exists - User user = this.persistenceHandler.getUser(userRep.getUsername()); - if (user == null) { + User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); + if (existingUser == null) { String msg = "User {0} can not be replaced as it does not exist!"; throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); } // validate same userId - if (!user.getUserId().equals(userRep.getUserId())) { + if (!existingUser.getUserId().equals(userRep.getUserId())) { String msg = "UserId of existing user {0} does not match userRep {1}"; - msg = MessageFormat.format(msg, user.getUserId(), userRep.getUserId()); + msg = MessageFormat.format(msg, existingUser.getUserId(), userRep.getUserId()); throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); } @@ -401,12 +447,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { passwordHash = this.encryptionHandler.convertToHash(password); } - user = createUser(userRep, passwordHash); + User newUser = createUser(userRep, passwordHash); + + // validate this user may modify this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); // delegate to persistence handler - this.persistenceHandler.replaceUser(user); + this.persistenceHandler.replaceUser(newUser); - return user.asUserRep(); + return newUser.asUserRep(); } finally { clearPassword(password); @@ -435,12 +484,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); // get existing user - User user = this.persistenceHandler.getUser(userRep.getUsername()); - if (user == null) { + User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); + if (existingUser == null) { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", userRep.getUsername())); //$NON-NLS-1$ } @@ -452,15 +502,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { "All updateable fields are empty for update of user {0}", userRep.getUsername())); //$NON-NLS-1$ } - String userId = user.getUserId(); - String username = user.getUsername(); - String password = user.getPassword(); - String firstname = user.getFirstname(); - String lastname = user.getLastname(); - UserState userState = user.getUserState(); - Set roles = user.getRoles(); - Locale locale = user.getLocale(); - Map propertyMap = user.getProperties(); + String userId = existingUser.getUserId(); + String username = existingUser.getUsername(); + String password = existingUser.getPassword(); + String firstname = existingUser.getFirstname(); + String lastname = existingUser.getLastname(); + UserState userState = existingUser.getUserState(); + Set roles = existingUser.getRoles(); + Locale locale = existingUser.getLocale(); + Map propertyMap = existingUser.getProperties(); // get updated fields if (StringHelper.isNotEmpty(userRep.getFirstname())) @@ -473,134 +523,64 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { propertyMap = userRep.getPropertyMap(); // create new user - user = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); + User newUser = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); + + // validate this user may modify this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); // delegate to persistence handler - this.persistenceHandler.replaceUser(user); + this.persistenceHandler.replaceUser(newUser); - return user.asUserRep(); + return newUser.asUserRep(); } @Override - public RoleRep addRole(Certificate certificate, RoleRep roleRep) { + public UserRep removeUser(Certificate certificate, String username) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_USER); - // first validate role - roleRep.validate(); - - // validate role does not exist - if (this.persistenceHandler.getRole(roleRep.getName()) != null) { - String msg = MessageFormat.format("Can not add role {0} as it already exists!", roleRep.getName()); - throw new PrivilegeException(msg); + // validate user exists + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { + String msg = "Can not remove User {0} because user does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, username)); } - // create new role from RoleRep - Role role = new Role(roleRep); + // validate this user may remove this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_USER, new Tuple(null, existingUser))); - // validate policy if not null - validatePolicies(role); + // delegate user removal to persistence handler + this.persistenceHandler.removeUser(username); - // delegate to persistence handler - this.persistenceHandler.addRole(role); - - return role.asRoleRep(); - } - - @Override - public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // first validate role - roleRep.validate(); - - // validate role does exist - if (this.persistenceHandler.getRole(roleRep.getName()) == null) { - String msg = MessageFormat.format("Can not replace role {0} as it does not exist!", roleRep.getName()); - throw new PrivilegeException(msg); - } - - // create new role from RoleRep - Role role = new Role(roleRep); - - // validate policy if not null - validatePolicies(role); - - // delegate to persistence handler - this.persistenceHandler.replaceRole(role); - - return role.asRoleRep(); - } - - @Override - public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // validate PrivilegeRep - privilegeRep.validate(); - - // get role - Role role = this.persistenceHandler.getRole(roleName); - if (role == null) { - String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ - throw new PrivilegeException(msg); - } - - // validate that policy exists if needed - String policy = privilegeRep.getPolicy(); - if (policy != null && !this.policyMap.containsKey(policy)) { - String msg = "Policy {0} for Privilege {1} does not exist"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, policy, privilegeRep.getName()); - throw new PrivilegeException(msg); - } - - // create new role with the additional privilege - IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); - - // copy existing privileges - Set existingPrivilegeNames = role.getPrivilegeNames(); - Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); - for (String name : existingPrivilegeNames) { - IPrivilege privilege = role.getPrivilege(name); - privilegeMap.put(name, privilege); - } - - // add new one - privilegeMap.put(newPrivilege.getName(), newPrivilege); - - Role newRole = new Role(role.getName(), privilegeMap); - - // delegate role replacement to persistence handler - this.persistenceHandler.replaceRole(newRole); - - return newRole.asRoleRep(); + return existingUser.asUserRep(); } @Override public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE_TO_USER); // get user - User user = this.persistenceHandler.getUser(username); - if (user == null) { + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } + // validate that this user may add this role to this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_ROLE_TO_USER, new Tuple(existingUser, roleName))); + // check that user not already has role - Set currentRoles = user.getRoles(); + Set currentRoles = existingUser.getRoles(); if (currentRoles.contains(roleName)) { String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); //$NON-NLS-1$ throw new PrivilegeException(msg); } - // validate that role exists + // validate that the role exists if (this.persistenceHandler.getRole(roleName) == null) { String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ throw new PrivilegeException(msg); @@ -610,8 +590,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet<>(currentRoles); newRoles.add(roleName); - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles, + existingUser.getLocale(), existingUser.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -619,94 +600,35 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return newUser.asUserRep(); } - @Override - public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // get role - Role role = this.persistenceHandler.getRole(roleName); - if (role == null) { - throw new PrivilegeException(MessageFormat.format("Role {0} does not exist!", roleName)); //$NON-NLS-1$ - } - - // ignore if role does not have privilege - if (!role.hasPrivilege(privilegeName)) { - String msg = MessageFormat.format("Role {0} does not have Privilege {1}", roleName, privilegeName); //$NON-NLS-1$ - throw new PrivilegeException(msg); - } - - // create new set of privileges with out the to removed privilege - Set privilegeNames = role.getPrivilegeNames(); - Map newPrivileges = new HashMap(privilegeNames.size() - 1); - for (String name : privilegeNames) { - IPrivilege privilege = role.getPrivilege(name); - if (!privilege.getName().equals(privilegeName)) - newPrivileges.put(privilege.getName(), privilege); - } - - // create new role - Role newRole = new Role(role.getName(), newPrivileges); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceRole(newRole); - - return newRole.asRoleRep(); - } - - @Override - public RoleRep removeRole(Certificate certificate, String roleName) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // validate no user is using this role - Set roles = new HashSet<>(Arrays.asList(roleName)); - UserRep selector = new UserRep(null, null, null, null, null, roles, null, null); - List usersWithRole = queryUsers(certificate, selector); - if (!usersWithRole.isEmpty()) { - String usersS = usersWithRole.stream().map(UserRep::getUsername).collect(Collectors.joining(", ")); - String msg = "The role {0} can not be removed as the following {1} user have the role assigned: {2}"; - msg = MessageFormat.format(msg, roleName, usersWithRole.size(), usersS); - throw new PrivilegeException(msg); - } - - // delegate role removal to persistence handler - Role removedRole = this.persistenceHandler.removeRole(roleName); - if (removedRole == null) { - String msg = "Can not remove Role {0} because role does not exist!"; - throw new PrivilegeException(MessageFormat.format(msg, roleName)); - } - - // return role rep if it was removed - return removedRole.asRoleRep(); - } - @Override public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE_FROM_USER); // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } + // validate that this user may remove this role from this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE_FROM_USER, new Tuple(existingUser, roleName))); + // ignore if user does not have role - Set currentRoles = user.getRoles(); + Set currentRoles = existingUser.getRoles(); if (!currentRoles.contains(roleName)) { - String msg = MessageFormat.format("User {0} does not have role {1}", user.getUsername(), roleName); //$NON-NLS-1$ + String msg = MessageFormat.format("User {0} does not have role {1}", existingUser.getUsername(), roleName); //$NON-NLS-1$ throw new PrivilegeException(msg); } // create new user Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles, + existingUser.getLocale(), existingUser.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -714,23 +636,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return newUser.asUserRep(); } - @Override - public UserRep removeUser(Certificate certificate, String username) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // delegate user removal to persistence handler - User removedUser = this.persistenceHandler.removeUser(username); - if (removedUser == null) { - String msg = "Can not remove User {0} because user does not exist!"; - throw new PrivilegeException(MessageFormat.format(msg, username)); - } - - // return user rep if it was removed - return removedUser.asUserRep(); - } - @Override public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { @@ -742,19 +647,27 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } else { - // otherwise validate the the certificate is for a privilege admin - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); } // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getLastname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getRoles(), locale, existingUser.getProperties()); + + // if the user is not setting their own locale, then make sure this user may set this user's locale + if (!certificate.getUsername().equals(username)) { + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + } // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -779,13 +692,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } else { - // otherwise validate the the certificate is for a privilege admin - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); } // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } @@ -800,8 +714,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), - user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), passwordHash, + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties()); + + // if the user is not setting their own password, then make sure this user may set this user's password + if (!certificate.getUsername().equals(username)) { + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + } // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -819,18 +740,23 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public UserRep setUserState(Certificate certificate, String username, UserState state) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getLastname(), state, user.getRoles(), user.getLocale(), user.getProperties()); + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), + existingUser.getFirstname(), existingUser.getLastname(), state, existingUser.getRoles(), + existingUser.getLocale(), existingUser.getProperties()); + + // validate that this user may modify this user's state + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -838,6 +764,194 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return newUser.asUserRep(); } + @Override + public RoleRep addRole(Certificate certificate, RoleRep roleRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE); + + // first validate role + roleRep.validate(); + + // validate role does not exist + if (this.persistenceHandler.getRole(roleRep.getName()) != null) { + String msg = MessageFormat.format("Can not add role {0} as it already exists!", roleRep.getName()); + throw new PrivilegeException(msg); + } + + // create new role from RoleRep + Role newRole = new Role(roleRep); + + // validate that this user may add this new role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_ROLE, new Tuple(null, newRole))); + + // validate policy if not null + validatePolicies(newRole); + + // delegate to persistence handler + this.persistenceHandler.addRole(newRole); + + return newRole.asRoleRep(); + } + + @Override + public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); + + // first validate role + roleRep.validate(); + + // validate role does exist + Role existingRole = this.persistenceHandler.getRole(roleRep.getName()); + if (existingRole == null) { + String msg = MessageFormat.format("Can not replace role {0} as it does not exist!", roleRep.getName()); + throw new PrivilegeException(msg); + } + + // create new role from RoleRep + Role newRole = new Role(roleRep); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); + + // validate policy if not null + validatePolicies(newRole); + + // delegate to persistence handler + this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); + } + + @Override + public RoleRep removeRole(Certificate certificate, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE); + + // validate no user is using this role + Set roles = new HashSet<>(Arrays.asList(roleName)); + UserRep selector = new UserRep(null, null, null, null, null, roles, null, null); + List usersWithRole = queryUsers(certificate, selector); + if (!usersWithRole.isEmpty()) { + String usersS = usersWithRole.stream().map(UserRep::getUsername).collect(Collectors.joining(", ")); + String msg = "The role {0} can not be removed as the following {1} user have the role assigned: {2}"; + msg = MessageFormat.format(msg, roleName, usersWithRole.size(), usersS); + throw new PrivilegeException(msg); + } + + // validate role exists + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + String msg = "Can not remove Role {0} because role does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, roleName)); + } + + // validate that this user may remove this role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE, new Tuple(null, existingRole))); + + // delegate role removal to persistence handler + this.persistenceHandler.removeRole(roleName); + + return existingRole.asRoleRep(); + } + + @Override + public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); + + // validate PrivilegeRep + privilegeRep.validate(); + + // get role + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // validate that policy exists if needed + String policy = privilegeRep.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + String msg = "Policy {0} for Privilege {1} does not exist"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policy, privilegeRep.getName()); + throw new PrivilegeException(msg); + } + + // create new role with the additional privilege + IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); + + // copy existing privileges + Set existingPrivilegeNames = existingRole.getPrivilegeNames(); + Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); + for (String name : existingPrivilegeNames) { + IPrivilege privilege = existingRole.getPrivilege(name); + privilegeMap.put(name, privilege); + } + + // add new one + privilegeMap.put(newPrivilege.getName(), newPrivilege); + + // create new role + Role newRole = new Role(existingRole.getName(), privilegeMap); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); + + // delegate role replacement to persistence handler + this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); + } + + @Override + public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); + + // get role + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + throw new PrivilegeException(MessageFormat.format("Role {0} does not exist!", roleName)); //$NON-NLS-1$ + } + + // ignore if role does not have privilege + if (!existingRole.hasPrivilege(privilegeName)) { + String msg = MessageFormat.format("Role {0} does not have Privilege {1}", roleName, privilegeName); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // create new set of privileges with out the to removed privilege + Set privilegeNames = existingRole.getPrivilegeNames(); + Map newPrivileges = new HashMap(privilegeNames.size() - 1); + for (String name : privilegeNames) { + IPrivilege privilege = existingRole.getPrivilege(name); + if (!privilege.getName().equals(privilegeName)) + newPrivileges.put(privilege.getName(), privilege); + } + + // create new role + Role newRole = new Role(existingRole.getName(), newPrivileges); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); + } + @Override public Certificate authenticate(String username, byte[] password) { @@ -1068,28 +1182,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return this.privilegeContextMap.get(certificate.getSessionId()); } - @Override - public void assertIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { - - // validate certificate - isCertificateValid(certificate); - - // get user object - User user = this.persistenceHandler.getUser(certificate.getUsername()); - if (user == null) { - String msg = "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: {0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, certificate); - throw new PrivilegeException(msg); - } - - // validate user has PrivilegeAdmin role - if (!user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { - String msg = "User does not have {0} role! Certificate: {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE, certificate); - throw new AccessDeniedException(msg); - } - } - /** * This simple implementation validates that the password is not null, and that the password string is not empty * @@ -1111,11 +1203,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public boolean persist(Certificate certificate) { // validate who is doing this - assertIsPrivilegeAdmin(certificate); + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST)); return this.persistenceHandler.persist(); } + @Override + public boolean reload(Certificate certificate) { + + // validate who is doing this + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_RELOAD)); + + return this.persistenceHandler.reload(); + } + /** * Initializes the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this * {@link PrivilegeHandler} might need. This method may only be called once and this must be enforced by the diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index adaccd05e..9aea7c272 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -41,10 +41,85 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public interface PrivilegeHandler { + /// + /** - * PRIVILEGE_ADMIN_ROLE = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects + * Privilege "PrivilegeAction" which is used for privileges which are not further categorized e.g. s + * {@link #PRIVILEGE_ACTION_PERSIST} and {@link #PRIVILEGE_ACTION_GET_POLICIES} */ - public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; //$NON-NLS-1$ + public static final String PRIVILEGE_ACTION = "PrivilegeAction"; + + /** + * For Privilege "PrivilegeAction" value required to be able to persist changes if not exempted by auto persist or + * allAllowed + */ + public static final String PRIVILEGE_ACTION_PERSIST = "Persist"; + /** + * For Privilege "PrivilegeAction" value required to be able to reload changes if not exempted by + * allAllowed + */ + public static final String PRIVILEGE_ACTION_RELOAD = "Reload"; + /** + * For Privilege "PrivilegeAction" value required to get currently configured policies if not + * allAllowed + */ + public static final String PRIVILEGE_ACTION_GET_POLICIES = "GetPolicies"; + + /// + + /** + * Privilege "PrivilegeGetRole" which is used to validate that a user can get a specific role + */ + public static final String PRIVILEGE_GET_ROLE = "PrivilegeGetRole"; + /** + * Privilege "PrivilegeAddRole" which is used to validate that a user can add a specific role + */ + public static final String PRIVILEGE_ADD_ROLE = "PrivilegeAddRole"; + /** + * Privilege "PrivilegeRemoveRole" which is used to validate that a user can remove a specific role + */ + public static final String PRIVILEGE_REMOVE_ROLE = "PrivilegeRemoveRole"; + /** + * Privilege "PrivilegeModifyRole" which is used to validate that a user can modify a specific role. Note: + * This includes modifying of the privileges on the role + */ + public static final String PRIVILEGE_MODIFY_ROLE = "PrivilegeModifyRole"; + + /// + + /** + * Privilege "PrivilegeGetUser" which is used to validate that a user can get a specific user + */ + public static final String PRIVILEGE_GET_USER = "PrivilegeGetUser"; + /** + * Privilege "PrivilegeAddUser" which is used to validate that a user can add a specific user + */ + public static final String PRIVILEGE_ADD_USER = "PrivilegeAddUser"; + /** + * Privilege "PrivilegeRemoveUser" which is used to validate that a user can remove a specific user + */ + public static final String PRIVILEGE_REMOVE_USER = "PrivilegeRemoveUser"; + /** + * Privilege "PrivilegeModifyUser" which is used to validate that a user can modify a specific user + */ + public static final String PRIVILEGE_MODIFY_USER = "PrivilegeModifyUser"; + /** + * Privilege "PrivilegeAddRoleToUser" which is used to validate that a user can add a specific role to a specific + * user + */ + public static final String PRIVILEGE_ADD_ROLE_TO_USER = "PrivilegeAddRoleToUser"; + /** + * Privilege "PrivilegeRemoveRoleFromUser" which is used to validate that a user can remove a specific role from a + * specific user + */ + public static final String PRIVILEGE_REMOVE_ROLE_FROM_USER = "PrivilegeRemoveRoleFromUser"; + + /// + + /** + * configuration parameter to define automatic persisting on password change + */ + public static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ /** * Returns a {@link UserRep} for the given username @@ -469,28 +544,6 @@ public interface PrivilegeHandler { */ public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException; - /** - *

      - * Validates if this {@link Certificate} is for a {@link ch.eitchnet.privilege.model.internal.User} with - * {@link Role} with name {@link PrivilegeHandler#PRIVILEGE_ADMIN_ROLE} - *

      - * - *

      - * In other words, this method checks if the given certificate is for a user who has the rights to change objects - *

      - * - *

      - * If the user is not the administrator, then a {@link ch.eitchnet.privilege.base.PrivilegeException} is thrown - *

      - * - * @param certificate - * the {@link Certificate} for which the role should be validated against - * - * @throws AccessDeniedException - * if the user does not not have admin privileges - */ - public void assertIsPrivilegeAdmin(Certificate certificate) throws AccessDeniedException; - /** * Validate that the given password meets certain requirements. What these requirements are is a decision made by * the concrete implementation @@ -503,6 +556,23 @@ public interface PrivilegeHandler { */ public void validatePassword(byte[] password) throws PrivilegeException; + /** + *

      + * Informs this {@link PersistenceHandler} to reload the data from the backend + *

      + * + * Note: It depends on the underlying {@link PersistenceHandler} implementation if data really is read + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return true if the reload was successful, false if something went wrong + * + * @throws AccessDeniedException + * if the users of the given certificate does not have the privilege to perform this action + */ + public boolean reload(Certificate certificate); + /** * Persists any changes to the privilege data model. Changes are thus not persisted immediately, but must be * actively performed diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 292743a7e..0d31c1048 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -224,22 +224,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } - // validate we have a user with PrivilegeAdmin access - boolean privilegeAdminExists = false; - for (String username : this.userMap.keySet()) { - User user = this.userMap.get(username); - if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { - privilegeAdminExists = true; - break; - } - } - - if (!privilegeAdminExists) { - String msg = "No User with role ''{0}'' exists. Privilege modifications will not be possible!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - logger.warn(msg); - } - return true; } diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 09a282567..32eef9e6a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -16,10 +16,8 @@ package ch.eitchnet.privilege.model; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; @@ -73,16 +71,26 @@ public class PrivilegeContext { return this.privileges.keySet(); } + public void assertHasPrivilege(String privilegeName) { + if (!this.privileges.containsKey(privilegeName)) { + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.user"), //$NON-NLS-1$ + userRep.getUsername(), privilegeName); + throw new AccessDeniedException(msg); + } + } + public IPrivilege getPrivilege(String privilegeName) { + assertHasPrivilege(privilegeName); return this.privileges.get(privilegeName); } - public List getFlatAllowList() { - List allowList = new ArrayList<>(); - for (IPrivilege privilege : this.privileges.values()) { - allowList.addAll(privilege.getAllowList()); + public PrivilegePolicy getPolicy(String policyName) { + PrivilegePolicy policy = this.policies.get(policyName); + if (policy == null) { + String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format(msg, policyName)); } - return allowList; + return policy; } // @@ -115,11 +123,7 @@ public class PrivilegeContext { // get the policy referenced by the restrictable String policyName = privilege.getPolicy(); - PrivilegePolicy policy = this.policies.get(policyName); - if (policy == null) { - String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; //$NON-NLS-1$ - throw new PrivilegeException(MessageFormat.format(msg, policyName)); - } + PrivilegePolicy policy = getPolicy(policyName); // delegate to the policy policy.validateAction(this, privilege, restrictable); diff --git a/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java b/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java new file mode 100644 index 000000000..341bbb37c --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.model; + +import ch.eitchnet.utils.dbc.DBC; + +public class SimpleRestrictable implements Restrictable { + + private final String name; + private final Object value; + + /** + * @param name + * @param value + */ + public SimpleRestrictable(String name, Object value) { + DBC.PRE.assertNotEmpty("name must not be emepty", name); + DBC.PRE.assertNotNull("value must not be null", value); + this.name = name; + this.value = value; + } + + @Override + public String getPrivilegeName() { + return this.name; + } + + @Override + public Object getPrivilegeValue() { + return this.value; + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 32375ee3b..05494e5c5 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -17,14 +17,12 @@ package ch.eitchnet.privilege.policy; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.i18n.PrivilegeMessages; import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.utils.helper.StringHelper; /** * This is a simple implementation of {@link PrivilegePolicy} which uses the {@link Restrictable#getPrivilegeName()} to @@ -41,29 +39,7 @@ public class DefaultPrivilege implements PrivilegePolicy { */ @Override public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { - - if (privilege == null) - throw new PrivilegeException(PrivilegeMessages.getString("Privilege.privilegeNull")); //$NON-NLS-1$ - if (restrictable == null) - throw new PrivilegeException(PrivilegeMessages.getString("Privilege.restrictableNull")); //$NON-NLS-1$ - - // get the PrivilegeName - String privilegeName = restrictable.getPrivilegeName(); - if (StringHelper.isEmpty(privilegeName)) { - String msg = PrivilegeMessages.getString("Privilege.privilegeNameEmpty"); //$NON-NLS-1$ - throw new PrivilegeException(MessageFormat.format(msg, restrictable)); - } - - // we want the privileges names to match - if (!privilege.getName().equals(privilegeName)) { - throw new PrivilegeException(MessageFormat.format( - PrivilegeMessages.getString("Privilege.illegalArgument.privilegeNameMismatch"), //$NON-NLS-1$ - privilege.getName(), privilegeName)); - } - - // if everything is allowed, then no need to carry on - if (privilege.isAllAllowed()) - return; + PrivilegePolicyHelper.preValidate(privilege, restrictable); // get the value on which the action is to be performed Object object = restrictable.getPrivilegeValue(); @@ -76,23 +52,12 @@ public class DefaultPrivilege implements PrivilegePolicy { throw new PrivilegeException(msg); } - String privilegeValue = (String) object; - - // first check values not allowed - if (privilege.isDenied(privilegeValue)) { - // then throw access denied - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ - ctx.getUsername(), privilegeName, restrictable.getClass().getName()); - throw new AccessDeniedException(msg); - } - - // now check values allowed - if (privilege.isAllowed(privilegeValue)) + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) return; - // default is not allowed - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ - ctx.getUsername(), privilegeName, restrictable.getClass().getName()); - throw new AccessDeniedException(msg); + String privilegeValue = (String) object; + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java new file mode 100644 index 000000000..b06456453 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java @@ -0,0 +1,80 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public class PrivilegePolicyHelper { + + public static String preValidate(IPrivilege privilege, Restrictable restrictable) { + if (privilege == null) + throw new PrivilegeException(PrivilegeMessages.getString("Privilege.privilegeNull")); //$NON-NLS-1$ + if (restrictable == null) + throw new PrivilegeException(PrivilegeMessages.getString("Privilege.restrictableNull")); //$NON-NLS-1$ + + // get the PrivilegeName + String privilegeName = restrictable.getPrivilegeName(); + if (StringHelper.isEmpty(privilegeName)) { + String msg = PrivilegeMessages.getString("Privilege.privilegeNameEmpty"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format(msg, restrictable)); + } + + // we want the privileges names to match + if (!privilege.getName().equals(privilegeName)) { + throw new PrivilegeException(MessageFormat.format( + PrivilegeMessages.getString("Privilege.illegalArgument.privilegeNameMismatch"), //$NON-NLS-1$ + privilege.getName(), privilegeName)); + } + + return privilegeName; + } + + /** + * @param privilege + * @param privilegeValue + */ + public static void checkByAllowDenyValues(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + String privilegeValue) { + + // first check values not allowed + if (privilege.isDenied(privilegeValue)) { + // then throw access denied + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ + ctx.getUsername(), privilege.getName(), restrictable.getClass().getName()); + throw new AccessDeniedException(msg); + } + + // now check values allowed + if (privilege.isAllowed(privilegeValue)) + return; + + // default is not allowed + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ + ctx.getUsername(), privilege.getName(), restrictable.getClass().getName()); + throw new AccessDeniedException(msg); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java new file mode 100644 index 000000000..a2bf2fa1d --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java @@ -0,0 +1,110 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.collections.Tuple; +import ch.eitchnet.utils.dbc.DBC; + +/** + * TODO + * + * @author Robert von Burg + */ +public class RoleAccessPrivilege implements PrivilegePolicy { + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + String privilegeName = PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Tuple)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nontuple"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + + Tuple tuple = (Tuple) object; + + // get role name as privilege value + Role oldRole = tuple.getFirst(); + Role newRole = tuple.getSecond(); + + switch (privilegeName) { + case PrivilegeHandler.PRIVILEGE_GET_ROLE: { + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldRole); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole); + + String privilegeValue = newRole.getName(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + + case PrivilegeHandler.PRIVILEGE_ADD_ROLE: { + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldRole); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole); + + String privilegeValue = newRole.getName(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_MODIFY_ROLE: { + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldRole); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole); + + String privilegeValue = newRole.getName(); + DBC.INTERIM.assertEquals("oldRole and newRole names must be the same", oldRole.getName(), privilegeValue); + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_REMOVE_ROLE: { + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldRole); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole); + + String privilegeValue = newRole.getName(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + + default: + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.roleAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName); + throw new PrivilegeException(msg); + } + } +} diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java new file mode 100644 index 000000000..ad35d4115 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java @@ -0,0 +1,139 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.collections.Tuple; +import ch.eitchnet.utils.dbc.DBC; + +/** + * TODO + * + * @author Robert von Burg + */ +public class UserAccessPrivilege implements PrivilegePolicy { + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + String privilegeName = PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Tuple)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nontuple"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + + Tuple tuple = (Tuple) object; + + switch (privilegeName) { + case PrivilegeHandler.PRIVILEGE_GET_USER: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_ADD_USER: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_REMOVE_USER: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_MODIFY_USER: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + DBC.INTERIM.assertEquals("oldUser and newUser names must be the same", oldUser.getUsername(), + privilegeValue); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER: { + User user = tuple.getFirst(); + String roleName = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", user); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", roleName); + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, roleName); + + break; + } + case PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER: { + User user = tuple.getFirst(); + String roleName = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", user); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", roleName); + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, roleName); + + break; + } + + default: + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.userAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName); + throw new PrivilegeException(msg); + } + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index 03f43bf91..03d5779c4 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -169,6 +169,11 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } else if (qName.equals(XmlConstants.XML_PRIVILEGE)) { this.privilegeName = attributes.getValue(XmlConstants.XML_ATTR_NAME); this.privilegePolicy = attributes.getValue(XmlConstants.XML_ATTR_POLICY); + } else if (qName.equals(XmlConstants.XML_ALLOW) || qName.equals(XmlConstants.XML_DENY) + || qName.equals(XmlConstants.XML_ALL_ALLOWED)) { + // no-op + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); } } @@ -267,12 +272,20 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.locale = new Locale(this.text.toString().trim()); } else if (qName.equals(XmlConstants.XML_ROLE)) { this.userRoles.add(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_ROLES)) { + // NO-OP + } else if (qName.equals(XmlConstants.XML_PARAMETER)) { + // NO-OP + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { + // NO-OP } else if (qName.equals(XmlConstants.XML_USER)) { User user = new User(this.userId, this.username, this.password, this.firstName, this.lastname, this.userState, this.userRoles, this.locale, this.parameters); logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$ getUsers().add(user); + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); } } @@ -296,6 +309,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); this.parameterMap.put(key, value); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + // NO-OP + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); } } diff --git a/src/main/resources/PrivilegeMessages.properties b/src/main/resources/PrivilegeMessages.properties index 62f19745c..89127fb00 100644 --- a/src/main/resources/PrivilegeMessages.properties +++ b/src/main/resources/PrivilegeMessages.properties @@ -1,6 +1,12 @@ Privilege.accessdenied.noprivilege=User {0} does not have Privilege {1} needed for Restrictable {2} -Privilege.illegalArgument.nonstring=\ {1} has returned a non-string privilege value\! +Privilege.illegalArgument.nonstring=\ {0} has returned a non-string privilege value\! +Privilege.illegalArgument.nonrole=\ {0} did not return a Role privilege value\! +Privilege.illegalArgument.nonuser=\ {0} did not return a User privilege value\! Privilege.illegalArgument.privilegeNameMismatch=The passed privilege has the name {0} but the restrictable is referencing privilege {1} Privilege.privilegeNameEmpty=The PrivilegeName for the Restrictable is null or empty: {0} Privilege.privilegeNull=Privilege may not be null\! Privilege.restrictableNull=Restrictable may not be null\! +Privilege.noprivilege=No Privilege exists with name {0} +Privilege.noprivilege.user=User {0} does not have a Privilege {0} +Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled RoleAccessPrivilege {0} +Privilege.userAccessPrivilege.unknownPrivilege=Unhandled UserAccessPrivilege {0} diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 0671c4591..310c88ad4 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -40,6 +41,7 @@ import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -63,6 +65,7 @@ import ch.eitchnet.utils.helper.FileHelper; @SuppressWarnings("nls") public class PrivilegeTest { + private static final String ROLE_PRIVILEGE_ADMIN = "PrivilegeAdmin"; private static final String ADMIN = "admin"; private static final byte[] PASS_ADMIN = "admin".getBytes(); private static final String BOB = "bob"; @@ -146,7 +149,7 @@ public class PrivilegeTest { String pwd = System.getProperty("user.dir"); - File privilegeConfigFile = new File(pwd + "/config/Privilege.xml"); + File privilegeConfigFile = new File(pwd + "/config/PrivilegeConfig.xml"); // initialize privilege privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); @@ -579,8 +582,8 @@ public class PrivilegeTest { // testAddAdminRoleToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_PRIVILEGE_ADMIN); + logger.info("Added " + ROLE_PRIVILEGE_ADMIN + " to " + ADMIN); privilegeHandler.persist(certificate); } finally { logout(); @@ -602,7 +605,8 @@ public class PrivilegeTest { privilegeHandler.addUser(certificate, userRep, null); fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { - String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate; + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.user"), //$NON-NLS-1$ + BOB, PrivilegeHandler.PRIVILEGE_ADD_USER); assertEquals(msg, e.getMessage()); } finally { logout(); From 7ff8ba67793b35480b01134003aa5607244382a9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 12 Mar 2015 18:30:38 +0100 Subject: [PATCH 376/457] [Bugfix] Fixed bad parsing of Allow on multiple privileges per Role --- .../xml/PrivilegeModelSaxReader.java | 6 +++++ .../ch/eitchnet/privilege/test/XmlTest.java | 23 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index 03d5779c4..be05f0b62 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -198,6 +198,12 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.denyList, this.allowList); this.privileges.put(this.privilegeName, privilege); + this.privilegeName = null; + this.privilegePolicy = null; + this.allAllowed = false; + this.denyList = new HashSet(); + this.allowList = new HashSet(); + } else if (qName.equals(XmlConstants.XML_ROLE)) { Role role = new Role(this.roleName, this.privileges); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 6d46fc25d..6526e913a 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -40,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.DefaultEncryptionHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.handler.XmlPersistenceHandler; import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.UserState; @@ -107,7 +108,7 @@ public class XmlTest { PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); PrivilegeConfigSaxReader saxReader = new PrivilegeConfigSaxReader(containerModel); - File xmlFile = new File("config/Privilege.xml"); + File xmlFile = new File("config/PrivilegeConfig.xml"); XmlHelper.parseDocument(xmlFile, saxReader); logger.info(containerModel.toString()); @@ -120,7 +121,7 @@ public class XmlTest { assertNotNull(containerModel.getPersistenceHandlerParameterMap()); assertEquals(1, containerModel.getParameterMap().size()); - assertEquals(1, containerModel.getPolicies().size()); + assertEquals(3, containerModel.getPolicies().size()); assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); @@ -211,7 +212,23 @@ public class XmlTest { // PrivilegeAdmin Role privilegeAdmin = findRole("PrivilegeAdmin", roles); assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); - assertTrue(privilegeAdmin.getPrivilegeNames().isEmpty()); + assertEquals(11, privilegeAdmin.getPrivilegeNames().size()); + IPrivilege privilegeAction = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ACTION); + assertFalse(privilegeAction.isAllAllowed()); + assertEquals(3, privilegeAction.getAllowList().size()); + assertEquals(0, privilegeAction.getDenyList().size()); + assertEquals("DefaultPrivilege", privilegeAction.getPolicy()); + + IPrivilege privilegeAddRole = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ADD_ROLE); + assertTrue(privilegeAddRole.isAllAllowed()); + assertEquals(0, privilegeAddRole.getAllowList().size()); + assertEquals(0, privilegeAddRole.getDenyList().size()); + + IPrivilege privilegeRemRoleFromUser = privilegeAdmin + .getPrivilege(PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER); + assertTrue(privilegeRemRoleFromUser.isAllAllowed()); + assertEquals(0, privilegeRemRoleFromUser.getAllowList().size()); + assertEquals(0, privilegeRemRoleFromUser.getDenyList().size()); // AppUser Role appUser = findRole("AppUser", roles); From 9870513beb0a656a7d9153ea30aa7a0000e17417 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 13 Mar 2015 22:55:10 +0100 Subject: [PATCH 377/457] [New] Added new param "privilegeConflictResolution" - privilegeConflictResolution is used to configure how conflicts of privileges on multiple roles are handled. - Implemented is STRICT where if a privilege with the same name exists on a role used by the same user occurs, then an exception is thrown. - Next is MERGE where if a conflict occurs, then the privileges are merged: allAllowed overrides, allow and deny list are merged --- config/PrivilegeConfig.xml | 1 + config/PrivilegeConfigMerge.xml | 33 ++++ config/PrivilegeModel.xml | 12 +- config/PrivilegeModelMerge.xml | 54 ++++++ .../base/PrivilegeConflictResolution.java | 52 ++++++ .../handler/DefaultPrivilegeHandler.java | 147 +++++++++++++++- .../privilege/handler/PrivilegeHandler.java | 6 + .../test/PrivilegeConflictMergeTest.java | 164 ++++++++++++++++++ .../privilege/test/PrivilegeTest.java | 35 +++- .../ch/eitchnet/privilege/test/XmlTest.java | 4 +- 10 files changed, 497 insertions(+), 11 deletions(-) create mode 100644 config/PrivilegeConfigMerge.xml create mode 100644 config/PrivilegeModelMerge.xml create mode 100644 src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java diff --git a/config/PrivilegeConfig.xml b/config/PrivilegeConfig.xml index 17fb4ad27..9dd3e2b11 100644 --- a/config/PrivilegeConfig.xml +++ b/config/PrivilegeConfig.xml @@ -6,6 +6,7 @@ + diff --git a/config/PrivilegeConfigMerge.xml b/config/PrivilegeConfigMerge.xml new file mode 100644 index 000000000..923189b53 --- /dev/null +++ b/config/PrivilegeConfigMerge.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 104b28130..070e087d7 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -89,7 +89,17 @@
      - + + + true + + + + + + true + + diff --git a/config/PrivilegeModelMerge.xml b/config/PrivilegeModelMerge.xml new file mode 100644 index 000000000..97fb7065d --- /dev/null +++ b/config/PrivilegeModelMerge.xml @@ -0,0 +1,54 @@ + + + + + + System User + Administrator + ENABLED + en_GB + + RoleA1 + RoleA2 + + + + System User + Administrator + ENABLED + en_GB + + RoleB1 + RoleB2 + + + + + + + + + allow1 + + + + + true + + + + + + allow1 + deny1 + + + + + allow2 + deny2 + + + + + \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java b/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java new file mode 100644 index 000000000..0d60fb28d --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.base; + +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * The {@link PrivilegeConflictResolution} defines what should be done if a {@link User} has {@link Role Roles} which + * have Privileges with conflicting names. + * + * @author Robert von Burg + */ +public enum PrivilegeConflictResolution { + + /** + * STRICT requires that a User may not have conflicting Privileges throug multiple Roles. In this case an Exception + * is thrown. + */ + STRICT { + @Override + public boolean isStrict() { + return true; + } + }, + + /** + * MERGE defines that if conflicting privileges are encountered then a merge is to take place. A merge means that if + * all is allowed, then that wins. Otherwise any existing allow and deny lists are merged + */ + MERGE { + @Override + public boolean isStrict() { + return false; + } + }; + + public abstract boolean isStrict(); +} diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 89fc92884..26eb14d20 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -33,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeConflictResolution; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.IPrivilege; @@ -111,6 +112,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private boolean autoPersistOnUserChangesData; + private PrivilegeConflictResolution privilegeConflictResolution; + @Override public RoleRep getRole(Certificate certificate, String roleName) { @@ -397,6 +400,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = createUser(userRep, passwordHash); + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + // validate this user may create such a user prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_USER, new Tuple(null, newUser))); @@ -449,6 +455,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { User newUser = createUser(userRep, passwordHash); + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + // validate this user may modify this user prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); @@ -525,6 +534,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + // validate this user may modify this user prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); @@ -594,6 +606,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties()); + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -815,6 +830,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new role from RoleRep Role newRole = new Role(roleRep); + // detect privilege conflicts + assertNoPrivilegeConflict(newRole); + // validate that this user may modify this role prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); @@ -903,6 +921,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new role Role newRole = new Role(existingRole.getName(), privilegeMap); + // detect privilege conflicts + assertNoPrivilegeConflict(newRole); + // validate that this user may modify this role prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); @@ -1078,16 +1099,40 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set privilegeNames = role.getPrivilegeNames(); for (String privilegeName : privilegeNames) { - // cache the privilege - if (privileges.containsKey(privilegeName)) - continue; - IPrivilege privilege = role.getPrivilege(privilegeName); if (privilege == null) { String msg = "The Privilege {0} does not exist for role {1}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, privilegeName, roleName); throw new PrivilegeException(msg); } + + // cache the privilege + if (privileges.containsKey(privilegeName)) { + if (this.privilegeConflictResolution.isStrict()) { + String msg = "User has conflicts for privilege {0} with role {1}"; + msg = MessageFormat.format(msg, privilegeName, roleName); + throw new PrivilegeException(msg); + } + + IPrivilege priv = privileges.get(privilegeName); + boolean allAllowed = priv.isAllAllowed() || privilege.isAllAllowed(); + Set allowList; + Set denyList; + if (allAllowed) { + allowList = Collections.emptySet(); + denyList = Collections.emptySet(); + } else { + allowList = new HashSet<>(priv.getAllowList()); + allowList.addAll(privilege.getAllowList()); + denyList = new HashSet<>(priv.getDenyList()); + denyList.addAll(privilege.getDenyList()); + } + priv = new PrivilegeImpl(priv.getName(), priv.getPolicy(), allAllowed, denyList, allowList); + + privileges.put(privilegeName, priv); + continue; + } + privileges.put(privilegeName, privilege); // cache the policy for the privilege @@ -1258,16 +1303,110 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { logger.error(msg); } + String privilegeConflictResolutionS = parameterMap.get(PARAM_PRIVILEGE_CONFLICT_RESOLUTION); + if (privilegeConflictResolutionS == null) { + this.privilegeConflictResolution = PrivilegeConflictResolution.STRICT; + String msg = "No {0} parameter defined. Using {1}"; + msg = MessageFormat.format(msg, PARAM_PRIVILEGE_CONFLICT_RESOLUTION, this.privilegeConflictResolution); + logger.info(msg); + } else { + try { + this.privilegeConflictResolution = PrivilegeConflictResolution.valueOf(privilegeConflictResolutionS); + } catch (Exception e) { + String msg = "Parameter {0} has illegal value {1}."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PRIVILEGE_CONFLICT_RESOLUTION, privilegeConflictResolutionS); + throw new PrivilegeException(msg); + } + } + logger.info("Privilege conflict resolution set to " + this.privilegeConflictResolution); //$NON-NLS-1$ + // validate policies on privileges of Roles for (Role role : persistenceHandler.getAllRoles()) { validatePolicies(role); } + // validate privilege conflicts + validatePrivilegeConflicts(); + this.lastSessionId = 0l; this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } + private void validatePrivilegeConflicts() { + if (!this.privilegeConflictResolution.isStrict()) { + return; + } + + List conflicts = new ArrayList<>(); + List users = this.persistenceHandler.getAllUsers(); + for (User user : users) { + Map privilegeNames = new HashMap<>(); + conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user)); + } + + if (!conflicts.isEmpty()) { + for (String conflict : conflicts) { + logger.error(conflict); + } + throw new PrivilegeException("There are " + conflicts.size() + " privilege conflicts!"); + } + } + + private void assertNoPrivilegeConflict(User user) { + if (this.privilegeConflictResolution.isStrict()) { + Map privilegeNames = new HashMap<>(); + List conflicts = detectPrivilegeConflicts(privilegeNames, user); + if (!conflicts.isEmpty()) { + String msg = conflicts.stream().collect(Collectors.joining("\n")); + throw new PrivilegeException(msg); + } + } + } + + private void assertNoPrivilegeConflict(Role role) { + if (!this.privilegeConflictResolution.isStrict()) + return; + + Map privilegeNames = new HashMap<>(); + for (String privilegeName : role.getPrivilegeNames()) { + privilegeNames.put(privilegeName, role.getName()); + } + + List conflicts = new ArrayList<>(); + List users = this.persistenceHandler.getAllUsers(); + for (User user : users) { + if (user.hasRole(role.getName())) + conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user)); + } + + if (!conflicts.isEmpty()) { + String msg = conflicts.stream().collect(Collectors.joining("\n")); + throw new PrivilegeException(msg); + } + } + + private List detectPrivilegeConflicts(Map privilegeNames, User user) { + List conflicts = new ArrayList<>(); + + Set userRoles = user.getRoles(); + for (String roleName : userRoles) { + Role role = this.persistenceHandler.getRole(roleName); + for (String privilegeName : role.getPrivilegeNames()) { + if (!privilegeNames.containsKey(privilegeName)) { + privilegeNames.put(privilegeName, roleName); + } else { + String roleOrigin = privilegeNames.get(privilegeName); + String msg = "User has conflicts for privilege {0} on roles {1} and {2}"; + msg = MessageFormat.format(msg, privilegeName, roleOrigin, roleName); + conflicts.add(msg); + } + } + } + + return conflicts; + } + /** * Validates that the policies which are not null on the privileges of the role exist * diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 9aea7c272..5b6d81ad1 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -20,6 +20,7 @@ import java.util.Locale; import java.util.Map; import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeConflictResolution; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.IPrivilege; @@ -121,6 +122,11 @@ public interface PrivilegeHandler { */ public static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ + /** + * configuration parameter to define {@link PrivilegeConflictResolution} + */ + public static final String PARAM_PRIVILEGE_CONFLICT_RESOLUTION = "privilegeConflictResolution"; + /** * Returns a {@link UserRep} for the given username * diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java new file mode 100644 index 000000000..ab9a352e1 --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +public class PrivilegeConflictMergeTest { + + private static final Logger logger = LoggerFactory.getLogger(PrivilegeConflictMergeTest.class); + + @BeforeClass + public static void init() throws Exception { + try { + destroy(); + + // copy configuration to tmp + String pwd = System.getProperty("user.dir"); + + File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModelMerge.xml"); + File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModelMerge.xml"); + if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { + throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + + tmpPrivilegeModelFile.getAbsolutePath()); + } + + File parentFile = tmpPrivilegeModelFile.getParentFile(); + if (!parentFile.exists()) { + if (!parentFile.mkdirs()) + throw new RuntimeException("Could not create parent for tmp " + tmpPrivilegeModelFile); + } + + if (!FileHelper.copy(origPrivilegeModelFile, tmpPrivilegeModelFile, true)) + throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + + throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); + } + } + + @AfterClass + public static void destroy() throws Exception { + + // delete temporary file + String pwd = System.getProperty("user.dir"); + + File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModelMerge.xml"); + if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { + throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + + tmpPrivilegeModelFile.getAbsolutePath()); + } + + // and temporary parent + File parentFile = tmpPrivilegeModelFile.getParentFile(); + if (parentFile.exists() && !parentFile.delete()) { + throw new RuntimeException("Could not remove temporary parent for tmp " + tmpPrivilegeModelFile); + } + } + + private PrivilegeHandler privilegeHandler; + private PrivilegeContext ctx; + + @Before + public void setup() throws Exception { + try { + + String pwd = System.getProperty("user.dir"); + + File privilegeConfigFile = new File(pwd + "/config/PrivilegeConfigMerge.xml"); + + // initialize privilege + privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + + throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); + } + } + + private void login(String username, byte[] password) { + Certificate certificate = privilegeHandler.authenticate(username, password); + assertTrue("Certificate is null!", certificate != null); + PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); + this.ctx = privilegeContext; + } + + private void logout() { + if (this.ctx != null) { + try { + PrivilegeContext privilegeContext = this.ctx; + this.ctx = null; + privilegeHandler.invalidateSession(privilegeContext.getCertificate()); + } catch (PrivilegeException e) { + String msg = "There is no PrivilegeContext currently bound to the ThreadLocal!"; + if (!e.getMessage().equals(msg)) + throw e; + } + } + } + + @Test + public void shouldMergePrivileges1() { + try { + login("userA", "admin".getBytes()); + IPrivilege privilege = this.ctx.getPrivilege("Foo"); + assertTrue(privilege.isAllAllowed()); + assertTrue(privilege.getAllowList().isEmpty()); + assertTrue(privilege.getDenyList().isEmpty()); + + } finally { + logout(); + } + } + + @Test + public void shouldMergePrivileges2() { + try { + login("userB", "admin".getBytes()); + IPrivilege privilege = this.ctx.getPrivilege("Bar"); + assertFalse(privilege.isAllAllowed()); + assertEquals(2, privilege.getAllowList().size()); + assertEquals(2, privilege.getDenyList().size()); + } finally { + logout(); + } + } +} diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 310c88ad4..f23293162 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -24,6 +24,7 @@ import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -75,6 +76,7 @@ public class PrivilegeTest { private static final byte[] PASS_BOB = "admin1".getBytes(); private static final String ROLE_APP_USER = "AppUser"; private static final String ROLE_MY = "MyRole"; + private static final String ROLE_MY2 = "MyRole2"; private static final String ROLE_TEMP = "temp"; private static final String ROLE_USER = "user"; private static final byte[] PASS_DEF = "def".getBytes(); @@ -89,10 +91,6 @@ public class PrivilegeTest { private static PrivilegeHandler privilegeHandler; private PrivilegeContext ctx; - /** - * @throws Exception - * if something goes wrong - */ @BeforeClass public static void init() throws Exception { try { @@ -425,6 +423,35 @@ public class PrivilegeTest { } } + @Test + public void shouldDetectPrivilegeConflict1() { + exception.expect(PrivilegeException.class); + exception.expectMessage("User has conflicts for privilege "); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = this.ctx.getCertificate(); + PrivilegeRep privilegeRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_ACTION, "DefaultPrivilege", true, + Collections.emptySet(), Collections.emptySet()); + privilegeHandler.addOrReplacePrivilegeOnRole(certificate, ROLE_APP_USER, privilegeRep); + } finally { + logout(); + } + } + + @Test + public void shouldDetectPrivilegeConflict2() { + exception.expect(PrivilegeException.class); + exception.expectMessage("User has conflicts for privilege "); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRoleToUser(certificate, ADMIN, ROLE_MY); + privilegeHandler.addRoleToUser(certificate, ADMIN, ROLE_MY2); + } finally { + logout(); + } + } + /** * This test performs multiple tests which are dependent on each other as the following is done: *
        diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 6526e913a..ba39eb0e2 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -120,7 +120,7 @@ public class XmlTest { assertNotNull(containerModel.getPersistenceHandlerClassName()); assertNotNull(containerModel.getPersistenceHandlerParameterMap()); - assertEquals(1, containerModel.getParameterMap().size()); + assertEquals(2, containerModel.getParameterMap().size()); assertEquals(3, containerModel.getPolicies().size()); assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); @@ -170,7 +170,7 @@ public class XmlTest { assertNotNull(roles); assertEquals(3, users.size()); - assertEquals(6, roles.size()); + assertEquals(7, roles.size()); // assert model From c2f4d7468b124f2579ac21d080d47c179f007afd Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 14 Mar 2015 21:22:20 +0100 Subject: [PATCH 378/457] [Major] changed Certificate to use Date and not long --- .../privilege/handler/DefaultPrivilegeHandler.java | 10 +++++----- .../ch/eitchnet/privilege/model/Certificate.java | 13 +++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 26eb14d20..4a76bf936 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -19,6 +19,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -1002,8 +1003,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, System.currentTimeMillis(), username, user.getFirstname(), - user.getLastname(), authToken, user.getLocale(), userRoles, new HashMap<>(user.getProperties())); + certificate = new Certificate(sessionId, new Date(), username, user.getFirstname(), user.getLastname(), + authToken, user.getLocale(), userRoles, new HashMap<>(user.getProperties())); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); @@ -1554,9 +1555,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - user.getFirstname(), user.getLastname(), authToken, user.getLocale(), user.getRoles(), new HashMap<>( - user.getProperties())); + Certificate systemUserCertificate = new Certificate(sessionId, new Date(), systemUsername, user.getFirstname(), + user.getLastname(), authToken, user.getLocale(), user.getRoles(), new HashMap<>(user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index cf9538c3c..2255300ca 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -17,6 +17,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -39,7 +40,7 @@ public final class Certificate implements Serializable { private static final long serialVersionUID = 1L; private final String sessionId; - private final long loginTime; + private final Date loginTime; private final String username; private final String firstname; private final String lastname; @@ -50,7 +51,7 @@ public final class Certificate implements Serializable { private final Map sessionDataMap; private Locale locale; - private long lastAccess; + private Date lastAccess; /** * Default constructor initializing with all information needed for this certificate @@ -78,7 +79,7 @@ public final class Certificate implements Serializable { * a {@link Map} containing string value pairs of properties for the logged in user. These properties can * be edited and can be used for the user to change settings of this session */ - public Certificate(String sessionId, long loginTime, String username, String firstname, String lastname, + public Certificate(String sessionId, Date loginTime, String username, String firstname, String lastname, String authToken, Locale locale, Set userRoles, Map propertyMap) { // validate arguments are not null @@ -211,7 +212,7 @@ public final class Certificate implements Serializable { /** * @return the loginTime */ - public long getLoginTime() { + public Date getLoginTime() { return this.loginTime; } @@ -227,7 +228,7 @@ public final class Certificate implements Serializable { /** * @return the lastAccess */ - public long getLastAccess() { + public Date getLastAccess() { return this.lastAccess; } @@ -235,7 +236,7 @@ public final class Certificate implements Serializable { * @param lastAccess * the lastAccess to set */ - public void setLastAccess(long lastAccess) { + public void setLastAccess(Date lastAccess) { this.lastAccess = lastAccess; } From 8e75a7651ae64b0cd47cfe923244d6828a9f4c93 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Mar 2015 11:02:57 +0100 Subject: [PATCH 379/457] [Bugfix] fixed exception formatting bug in StringHelper --- src/main/java/ch/eitchnet/utils/helper/StringHelper.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index c6306e61c..069135ed6 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -589,8 +589,6 @@ public class StringHelper { public static String formatExceptionMessage(Throwable t) { StringBuilder sb = new StringBuilder(); sb.append(t.getMessage()); - sb.append("\n"); - appendCause(sb, t); return sb.toString(); } @@ -600,9 +598,10 @@ public class StringHelper { if (cause == null) return; + sb.append("\n"); + sb.append("cause:\n"); sb.append(cause.getMessage()); - sb.append("\n"); if (cause.getCause() != null) appendCause(sb, cause.getCause()); From 6ccb4425cc683e96ec152c59da2621c5b99773eb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Mar 2015 11:03:23 +0100 Subject: [PATCH 380/457] [New] changed session ID to be a UUID --- .../handler/DefaultPrivilegeHandler.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 4a76bf936..3e6179ef0 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -27,6 +27,7 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -78,11 +79,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); - /** - * last assigned id for the {@link Certificate}s - */ - private long lastSessionId; - /** * Map keeping a reference to all active sessions */ @@ -1000,7 +996,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String authToken = this.encryptionHandler.convertToHash(this.encryptionHandler.nextToken()); // get next session id - String sessionId = nextSessionId(); + String sessionId = UUID.randomUUID().toString(); // create a new certificate, with details of the user certificate = new Certificate(sessionId, new Date(), username, user.getFirstname(), user.getLastname(), @@ -1329,7 +1325,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate privilege conflicts validatePrivilegeConflicts(); - this.lastSessionId = 0l; this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } @@ -1426,13 +1421,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @return a new session id - */ - private synchronized String nextSessionId() { - return Long.toString(++this.lastSessionId % Long.MAX_VALUE); - } - /** * Passwords should not be kept as strings, as string are immutable, this method thus clears the byte array so that * the password is not in memory anymore @@ -1552,7 +1540,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String authToken = this.encryptionHandler.nextToken(); // get next session id - String sessionId = nextSessionId(); + String sessionId = UUID.randomUUID().toString(); // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, new Date(), systemUsername, user.getFirstname(), From e076ced839e0ed1490a445b91c028272682b743e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Mar 2015 11:03:37 +0100 Subject: [PATCH 381/457] [New] Added UsernameFromCertificatePrivilege policy --- .../UsernameFromCertificatePrivilege.java | 57 +++++++++++++++++++ .../resources/PrivilegeMessages.properties | 5 +- 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java new file mode 100644 index 000000000..258f731e1 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; + +/** + * TODO + * + * @author Robert von Burg + */ +public class UsernameFromCertificatePrivilege implements PrivilegePolicy { + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Certificate)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.noncertificate"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + + Certificate cert = (Certificate) object; + String privilegeValue = cert.getUsername(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + } +} diff --git a/src/main/resources/PrivilegeMessages.properties b/src/main/resources/PrivilegeMessages.properties index 89127fb00..5c291a359 100644 --- a/src/main/resources/PrivilegeMessages.properties +++ b/src/main/resources/PrivilegeMessages.properties @@ -1,12 +1,13 @@ -Privilege.accessdenied.noprivilege=User {0} does not have Privilege {1} needed for Restrictable {2} +Privilege.accessdenied.noprivilege=User {0} does not have the privilege {1} needed for Restrictable {2} Privilege.illegalArgument.nonstring=\ {0} has returned a non-string privilege value\! Privilege.illegalArgument.nonrole=\ {0} did not return a Role privilege value\! +Privilege.illegalArgument.noncertificate=\ {0} did not return a Certificate privilege value\! Privilege.illegalArgument.nonuser=\ {0} did not return a User privilege value\! Privilege.illegalArgument.privilegeNameMismatch=The passed privilege has the name {0} but the restrictable is referencing privilege {1} Privilege.privilegeNameEmpty=The PrivilegeName for the Restrictable is null or empty: {0} Privilege.privilegeNull=Privilege may not be null\! Privilege.restrictableNull=Restrictable may not be null\! Privilege.noprivilege=No Privilege exists with name {0} -Privilege.noprivilege.user=User {0} does not have a Privilege {0} +Privilege.noprivilege.user=User {0} does not have the privilege {1} Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled RoleAccessPrivilege {0} Privilege.userAccessPrivilege.unknownPrivilege=Unhandled UserAccessPrivilege {0} From 2076675ca5f244181912c0ba67a609366bacb849 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 17 Mar 2015 21:47:22 +0100 Subject: [PATCH 382/457] [Minor] fixed broken tests --- src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index f23293162..3b7a35411 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -275,7 +275,7 @@ public class PrivilegeTest { public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); this.exception - .expectMessage("User system_admin2 does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable"); + .expectMessage("User system_admin2 does not have the privilege ch.eitchnet.privilege.test.model.TestRestrictable"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -531,7 +531,7 @@ public class PrivilegeTest { this.ctx.validateAction(restrictable); fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { - String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; + String msg = "User bob does not have the privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; assertEquals(msg, e.getLocalizedMessage()); } finally { logout(); From 3f7636428d460b37f1a5cb02144b41c0376be433 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 18 Mar 2015 20:11:45 +0100 Subject: [PATCH 383/457] [Minor] added JavaDoc to new policies --- .../eitchnet/privilege/policy/RoleAccessPrivilege.java | 5 ++++- .../eitchnet/privilege/policy/UserAccessPrivilege.java | 4 +++- .../policy/UsernameFromCertificatePrivilege.java | 10 +++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java index a2bf2fa1d..d5b94f859 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java @@ -28,7 +28,10 @@ import ch.eitchnet.utils.collections.Tuple; import ch.eitchnet.utils.dbc.DBC; /** - * TODO + * This {@link PrivilegePolicy} expects a {@link Tuple} as {@link Restrictable#getPrivilegeValue()}. The Tuple must + * contain {@link Role} as first and second value. Then the policy decides depending on the user specific privileges + * (see {@link PrivilegeHandler}), uses the basic Allow and Deny to detect if the username of + * the certificate is allowed * * @author Robert von Burg */ diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java index ad35d4115..1b92f19cf 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java @@ -28,7 +28,9 @@ import ch.eitchnet.utils.collections.Tuple; import ch.eitchnet.utils.dbc.DBC; /** - * TODO + * This {@link PrivilegePolicy} expects a {@link Tuple} as {@link Restrictable#getPrivilegeValue()} and then depending + * on the user specific privileges (see {@link PrivilegeHandler}), uses the basic Allow and + * Deny to detect if the username of the certificate is allowed * * @author Robert von Burg */ diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java index 258f731e1..425c4183d 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java @@ -25,7 +25,15 @@ import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.Restrictable; /** - * TODO + *

        + * This {@link PrivilegePolicy} expects a {@link Certificate} as {@link Restrictable#getPrivilegeValue()} and uses the + * basic Allow and Deny to detect if the username of the certificate is allowed. + *

        + * + *

        + * The {@link Certificate} as privilegeValue is not to be confused with the {@link Certificate} of the current user. + * This certificate is of the user to which access is request, i.e. modifying the session of a logged in user. + *

        * * @author Robert von Burg */ From 2607bbef3fc7df863548820665d8ed133e524c39 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 18 Mar 2015 20:40:10 +0100 Subject: [PATCH 384/457] [Bugfix] fixed bug where stream was not configured correctly --- .../eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 3e6179ef0..a91a018cd 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -167,7 +167,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate access to each role // TODO throwing and catching exception ain't cool - rolesStream.filter(role -> { + rolesStream = rolesStream.filter(role -> { try { prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role))); return true; @@ -191,7 +191,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate access to each user // TODO throwing and catching exception ain't cool - usersStream.filter(user -> { + usersStream = usersStream.filter(user -> { try { prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); return true; From 6338690ad2f849bdcb8c4c728b6bf96a87db42d8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 22 Mar 2015 00:33:59 +0100 Subject: [PATCH 385/457] [New] Added StringHelper.getExceptionMessage(Throwable) --- .../ch/eitchnet/utils/helper/StringHelper.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 069135ed6..b0adc8e75 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -563,6 +563,23 @@ public class StringHelper { } } + /** + *

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

        + * + *

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

        + * + * @param t + * @return + */ + public static String getExceptionMessage(Throwable t) { + return StringHelper.isEmpty(t.getMessage()) ? t.getClass().getName() : t.getMessage(); + } + /** * Formats the given {@link Throwable}'s stack trace to a string * From 4c6434f475dc40e73b54890540eaf943f21e1084 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 5 Apr 2015 00:13:16 +0200 Subject: [PATCH 386/457] [Major] Setting user password, locale and state are now separate privs --- config/PrivilegeModel.xml | 11 ++ .../handler/DefaultPrivilegeHandler.java | 41 ++---- .../privilege/handler/PrivilegeHandler.java | 14 +++ .../privilege/policy/RoleAccessPrivilege.java | 4 + .../privilege/policy/UserAccessPrivilege.java | 55 +++++++- .../resources/PrivilegeMessages.properties | 5 +- .../privilege/test/PrivilegeTest.java | 118 +++++++++++++++++- .../ch/eitchnet/privilege/test/XmlTest.java | 2 +- 8 files changed, 211 insertions(+), 39 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 070e087d7..68ef7222f 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -81,6 +81,17 @@ true + + true + + + ENABLED + DISABLED + SYSTEM + + + true + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index a91a018cd..1737181c4 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -651,18 +651,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { - // check if certificate is for same user, in which case user is changing their own locale - if (certificate.getUsername().equals(username)) { - - // validate the certificate - isCertificateValid(certificate); - - } else { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); - } + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_LOCALE); // get User User existingUser = this.persistenceHandler.getUser(username); @@ -677,8 +668,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // if the user is not setting their own locale, then make sure this user may set this user's locale if (!certificate.getUsername().equals(username)) { - PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_LOCALE, new Tuple(existingUser, newUser))); } // delegate user replacement to persistence handler @@ -696,18 +686,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void setUserPassword(Certificate certificate, String username, byte[] password) { try { - // check if certificate is for same user, in which case user is changing their own password - if (certificate.getUsername().equals(username)) { - - // validate the certificate - isCertificateValid(certificate); - - } else { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); - } + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_PASSWORD); // get User User existingUser = this.persistenceHandler.getUser(username); @@ -732,8 +713,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // if the user is not setting their own password, then make sure this user may set this user's password if (!certificate.getUsername().equals(username)) { - PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_PASSWORD, new Tuple(existingUser, + newUser))); } // delegate user replacement to persistence handler @@ -754,7 +735,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate user actually has this type of privilege PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); + prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_STATE); // get User User existingUser = this.persistenceHandler.getUser(username); @@ -768,7 +749,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { existingUser.getLocale(), existingUser.getProperties()); // validate that this user may modify this user's state - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_STATE, new Tuple(existingUser, newUser))); // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 5b6d81ad1..4e6b848d9 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -114,6 +114,20 @@ public interface PrivilegeHandler { * specific user */ public static final String PRIVILEGE_REMOVE_ROLE_FROM_USER = "PrivilegeRemoveRoleFromUser"; + /** + * Privilege "PRIVILEGE_SET_USER_LOCALE" which is used to validate that a user can set the locale of a user, or + * their own + */ + public static final String PRIVILEGE_SET_USER_LOCALE = "PrivilegeSetUserLocale"; + /** + * Privilege "PRIVILEGE_SET_USER_STATE" which is used to validate that a user can set the state of a user + */ + public static final String PRIVILEGE_SET_USER_STATE = "PrivilegeSetUserState"; + /** + * Privilege "PRIVILEGE_SET_USER_PASSWORD" which is used to validate that a user can set the password of a user, or + * their own + */ + public static final String PRIVILEGE_SET_USER_PASSWORD = "PrivilegeSetUserPassword"; /// diff --git a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java index d5b94f859..d82f3576d 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java @@ -44,6 +44,10 @@ public class RoleAccessPrivilege implements PrivilegePolicy { // get the value on which the action is to be performed Object object = restrictable.getPrivilegeValue(); + // if the object is null, then it means the validation is that the privilege must exist + if (object == null) + return; + // RoleAccessPrivilege policy expects the privilege value to be a role if (!(object instanceof Tuple)) { String msg = Restrictable.class.getName() diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java index 1b92f19cf..cafbd37c5 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java @@ -43,6 +43,10 @@ public class UserAccessPrivilege implements PrivilegePolicy { // get the value on which the action is to be performed Object object = restrictable.getPrivilegeValue(); + // if the object is null, then it means the validation is that the privilege must exist + if (object == null) + return; + // RoleAccessPrivilege policy expects the privilege value to be a role if (!(object instanceof Tuple)) { String msg = Restrictable.class.getName() @@ -130,11 +134,56 @@ public class UserAccessPrivilege implements PrivilegePolicy { break; } + case PrivilegeHandler.PRIVILEGE_SET_USER_STATE: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUserState().name(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_SET_USER_LOCALE: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + + // user can set their own locale + if (ctx.getUsername().equals(privilegeValue)) + return; + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + + // user can set their own password + if (ctx.getUsername().equals(privilegeValue)) + return; + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } default: - String msg = Restrictable.class.getName() - + PrivilegeMessages.getString("Privilege.userAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ - msg = MessageFormat.format(msg, privilegeName); + String msg = PrivilegeMessages.getString("Privilege.userAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, this.getClass().getName()); throw new PrivilegeException(msg); } } diff --git a/src/main/resources/PrivilegeMessages.properties b/src/main/resources/PrivilegeMessages.properties index 5c291a359..a92f0f135 100644 --- a/src/main/resources/PrivilegeMessages.properties +++ b/src/main/resources/PrivilegeMessages.properties @@ -4,10 +4,11 @@ Privilege.illegalArgument.nonrole=\ {0} did not return a Role privilege value\! Privilege.illegalArgument.noncertificate=\ {0} did not return a Certificate privilege value\! Privilege.illegalArgument.nonuser=\ {0} did not return a User privilege value\! Privilege.illegalArgument.privilegeNameMismatch=The passed privilege has the name {0} but the restrictable is referencing privilege {1} +Privilege.illegalArgument.nontuple=\ {0} did not return a Tuple privilege value\! Privilege.privilegeNameEmpty=The PrivilegeName for the Restrictable is null or empty: {0} Privilege.privilegeNull=Privilege may not be null\! Privilege.restrictableNull=Restrictable may not be null\! Privilege.noprivilege=No Privilege exists with name {0} Privilege.noprivilege.user=User {0} does not have the privilege {1} -Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled RoleAccessPrivilege {0} -Privilege.userAccessPrivilege.unknownPrivilege=Unhandled UserAccessPrivilege {0} +Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} +Privilege.userAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 3b7a35411..37d159165 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import org.junit.AfterClass; import org.junit.Before; @@ -67,6 +68,7 @@ import ch.eitchnet.utils.helper.FileHelper; public class PrivilegeTest { private static final String ROLE_PRIVILEGE_ADMIN = "PrivilegeAdmin"; + private static final String PRIVILEGE_USER_ACCESS = "UserAccessPrivilege"; private static final String ADMIN = "admin"; private static final byte[] PASS_ADMIN = "admin".getBytes(); private static final String BOB = "bob"; @@ -77,6 +79,7 @@ public class PrivilegeTest { private static final String ROLE_APP_USER = "AppUser"; private static final String ROLE_MY = "MyRole"; private static final String ROLE_MY2 = "MyRole2"; + private static final String ROLE_CHANGE_PW = "changePw"; private static final String ROLE_TEMP = "temp"; private static final String ROLE_USER = "user"; private static final byte[] PASS_DEF = "def".getBytes(); @@ -487,13 +490,121 @@ public class PrivilegeTest { addTedAsBob(); failAuthAsTedNoPass(); setPassForTedAsBob(); - tedChangesOwnPass(); + failSetSystemStateTed(); + failTedChangesPwAndLocale(); + addChangePwRoleToTed(); + tedChangesOwnPassAndLocale(); + failTedChangesOtherPwStateAndLocale(); authAsTed(); failPerformRestrictableAsBobNoRoleApp(); addRoleAppToBob(); performRestrictableAsBob(); } + private void failSetSystemStateTed() { + try { + // testEnableUserBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + Certificate certificate = this.ctx.getCertificate(); + + try { + privilegeHandler.setUserState(certificate, TED, UserState.SYSTEM); + fail("Should not be able to set user state to SYSTEM"); + } catch (AccessDeniedException e) { + // ok + } + + } finally { + logout(); + } + } + + private void failTedChangesOtherPwStateAndLocale() { + try { + // testTedChangesOwnPwd + login(TED, ArraysHelper.copyOf(PASS_TED)); + Certificate certificate = this.ctx.getCertificate(); + + try { + privilegeHandler.setUserPassword(certificate, BOB, ArraysHelper.copyOf(PASS_TED)); + fail("Should not be able to set password of other user, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + try { + privilegeHandler.setUserLocale(certificate, BOB, Locale.FRENCH); + fail("Should not be able to set locale of other user, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + try { + privilegeHandler.setUserState(certificate, BOB, UserState.DISABLED); + fail("Should not be able to set state of other user, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + } finally { + logout(); + } + } + + private void failTedChangesPwAndLocale() { + try { + // testTedChangesOwnPwd + login(TED, ArraysHelper.copyOf(PASS_DEF)); + Certificate certificate = this.ctx.getCertificate(); + + try { + privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_TED)); + fail("Should not be able to set password, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + try { + privilegeHandler.setUserLocale(certificate, TED, Locale.FRENCH); + fail("Should not be able to set locale, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + try { + privilegeHandler.setUserState(certificate, TED, UserState.ENABLED); + fail("Should not be able to set state, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + } finally { + logout(); + } + } + + private void addChangePwRoleToTed() { + try { + // add role user + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + PrivilegeRep passwordRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD, + PRIVILEGE_USER_ACCESS, false, Collections.emptySet(), Collections.emptySet()); + PrivilegeRep localeRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_LOCALE, + PRIVILEGE_USER_ACCESS, false, Collections.emptySet(), Collections.emptySet()); + + RoleRep roleRep = new RoleRep(ROLE_CHANGE_PW, Arrays.asList(passwordRep, localeRep)); + + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRole(certificate, roleRep); + privilegeHandler.addRoleToUser(certificate, TED, ROLE_CHANGE_PW); + logger.info("Added " + ROLE_CHANGE_PW + " to " + TED); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + private void performRestrictableAsBob() { try { // testPerformRestrictableAsBob @@ -547,12 +658,13 @@ public class PrivilegeTest { } } - private void tedChangesOwnPass() { + private void tedChangesOwnPassAndLocale() { try { // testTedChangesOwnPwd login(TED, ArraysHelper.copyOf(PASS_DEF)); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_TED)); + privilegeHandler.setUserLocale(certificate, TED, Locale.FRENCH); } finally { logout(); } @@ -666,7 +778,7 @@ public class PrivilegeTest { try { // add role user login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - RoleRep roleRep = new RoleRep(ROLE_USER, new ArrayList()); + RoleRep roleRep = new RoleRep(ROLE_USER, new ArrayList<>()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRole(certificate, roleRep); privilegeHandler.persist(certificate); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index ba39eb0e2..637c43901 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -212,7 +212,7 @@ public class XmlTest { // PrivilegeAdmin Role privilegeAdmin = findRole("PrivilegeAdmin", roles); assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); - assertEquals(11, privilegeAdmin.getPrivilegeNames().size()); + assertEquals(14, privilegeAdmin.getPrivilegeNames().size()); IPrivilege privilegeAction = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ACTION); assertFalse(privilegeAction.isAllAllowed()); assertEquals(3, privilegeAction.getAllowList().size()); From ca8f5b2f6a3e737de85914359085be3761bc67c3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 16 Apr 2015 15:45:04 +0200 Subject: [PATCH 387/457] [New] added DateRange.toString() --- .../java/ch/eitchnet/utils/collections/DateRange.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/collections/DateRange.java b/src/main/java/ch/eitchnet/utils/collections/DateRange.java index 3c25b4bc7..b4410b8a9 100644 --- a/src/main/java/ch/eitchnet/utils/collections/DateRange.java +++ b/src/main/java/ch/eitchnet/utils/collections/DateRange.java @@ -18,6 +18,7 @@ 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 @@ -122,4 +123,11 @@ public class DateRange { } 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)"); + } } From 556981777c129ea17cd4acc7d13e5e8162498ac6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 19 Apr 2015 16:12:26 +0200 Subject: [PATCH 388/457] [New] Added IndentingXMLStreamWriter from javanet.staxutils --- .../java/javanet/staxutils/Indentation.java | 36 ++ .../staxutils/IndentingXMLStreamWriter.java | 370 ++++++++++++++++++ .../helpers/StreamWriterDelegate.java | 213 ++++++++++ 3 files changed, 619 insertions(+) create mode 100644 src/main/java/javanet/staxutils/Indentation.java create mode 100644 src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java create mode 100644 src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java diff --git a/src/main/java/javanet/staxutils/Indentation.java b/src/main/java/javanet/staxutils/Indentation.java new file mode 100644 index 000000000..844513789 --- /dev/null +++ b/src/main/java/javanet/staxutils/Indentation.java @@ -0,0 +1,36 @@ +package javanet.staxutils; + +/** + * Characters that represent line breaks and indentation. These are represented as String-valued JavaBean properties. + */ +@SuppressWarnings("nls") +public interface Indentation { + + /** Two spaces; the default indentation. */ + public static final String DEFAULT_INDENT = " "; + + /** + * Set the characters used for one level of indentation. The default is {@link #DEFAULT_INDENT}. "\t" is a popular + * alternative. + */ + void setIndent(String indent); + + /** The characters used for one level of indentation. */ + String getIndent(); + + /** + * "\n"; the normalized representation of end-of-line in XML. + */ + public static final String NORMAL_END_OF_LINE = "\n"; + + /** + * Set the characters that introduce a new line. The default is {@link #NORMAL_END_OF_LINE}. + * {@link IndentingXMLStreamWriter#getLineSeparator}() is a popular alternative. + */ + public void setNewLine(String newLine); + + /** The characters that introduce a new line. */ + String getNewLine(); + +} diff --git a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java new file mode 100644 index 000000000..f4af62c62 --- /dev/null +++ b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2006, John Kristian + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of StAX-Utils nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package javanet.staxutils; + +import javanet.staxutils.helpers.StreamWriterDelegate; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * A filter that indents an XML stream. To apply it, construct a filter that contains another {@link XMLStreamWriter}, + * which you pass to the constructor. Then call methods of the filter instead of the contained stream. For example: + * + *
        + * {@link XMLStreamWriter} stream = ...
        + * stream = new {@link IndentingXMLStreamWriter}(stream);
        + * stream.writeStartDocument();
        + * ...
        + * 
        + * + *

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

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

        + * Indentation can be adjusted by setting the newLine and indent properties. But set them to whitespace only, for best + * results. Non-whitespace is apt to cause problems, for example when this class attempts to insert newLine before the + * root element. + * + * @author John Kristian + */ +@SuppressWarnings("nls") +public class IndentingXMLStreamWriter extends StreamWriterDelegate implements Indentation { + + public IndentingXMLStreamWriter(XMLStreamWriter out) { + this(out, DEFAULT_INDENT, NORMAL_END_OF_LINE); + } + + public IndentingXMLStreamWriter(XMLStreamWriter out, String indent) { + this(out, indent, NORMAL_END_OF_LINE); + } + + public IndentingXMLStreamWriter(XMLStreamWriter out, String indent, String newLine) { + super(out); + setIndent(indent); + setNewLine(newLine); + } + + /** How deeply nested the current scope is. The root element is depth 1. */ + private int depth = 0; // document scope + + /** stack[depth] indicates what's been written into the current scope. */ + private int[] stack = new int[] { 0, 0, 0, 0 }; // nothing written yet + + private static final int WROTE_MARKUP = 1; + + private static final int WROTE_DATA = 2; + + private String indent = DEFAULT_INDENT; + + private String newLine = NORMAL_END_OF_LINE; + + /** newLine followed by copies of indent. */ + private char[] linePrefix = null; + + @Override + public void setIndent(String indent) { + if (!indent.equals(this.indent)) { + this.indent = indent; + this.linePrefix = null; + } + } + + @Override + public String getIndent() { + return this.indent; + } + + @Override + public void setNewLine(String newLine) { + if (!newLine.equals(this.newLine)) { + this.newLine = newLine; + this.linePrefix = null; + } + } + + /** + * @return System.getProperty("line.separator"); or {@link #NORMAL_END_OF_LINE} if that fails. + */ + public static String getLineSeparator() { + try { + return System.getProperty("line.separator"); + } catch (SecurityException ignored) { + // + } + return NORMAL_END_OF_LINE; + } + + @Override + public String getNewLine() { + return this.newLine; + } + + @Override + public void writeStartDocument() throws XMLStreamException { + beforeMarkup(); + this.out.writeStartDocument(); + afterMarkup(); + } + + @Override + public void writeStartDocument(String version) throws XMLStreamException { + beforeMarkup(); + this.out.writeStartDocument(version); + afterMarkup(); + } + + @Override + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + beforeMarkup(); + this.out.writeStartDocument(encoding, version); + afterMarkup(); + } + + @Override + public void writeDTD(String dtd) throws XMLStreamException { + beforeMarkup(); + this.out.writeDTD(dtd); + afterMarkup(); + } + + @Override + public void writeProcessingInstruction(String target) throws XMLStreamException { + beforeMarkup(); + this.out.writeProcessingInstruction(target); + afterMarkup(); + } + + @Override + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + beforeMarkup(); + this.out.writeProcessingInstruction(target, data); + afterMarkup(); + } + + @Override + public void writeComment(String data) throws XMLStreamException { + beforeMarkup(); + this.out.writeComment(data); + afterMarkup(); + } + + @Override + public void writeEmptyElement(String localName) throws XMLStreamException { + beforeMarkup(); + this.out.writeEmptyElement(localName); + afterMarkup(); + } + + @Override + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + beforeMarkup(); + this.out.writeEmptyElement(namespaceURI, localName); + afterMarkup(); + } + + @Override + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + beforeMarkup(); + this.out.writeEmptyElement(prefix, localName, namespaceURI); + afterMarkup(); + } + + @Override + public void writeStartElement(String localName) throws XMLStreamException { + beforeStartElement(); + this.out.writeStartElement(localName); + afterStartElement(); + } + + @Override + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + beforeStartElement(); + this.out.writeStartElement(namespaceURI, localName); + afterStartElement(); + } + + @Override + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + beforeStartElement(); + this.out.writeStartElement(prefix, localName, namespaceURI); + afterStartElement(); + } + + @Override + public void writeCharacters(String text) throws XMLStreamException { + this.out.writeCharacters(text); + afterData(); + } + + @Override + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + this.out.writeCharacters(text, start, len); + afterData(); + } + + @Override + public void writeCData(String data) throws XMLStreamException { + this.out.writeCData(data); + afterData(); + } + + @Override + public void writeEntityRef(String name) throws XMLStreamException { + this.out.writeEntityRef(name); + afterData(); + } + + @Override + public void writeEndElement() throws XMLStreamException { + beforeEndElement(); + this.out.writeEndElement(); + afterEndElement(); + } + + @Override + public void writeEndDocument() throws XMLStreamException { + try { + while (this.depth > 0) { + writeEndElement(); // indented + } + } catch (Exception ignored) { + ignored.printStackTrace(); + } + this.out.writeEndDocument(); + afterEndDocument(); + } + + /** Prepare to write markup, by writing a new line and indentation. */ + protected void beforeMarkup() { + int soFar = this.stack[this.depth]; + if ((soFar & WROTE_DATA) == 0 // no data in this scope + && (this.depth > 0 || soFar != 0)) // not the first line + { + try { + writeNewLine(this.depth); + if (this.depth > 0 && getIndent().length() > 0) { + afterMarkup(); // indentation was written + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** Note that markup or indentation was written. */ + protected void afterMarkup() { + this.stack[this.depth] |= WROTE_MARKUP; + } + + /** Note that data were written. */ + protected void afterData() { + this.stack[this.depth] |= WROTE_DATA; + } + + /** Prepare to start an element, by allocating stack space. */ + protected void beforeStartElement() { + beforeMarkup(); + if (this.stack.length <= this.depth + 1) { + // Allocate more space for the stack: + int[] newStack = new int[this.stack.length * 2]; + System.arraycopy(this.stack, 0, newStack, 0, this.stack.length); + this.stack = newStack; + } + this.stack[this.depth + 1] = 0; // nothing written yet + } + + /** Note that an element was started. */ + protected void afterStartElement() { + afterMarkup(); + ++this.depth; + } + + /** Prepare to end an element, by writing a new line and indentation. */ + protected void beforeEndElement() { + if (this.depth > 0 && this.stack[this.depth] == WROTE_MARKUP) { // but not data + try { + writeNewLine(this.depth - 1); + } catch (Exception ignored) { + ignored.printStackTrace(); + } + } + } + + /** Note that an element was ended. */ + protected void afterEndElement() { + if (this.depth > 0) { + --this.depth; + } + } + + /** Note that a document was ended. */ + protected void afterEndDocument() { + if (this.stack[this.depth = 0] == WROTE_MARKUP) { // but not data + try { + writeNewLine(0); + } catch (Exception ignored) { + ignored.printStackTrace(); + } + } + this.stack[this.depth] = 0; // start fresh + } + + /** Write a line separator followed by indentation. */ + protected void writeNewLine(int indentation) throws XMLStreamException { + final int newLineLength = getNewLine().length(); + final int prefixLength = newLineLength + (getIndent().length() * indentation); + if (prefixLength > 0) { + if (this.linePrefix == null) { + this.linePrefix = (getNewLine() + getIndent()).toCharArray(); + } + while (prefixLength > this.linePrefix.length) { + // make linePrefix longer: + char[] newPrefix = new char[newLineLength + ((this.linePrefix.length - newLineLength) * 2)]; + System.arraycopy(this.linePrefix, 0, newPrefix, 0, this.linePrefix.length); + System.arraycopy(this.linePrefix, newLineLength, newPrefix, this.linePrefix.length, + this.linePrefix.length - newLineLength); + this.linePrefix = newPrefix; + } + this.out.writeCharacters(this.linePrefix, 0, prefixLength); + } + } + +} diff --git a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java new file mode 100644 index 000000000..9922aafa7 --- /dev/null +++ b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2006, John Kristian + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of StAX-Utils nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +package javanet.staxutils.helpers; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +/** + * Abstract class for writing filtered XML streams. This class provides methods that merely delegate to the contained + * stream. Subclasses should override some of these methods, and may also provide additional methods and fields. + * + * @author John Kristian + */ +public abstract class StreamWriterDelegate implements XMLStreamWriter { + + protected StreamWriterDelegate(XMLStreamWriter out) { + this.out = out; + } + + protected XMLStreamWriter out; + + @Override + public Object getProperty(String name) throws IllegalArgumentException { + return this.out.getProperty(name); + } + + @Override + public NamespaceContext getNamespaceContext() { + return this.out.getNamespaceContext(); + } + + @Override + public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { + this.out.setNamespaceContext(context); + } + + @Override + public void setDefaultNamespace(String uri) throws XMLStreamException { + this.out.setDefaultNamespace(uri); + } + + @Override + public void writeStartDocument() throws XMLStreamException { + this.out.writeStartDocument(); + } + + @Override + public void writeStartDocument(String version) throws XMLStreamException { + this.out.writeStartDocument(version); + } + + @Override + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + this.out.writeStartDocument(encoding, version); + } + + @Override + public void writeDTD(String dtd) throws XMLStreamException { + this.out.writeDTD(dtd); + } + + @Override + public void writeProcessingInstruction(String target) throws XMLStreamException { + this.out.writeProcessingInstruction(target); + } + + @Override + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + this.out.writeProcessingInstruction(target, data); + } + + @Override + public void writeComment(String data) throws XMLStreamException { + this.out.writeComment(data); + } + + @Override + public void writeEmptyElement(String localName) throws XMLStreamException { + this.out.writeEmptyElement(localName); + } + + @Override + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + this.out.writeEmptyElement(namespaceURI, localName); + } + + @Override + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + this.out.writeEmptyElement(prefix, localName, namespaceURI); + } + + @Override + public void writeStartElement(String localName) throws XMLStreamException { + this.out.writeStartElement(localName); + } + + @Override + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + this.out.writeStartElement(namespaceURI, localName); + } + + @Override + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + this.out.writeStartElement(prefix, localName, namespaceURI); + } + + @Override + public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { + this.out.writeDefaultNamespace(namespaceURI); + } + + @Override + public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { + this.out.writeNamespace(prefix, namespaceURI); + } + + @Override + public String getPrefix(String uri) throws XMLStreamException { + return this.out.getPrefix(uri); + } + + @Override + public void setPrefix(String prefix, String uri) throws XMLStreamException { + this.out.setPrefix(prefix, uri); + } + + @Override + public void writeAttribute(String localName, String value) throws XMLStreamException { + this.out.writeAttribute(localName, value); + } + + @Override + public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { + this.out.writeAttribute(namespaceURI, localName, value); + } + + @Override + public void writeAttribute(String prefix, String namespaceURI, String localName, String value) + throws XMLStreamException { + this.out.writeAttribute(prefix, namespaceURI, localName, value); + } + + @Override + public void writeCharacters(String text) throws XMLStreamException { + this.out.writeCharacters(text); + } + + @Override + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + this.out.writeCharacters(text, start, len); + } + + @Override + public void writeCData(String data) throws XMLStreamException { + this.out.writeCData(data); + } + + @Override + public void writeEntityRef(String name) throws XMLStreamException { + this.out.writeEntityRef(name); + } + + @Override + public void writeEndElement() throws XMLStreamException { + this.out.writeEndElement(); + } + + @Override + public void writeEndDocument() throws XMLStreamException { + this.out.writeEndDocument(); + } + + @Override + public void flush() throws XMLStreamException { + this.out.flush(); + } + + @Override + public void close() throws XMLStreamException { + this.out.close(); + } + +} From f72ff76b406f2675cf194174aa35872d0c969ab6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 19 Apr 2015 16:12:55 +0200 Subject: [PATCH 389/457] [Major] moved javanet.staxutils to ch.eitchnet.utils --- .../java/javanet/staxutils/Indentation.java | 36 -- .../staxutils/IndentingXMLStreamWriter.java | 370 ------------------ .../helpers/StreamWriterDelegate.java | 213 ---------- 3 files changed, 619 deletions(-) delete mode 100644 src/main/java/javanet/staxutils/Indentation.java delete mode 100644 src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java delete mode 100644 src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java diff --git a/src/main/java/javanet/staxutils/Indentation.java b/src/main/java/javanet/staxutils/Indentation.java deleted file mode 100644 index 844513789..000000000 --- a/src/main/java/javanet/staxutils/Indentation.java +++ /dev/null @@ -1,36 +0,0 @@ -package javanet.staxutils; - -/** - * Characters that represent line breaks and indentation. These are represented as String-valued JavaBean properties. - */ -@SuppressWarnings("nls") -public interface Indentation { - - /** Two spaces; the default indentation. */ - public static final String DEFAULT_INDENT = " "; - - /** - * Set the characters used for one level of indentation. The default is {@link #DEFAULT_INDENT}. "\t" is a popular - * alternative. - */ - void setIndent(String indent); - - /** The characters used for one level of indentation. */ - String getIndent(); - - /** - * "\n"; the normalized representation of end-of-line in XML. - */ - public static final String NORMAL_END_OF_LINE = "\n"; - - /** - * Set the characters that introduce a new line. The default is {@link #NORMAL_END_OF_LINE}. - * {@link IndentingXMLStreamWriter#getLineSeparator}() is a popular alternative. - */ - public void setNewLine(String newLine); - - /** The characters that introduce a new line. */ - String getNewLine(); - -} diff --git a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java deleted file mode 100644 index f4af62c62..000000000 --- a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright (c) 2006, John Kristian - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of StAX-Utils nor the names of its contributors - * may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - */ -package javanet.staxutils; - -import javanet.staxutils.helpers.StreamWriterDelegate; - -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; - -/** - * A filter that indents an XML stream. To apply it, construct a filter that contains another {@link XMLStreamWriter}, - * which you pass to the constructor. Then call methods of the filter instead of the contained stream. For example: - * - *

        - * {@link XMLStreamWriter} stream = ...
        - * stream = new {@link IndentingXMLStreamWriter}(stream);
        - * stream.writeStartDocument();
        - * ...
        - * 
        - * - *

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

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

        - * Indentation can be adjusted by setting the newLine and indent properties. But set them to whitespace only, for best - * results. Non-whitespace is apt to cause problems, for example when this class attempts to insert newLine before the - * root element. - * - * @author John Kristian - */ -@SuppressWarnings("nls") -public class IndentingXMLStreamWriter extends StreamWriterDelegate implements Indentation { - - public IndentingXMLStreamWriter(XMLStreamWriter out) { - this(out, DEFAULT_INDENT, NORMAL_END_OF_LINE); - } - - public IndentingXMLStreamWriter(XMLStreamWriter out, String indent) { - this(out, indent, NORMAL_END_OF_LINE); - } - - public IndentingXMLStreamWriter(XMLStreamWriter out, String indent, String newLine) { - super(out); - setIndent(indent); - setNewLine(newLine); - } - - /** How deeply nested the current scope is. The root element is depth 1. */ - private int depth = 0; // document scope - - /** stack[depth] indicates what's been written into the current scope. */ - private int[] stack = new int[] { 0, 0, 0, 0 }; // nothing written yet - - private static final int WROTE_MARKUP = 1; - - private static final int WROTE_DATA = 2; - - private String indent = DEFAULT_INDENT; - - private String newLine = NORMAL_END_OF_LINE; - - /** newLine followed by copies of indent. */ - private char[] linePrefix = null; - - @Override - public void setIndent(String indent) { - if (!indent.equals(this.indent)) { - this.indent = indent; - this.linePrefix = null; - } - } - - @Override - public String getIndent() { - return this.indent; - } - - @Override - public void setNewLine(String newLine) { - if (!newLine.equals(this.newLine)) { - this.newLine = newLine; - this.linePrefix = null; - } - } - - /** - * @return System.getProperty("line.separator"); or {@link #NORMAL_END_OF_LINE} if that fails. - */ - public static String getLineSeparator() { - try { - return System.getProperty("line.separator"); - } catch (SecurityException ignored) { - // - } - return NORMAL_END_OF_LINE; - } - - @Override - public String getNewLine() { - return this.newLine; - } - - @Override - public void writeStartDocument() throws XMLStreamException { - beforeMarkup(); - this.out.writeStartDocument(); - afterMarkup(); - } - - @Override - public void writeStartDocument(String version) throws XMLStreamException { - beforeMarkup(); - this.out.writeStartDocument(version); - afterMarkup(); - } - - @Override - public void writeStartDocument(String encoding, String version) throws XMLStreamException { - beforeMarkup(); - this.out.writeStartDocument(encoding, version); - afterMarkup(); - } - - @Override - public void writeDTD(String dtd) throws XMLStreamException { - beforeMarkup(); - this.out.writeDTD(dtd); - afterMarkup(); - } - - @Override - public void writeProcessingInstruction(String target) throws XMLStreamException { - beforeMarkup(); - this.out.writeProcessingInstruction(target); - afterMarkup(); - } - - @Override - public void writeProcessingInstruction(String target, String data) throws XMLStreamException { - beforeMarkup(); - this.out.writeProcessingInstruction(target, data); - afterMarkup(); - } - - @Override - public void writeComment(String data) throws XMLStreamException { - beforeMarkup(); - this.out.writeComment(data); - afterMarkup(); - } - - @Override - public void writeEmptyElement(String localName) throws XMLStreamException { - beforeMarkup(); - this.out.writeEmptyElement(localName); - afterMarkup(); - } - - @Override - public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { - beforeMarkup(); - this.out.writeEmptyElement(namespaceURI, localName); - afterMarkup(); - } - - @Override - public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { - beforeMarkup(); - this.out.writeEmptyElement(prefix, localName, namespaceURI); - afterMarkup(); - } - - @Override - public void writeStartElement(String localName) throws XMLStreamException { - beforeStartElement(); - this.out.writeStartElement(localName); - afterStartElement(); - } - - @Override - public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { - beforeStartElement(); - this.out.writeStartElement(namespaceURI, localName); - afterStartElement(); - } - - @Override - public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { - beforeStartElement(); - this.out.writeStartElement(prefix, localName, namespaceURI); - afterStartElement(); - } - - @Override - public void writeCharacters(String text) throws XMLStreamException { - this.out.writeCharacters(text); - afterData(); - } - - @Override - public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { - this.out.writeCharacters(text, start, len); - afterData(); - } - - @Override - public void writeCData(String data) throws XMLStreamException { - this.out.writeCData(data); - afterData(); - } - - @Override - public void writeEntityRef(String name) throws XMLStreamException { - this.out.writeEntityRef(name); - afterData(); - } - - @Override - public void writeEndElement() throws XMLStreamException { - beforeEndElement(); - this.out.writeEndElement(); - afterEndElement(); - } - - @Override - public void writeEndDocument() throws XMLStreamException { - try { - while (this.depth > 0) { - writeEndElement(); // indented - } - } catch (Exception ignored) { - ignored.printStackTrace(); - } - this.out.writeEndDocument(); - afterEndDocument(); - } - - /** Prepare to write markup, by writing a new line and indentation. */ - protected void beforeMarkup() { - int soFar = this.stack[this.depth]; - if ((soFar & WROTE_DATA) == 0 // no data in this scope - && (this.depth > 0 || soFar != 0)) // not the first line - { - try { - writeNewLine(this.depth); - if (this.depth > 0 && getIndent().length() > 0) { - afterMarkup(); // indentation was written - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - /** Note that markup or indentation was written. */ - protected void afterMarkup() { - this.stack[this.depth] |= WROTE_MARKUP; - } - - /** Note that data were written. */ - protected void afterData() { - this.stack[this.depth] |= WROTE_DATA; - } - - /** Prepare to start an element, by allocating stack space. */ - protected void beforeStartElement() { - beforeMarkup(); - if (this.stack.length <= this.depth + 1) { - // Allocate more space for the stack: - int[] newStack = new int[this.stack.length * 2]; - System.arraycopy(this.stack, 0, newStack, 0, this.stack.length); - this.stack = newStack; - } - this.stack[this.depth + 1] = 0; // nothing written yet - } - - /** Note that an element was started. */ - protected void afterStartElement() { - afterMarkup(); - ++this.depth; - } - - /** Prepare to end an element, by writing a new line and indentation. */ - protected void beforeEndElement() { - if (this.depth > 0 && this.stack[this.depth] == WROTE_MARKUP) { // but not data - try { - writeNewLine(this.depth - 1); - } catch (Exception ignored) { - ignored.printStackTrace(); - } - } - } - - /** Note that an element was ended. */ - protected void afterEndElement() { - if (this.depth > 0) { - --this.depth; - } - } - - /** Note that a document was ended. */ - protected void afterEndDocument() { - if (this.stack[this.depth = 0] == WROTE_MARKUP) { // but not data - try { - writeNewLine(0); - } catch (Exception ignored) { - ignored.printStackTrace(); - } - } - this.stack[this.depth] = 0; // start fresh - } - - /** Write a line separator followed by indentation. */ - protected void writeNewLine(int indentation) throws XMLStreamException { - final int newLineLength = getNewLine().length(); - final int prefixLength = newLineLength + (getIndent().length() * indentation); - if (prefixLength > 0) { - if (this.linePrefix == null) { - this.linePrefix = (getNewLine() + getIndent()).toCharArray(); - } - while (prefixLength > this.linePrefix.length) { - // make linePrefix longer: - char[] newPrefix = new char[newLineLength + ((this.linePrefix.length - newLineLength) * 2)]; - System.arraycopy(this.linePrefix, 0, newPrefix, 0, this.linePrefix.length); - System.arraycopy(this.linePrefix, newLineLength, newPrefix, this.linePrefix.length, - this.linePrefix.length - newLineLength); - this.linePrefix = newPrefix; - } - this.out.writeCharacters(this.linePrefix, 0, prefixLength); - } - } - -} diff --git a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java b/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java deleted file mode 100644 index 9922aafa7..000000000 --- a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2006, John Kristian - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of StAX-Utils nor the names of its contributors - * may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - */ -package javanet.staxutils.helpers; - -import javax.xml.namespace.NamespaceContext; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; - -/** - * Abstract class for writing filtered XML streams. This class provides methods that merely delegate to the contained - * stream. Subclasses should override some of these methods, and may also provide additional methods and fields. - * - * @author John Kristian - */ -public abstract class StreamWriterDelegate implements XMLStreamWriter { - - protected StreamWriterDelegate(XMLStreamWriter out) { - this.out = out; - } - - protected XMLStreamWriter out; - - @Override - public Object getProperty(String name) throws IllegalArgumentException { - return this.out.getProperty(name); - } - - @Override - public NamespaceContext getNamespaceContext() { - return this.out.getNamespaceContext(); - } - - @Override - public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { - this.out.setNamespaceContext(context); - } - - @Override - public void setDefaultNamespace(String uri) throws XMLStreamException { - this.out.setDefaultNamespace(uri); - } - - @Override - public void writeStartDocument() throws XMLStreamException { - this.out.writeStartDocument(); - } - - @Override - public void writeStartDocument(String version) throws XMLStreamException { - this.out.writeStartDocument(version); - } - - @Override - public void writeStartDocument(String encoding, String version) throws XMLStreamException { - this.out.writeStartDocument(encoding, version); - } - - @Override - public void writeDTD(String dtd) throws XMLStreamException { - this.out.writeDTD(dtd); - } - - @Override - public void writeProcessingInstruction(String target) throws XMLStreamException { - this.out.writeProcessingInstruction(target); - } - - @Override - public void writeProcessingInstruction(String target, String data) throws XMLStreamException { - this.out.writeProcessingInstruction(target, data); - } - - @Override - public void writeComment(String data) throws XMLStreamException { - this.out.writeComment(data); - } - - @Override - public void writeEmptyElement(String localName) throws XMLStreamException { - this.out.writeEmptyElement(localName); - } - - @Override - public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { - this.out.writeEmptyElement(namespaceURI, localName); - } - - @Override - public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { - this.out.writeEmptyElement(prefix, localName, namespaceURI); - } - - @Override - public void writeStartElement(String localName) throws XMLStreamException { - this.out.writeStartElement(localName); - } - - @Override - public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { - this.out.writeStartElement(namespaceURI, localName); - } - - @Override - public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { - this.out.writeStartElement(prefix, localName, namespaceURI); - } - - @Override - public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { - this.out.writeDefaultNamespace(namespaceURI); - } - - @Override - public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { - this.out.writeNamespace(prefix, namespaceURI); - } - - @Override - public String getPrefix(String uri) throws XMLStreamException { - return this.out.getPrefix(uri); - } - - @Override - public void setPrefix(String prefix, String uri) throws XMLStreamException { - this.out.setPrefix(prefix, uri); - } - - @Override - public void writeAttribute(String localName, String value) throws XMLStreamException { - this.out.writeAttribute(localName, value); - } - - @Override - public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { - this.out.writeAttribute(namespaceURI, localName, value); - } - - @Override - public void writeAttribute(String prefix, String namespaceURI, String localName, String value) - throws XMLStreamException { - this.out.writeAttribute(prefix, namespaceURI, localName, value); - } - - @Override - public void writeCharacters(String text) throws XMLStreamException { - this.out.writeCharacters(text); - } - - @Override - public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { - this.out.writeCharacters(text, start, len); - } - - @Override - public void writeCData(String data) throws XMLStreamException { - this.out.writeCData(data); - } - - @Override - public void writeEntityRef(String name) throws XMLStreamException { - this.out.writeEntityRef(name); - } - - @Override - public void writeEndElement() throws XMLStreamException { - this.out.writeEndElement(); - } - - @Override - public void writeEndDocument() throws XMLStreamException { - this.out.writeEndDocument(); - } - - @Override - public void flush() throws XMLStreamException { - this.out.flush(); - } - - @Override - public void close() throws XMLStreamException { - this.out.close(); - } - -} From dd1489772d243af5429201c5ac45a450d8635809 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 21 Apr 2015 15:03:30 +0200 Subject: [PATCH 390/457] [Major] Refactored DB package: Now use DataSource classes - this allows to inject a connection pool --- .../ch/eitchnet/db/DbConnectionCheck.java | 28 ++-- .../java/ch/eitchnet/db/DbConnectionInfo.java | 132 ------------------ .../ch/eitchnet/db/DbDataSourceBuilder.java | 28 ++++ .../java/ch/eitchnet/db/DbDriverLoader.java | 49 ------- .../ch/eitchnet/db/DbSchemaVersionCheck.java | 33 +++-- 5 files changed, 58 insertions(+), 212 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/db/DbConnectionInfo.java create mode 100644 src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java delete mode 100644 src/main/java/ch/eitchnet/db/DbDriverLoader.java diff --git a/src/main/java/ch/eitchnet/db/DbConnectionCheck.java b/src/main/java/ch/eitchnet/db/DbConnectionCheck.java index 9177757ee..967cdacf3 100644 --- a/src/main/java/ch/eitchnet/db/DbConnectionCheck.java +++ b/src/main/java/ch/eitchnet/db/DbConnectionCheck.java @@ -16,7 +16,6 @@ package ch.eitchnet.db; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -24,6 +23,8 @@ import java.text.MessageFormat; import java.util.Collection; import java.util.Map; +import javax.sql.DataSource; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,13 +34,13 @@ import org.slf4j.LoggerFactory; public class DbConnectionCheck { private static final Logger logger = LoggerFactory.getLogger(DbConnectionCheck.class); - private Map connetionInfoMap; + private Map dsMap; /** - * @param connetionInfoMap + * @param dsMap */ - public DbConnectionCheck(Map connetionInfoMap) { - this.connetionInfoMap = connetionInfoMap; + public DbConnectionCheck(Map dsMap) { + this.dsMap = dsMap; } /** @@ -48,17 +49,12 @@ public class DbConnectionCheck { * @throws DbException */ public void checkConnections() throws DbException { - Collection values = this.connetionInfoMap.values(); - for (DbConnectionInfo connectionInfo : values) { + Collection values = this.dsMap.values(); + for (DataSource ds : values) { - String url = connectionInfo.getUrl(); - String username = connectionInfo.getUsername(); - String password = connectionInfo.getPassword(); + logger.info("Checking connection " + ds); - logger.info("Checking connection " + username + "@" + url); - - try (Connection con = DriverManager.getConnection(url, username, password); - Statement st = con.createStatement();) { + try (Connection con = ds.getConnection(); Statement st = con.createStatement();) { try (ResultSet rs = st.executeQuery("select version()")) { //$NON-NLS-1$ if (rs.next()) { @@ -67,8 +63,8 @@ public class DbConnectionCheck { } } catch (SQLException e) { - String msg = "Failed to open DB connection to URL {0} due to: {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, url, e.getMessage()); + 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); } } diff --git a/src/main/java/ch/eitchnet/db/DbConnectionInfo.java b/src/main/java/ch/eitchnet/db/DbConnectionInfo.java deleted file mode 100644 index 391bb8d3f..000000000 --- a/src/main/java/ch/eitchnet/db/DbConnectionInfo.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.db; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.text.MessageFormat; - -import ch.eitchnet.utils.dbc.DBC; - -/** - * @author Robert von Burg - */ -public class DbConnectionInfo { - - private String realm; - private String url; - private String username; - private String password; - - public DbConnectionInfo(String realm, String url) { - DBC.PRE.assertNotEmpty("Realm must be set!", realm); //$NON-NLS-1$ - DBC.PRE.assertNotEmpty("Url must be set!", url); //$NON-NLS-1$ - this.realm = realm; - this.url = url; - } - - /** - * @return the realm - */ - public String getRealm() { - return this.realm; - } - - /** - * @param realm - * the realm to set - */ - public void setRealm(String realm) { - this.realm = realm; - } - - /** - * @return the url - */ - public String getUrl() { - return this.url; - } - - /** - * @param url - * the url to set - */ - public void setUrl(String url) { - this.url = url; - } - - /** - * @return the username - */ - public String getUsername() { - return this.username; - } - - /** - * @param username - * the username to set - */ - public void setUsername(String username) { - this.username = username; - } - - /** - * @return the password - */ - public String getPassword() { - return this.password; - } - - /** - * @param password - * the password to set - */ - public void setPassword(String password) { - this.password = password; - } - - @SuppressWarnings("nls") - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("DbConnectionInfo [realm="); - builder.append(this.realm); - builder.append(", url="); - builder.append(this.url); - builder.append(", username="); - builder.append(this.username); - builder.append(", password=***"); - builder.append("]"); - return builder.toString(); - } - - /** - * @return a {@link Connection} - * - * @throws DbException - */ - public Connection openConnection() throws DbException { - try { - Connection connection = DriverManager.getConnection(this.url, this.username, this.password); - connection.setAutoCommit(false); - return connection; - } catch (SQLException e) { - String msg = MessageFormat.format("Failed to get a connection for {0} due to {1}", this, e.getMessage()); //$NON-NLS-1$ - throw new DbException(msg, e); - } - } -} diff --git a/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java b/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java new file mode 100644 index 000000000..eb33af6f1 --- /dev/null +++ b/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.db; + +import java.util.Properties; + +import javax.sql.DataSource; + +/** + * @author Robert von Burg + */ +public interface DbDataSourceBuilder { + + public DataSource build(String realm, String url, String username, String password, Properties properties); +} diff --git a/src/main/java/ch/eitchnet/db/DbDriverLoader.java b/src/main/java/ch/eitchnet/db/DbDriverLoader.java deleted file mode 100644 index a824cce1a..000000000 --- a/src/main/java/ch/eitchnet/db/DbDriverLoader.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.db; - -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.text.MessageFormat; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author Robert von Burg - */ -public class DbDriverLoader { - - private static final Logger logger = LoggerFactory.getLogger(DbDriverLoader.class); - - public static void loadDriverForConnection(DbConnectionInfo connectionInfo) throws DbException { - Driver driver; - try { - driver = DriverManager.getDriver(connectionInfo.getUrl()); - } catch (SQLException e) { - String msg = "Failed to load DB driver for URL {0} due to: {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, connectionInfo.getUrl(), e.getMessage()); - throw new DbException(msg, e); - } - - String compliant = driver.jdbcCompliant() ? "" : "non"; //$NON-NLS-1$ //$NON-NLS-2$ - String msg = "Realm {0}: Using {1} JDBC compliant Driver {2}.{3}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, connectionInfo.getRealm(), compliant, driver.getMajorVersion(), - driver.getMinorVersion()); - logger.info(msg); - } -} diff --git a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java index d4c260219..f4c1efde4 100644 --- a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java +++ b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java @@ -21,7 +21,6 @@ import static ch.eitchnet.db.DbConstants.RESOURCE_DB_VERSION; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -29,8 +28,11 @@ 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; @@ -79,34 +81,34 @@ public class DbSchemaVersionCheck { return this.dbMigrationStates; } - public void checkSchemaVersion(Map connectionInfoMap) throws DbException { - for (DbConnectionInfo connectionInfo : connectionInfoMap.values()) { - DbMigrationState dbMigrationState = checkSchemaVersion(connectionInfo); - dbMigrationStates.put(connectionInfo.getRealm(), dbMigrationState); + public void checkSchemaVersion(Map dsMap) throws DbException { + for (Entry entry : dsMap.entrySet()) { + String realm = entry.getKey(); + DataSource ds = entry.getValue(); + DbMigrationState dbMigrationState = checkSchemaVersion(realm, ds); + 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(DbConnectionInfo connectionInfo) throws DbException { - String realm = connectionInfo.getRealm(); - String url = connectionInfo.getUrl(); - String username = connectionInfo.getUsername(); - String password = connectionInfo.getPassword(); + public DbMigrationState checkSchemaVersion(String realm, DataSource ds) throws DbException { - logger.info(MessageFormat.format("[{0}:{1}] Checking Schema version for: {2}@{3}", this.app, realm, username, - url)); + 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 = DriverManager.getConnection(url, username, password)) { + try (Connection con = ds.getConnection()) { // get current version Version currentVersion = getCurrentVersion(con, this.app); @@ -127,11 +129,12 @@ public class DbSchemaVersionCheck { break; } + con.commit(); return migrationType; } catch (SQLException e) { - String msg = "Failed to open DB connection to URL {0} due to: {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, url, e.getMessage()); + 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); } } From 1689ff69a93f7fcf491400e36b941c905d368018 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 8 May 2015 18:07:06 +0200 Subject: [PATCH 391/457] [New] added new DbConstants.PROP_DB_IGNORE_REALM --- src/main/java/ch/eitchnet/db/DbConstants.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ch/eitchnet/db/DbConstants.java b/src/main/java/ch/eitchnet/db/DbConstants.java index ceee7a85e..edec0cf3a 100644 --- a/src/main/java/ch/eitchnet/db/DbConstants.java +++ b/src/main/java/ch/eitchnet/db/DbConstants.java @@ -21,6 +21,7 @@ package ch.eitchnet.db; 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"; From 0c7315b713edb81442208c2b347c6432a3b6bc70 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 3 Jun 2015 23:25:30 +0200 Subject: [PATCH 392/457] [Major] SystemUserAction is now a normal privilege which is added as follows: ch.eitchnet.privilege.test.model.TestSystemUserAction ch.eitchnet.privilege.test.model.TestSystemUserActionDeny --- config/PrivilegeModel.xml | 14 ++----- .../handler/DefaultPrivilegeHandler.java | 42 ++++--------------- .../privilege/handler/SystemUserAction.java | 15 ++++++- .../privilege/test/PrivilegeTest.java | 8 ++-- .../ch/eitchnet/privilege/test/XmlTest.java | 20 ++++----- .../test/model/TestSystemUserAction.java | 2 +- .../test/model/TestSystemUserActionDeny.java | 2 +- 7 files changed, 40 insertions(+), 63 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 68ef7222f..83131e8bc 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -35,7 +35,6 @@ en_GB system_admin_privileges - system_admin_privileges2 @@ -113,22 +112,17 @@ - - true + + ch.eitchnet.privilege.test.model.TestSystemUserAction + ch.eitchnet.privilege.test.model.TestSystemUserActionDeny true - - - true - - - - + hello goodbye diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 1737181c4..6560c16e1 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1374,8 +1374,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { privilegeNames.put(privilegeName, roleName); } else { String roleOrigin = privilegeNames.get(privilegeName); - String msg = "User has conflicts for privilege {0} on roles {1} and {2}"; - msg = MessageFormat.format(msg, privilegeName, roleOrigin, roleName); + String msg = "User {0} has conflicts for privilege {1} on roles {2} and {3}"; + msg = MessageFormat.format(msg, user.getUsername(), privilegeName, roleOrigin, roleName); conflicts.add(msg); } } @@ -1434,12 +1434,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (systemUser.getUserState() != UserState.SYSTEM) throw new PrivilegeException(MessageFormat.format("User {0} is not a System user!", systemUsername)); //$NON-NLS-1$ - // validate this system user may perform the given action - String actionClassname = action.getClass().getName(); - checkPrivilege(actionClassname, systemUser); - - // get certificate for this system user + // get privilegeContext for this system user PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(systemUsername); + + // validate this system user may perform the given action + systemUserPrivilegeContext.validateAction(action); + String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId(); this.privilegeContextMap.put(sessionId, systemUserPrivilegeContext); try { @@ -1450,34 +1450,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * Checks if the given user has the given privilege - * - * @param privilegeName - * the name of the privilege to check on the user - * @param user - * the user to check for the given privilege - * - * @throws PrivilegeException - * if the user does not have the privilege - */ - private void checkPrivilege(String privilegeName, User user) throws PrivilegeException { - - // check each role if it has the privilege - for (String roleName : user.getRoles()) { - - Role role = this.persistenceHandler.getRole(roleName); - - // on the first occurrence of our privilege, stop - if (role.hasPrivilege(privilegeName)) - return; - } - - // default throw exception, as the user does not have the privilege - String msg = MessageFormat.format("User {0} does not have Privilege {1}", user.getUsername(), privilegeName); //$NON-NLS-1$ - throw new PrivilegeException(msg); - } - /** * Returns the {@link Certificate} for the given system username. If it does not yet exist, then it is created by * authenticating the system user diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java index caf484f8f..082bc2ca3 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -17,6 +17,7 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; /** * With this interface system actions, which are to be performed in an automated fashion, i.e. by cron jobs, can be @@ -25,7 +26,17 @@ import ch.eitchnet.privilege.model.PrivilegeContext; * * @author Robert von Burg */ -public interface SystemUserAction { +public abstract class SystemUserAction implements Restrictable { + + @Override + public String getPrivilegeName() { + return SystemUserAction.class.getName(); + } + + @Override + public Object getPrivilegeValue() { + return this.getClass().getName(); + } /** * This method will be called by the {@link PrivilegeHandler} when an authorized {@link Certificate} has been @@ -37,5 +48,5 @@ public interface SystemUserAction { * @param privilegeContext * the {@link PrivilegeContext} which was generated for a valid system user */ - public void execute(PrivilegeContext privilegeContext); + public abstract void execute(PrivilegeContext privilegeContext); } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 37d159165..3d29e60d7 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -259,7 +259,7 @@ public class PrivilegeTest { public void testPerformSystemRestrictableFailPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); this.exception - .expectMessage("User system_admin does not have Privilege ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); + .expectMessage("User system_admin does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -278,7 +278,7 @@ public class PrivilegeTest { public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); this.exception - .expectMessage("User system_admin2 does not have the privilege ch.eitchnet.privilege.test.model.TestRestrictable"); + .expectMessage("User system_admin2 does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction needed for Restrictable ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -429,7 +429,7 @@ public class PrivilegeTest { @Test public void shouldDetectPrivilegeConflict1() { exception.expect(PrivilegeException.class); - exception.expectMessage("User has conflicts for privilege "); + exception.expectMessage("User admin has conflicts for privilege "); try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); @@ -444,7 +444,7 @@ public class PrivilegeTest { @Test public void shouldDetectPrivilegeConflict2() { exception.expect(PrivilegeException.class); - exception.expectMessage("User has conflicts for privilege "); + exception.expectMessage("User admin has conflicts for privilege "); try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 637c43901..4789374ca 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -170,7 +170,7 @@ public class XmlTest { assertNotNull(roles); assertEquals(3, users.size()); - assertEquals(7, roles.size()); + assertEquals(6, roles.size()); // assert model @@ -249,16 +249,16 @@ public class XmlTest { assertEquals(2, systemAdminPrivileges.getPrivilegeNames().size()); assertThat( systemAdminPrivileges.getPrivilegeNames(), - containsInAnyOrder("ch.eitchnet.privilege.test.model.TestSystemUserAction", + containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction", "ch.eitchnet.privilege.test.model.TestSystemRestrictable")); IPrivilege testSystemUserAction = systemAdminPrivileges - .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemUserAction"); - assertEquals("ch.eitchnet.privilege.test.model.TestSystemUserAction", testSystemUserAction.getName()); + .getPrivilege("ch.eitchnet.privilege.handler.SystemUserAction"); + assertEquals("ch.eitchnet.privilege.handler.SystemUserAction", testSystemUserAction.getName()); assertEquals("DefaultPrivilege", testSystemUserAction.getPolicy()); - assertTrue(testSystemUserAction.isAllAllowed()); - assertEquals(0, testSystemUserAction.getAllowList().size()); - assertEquals(0, testSystemUserAction.getDenyList().size()); + assertFalse(testSystemUserAction.isAllAllowed()); + assertEquals(1, testSystemUserAction.getAllowList().size()); + assertEquals(1, testSystemUserAction.getDenyList().size()); IPrivilege testSystemRestrictable = systemAdminPrivileges .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemRestrictable"); @@ -273,11 +273,11 @@ public class XmlTest { assertEquals("restrictedRole", restrictedRole.getName()); assertEquals(1, restrictedRole.getPrivilegeNames().size()); assertThat(restrictedRole.getPrivilegeNames(), - containsInAnyOrder("ch.eitchnet.privilege.test.model.TestSystemUserAction")); + containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction")); IPrivilege testSystemUserAction2 = restrictedRole - .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemUserAction"); - assertEquals("ch.eitchnet.privilege.test.model.TestSystemUserAction", testSystemUserAction2.getName()); + .getPrivilege("ch.eitchnet.privilege.handler.SystemUserAction"); + assertEquals("ch.eitchnet.privilege.handler.SystemUserAction", testSystemUserAction2.getName()); assertEquals("DefaultPrivilege", testSystemUserAction2.getPolicy()); assertFalse(testSystemUserAction2.isAllAllowed()); assertEquals(1, testSystemUserAction2.getAllowList().size()); diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index 5b64a5421..b3942b7cc 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -22,7 +22,7 @@ import ch.eitchnet.privilege.model.PrivilegeContext; * @author Robert von Burg * */ -public class TestSystemUserAction implements SystemUserAction { +public class TestSystemUserAction extends SystemUserAction { @Override public void execute(PrivilegeContext context) { diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java index 5cd8092f9..51c1574cc 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -22,7 +22,7 @@ import ch.eitchnet.privilege.model.PrivilegeContext; * @author Robert von Burg * */ -public class TestSystemUserActionDeny implements SystemUserAction { +public class TestSystemUserActionDeny extends SystemUserAction { @Override public void execute(PrivilegeContext privilegeContext) { From 9e449e56eedc01ef24b9d4a8a5cbf5c52b013a48 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 3 Jul 2015 15:11:25 +0200 Subject: [PATCH 393/457] [Minor] Added method to write XML to string --- .../ch/eitchnet/utils/helper/XmlHelper.java | 136 +++++++++++++----- 1 file changed, 103 insertions(+), 33 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java index bf618b412..da2f03efc 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -15,10 +15,13 @@ */ 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; @@ -63,7 +66,7 @@ public class XmlHelper { /** * 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$ + public static final String DEFAULT_ENCODING = "UTF-8"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); @@ -113,30 +116,118 @@ public class XmlHelper { } /** - * Writes a {@link Document} to an XML file on the file system + * Writes an {@link Element} to an XML file on the file system * - * @param document - * the {@link Document} to write to 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 writeDocument(Document document, File file) throws RuntimeException { + 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 encoding = document.getInputEncoding(); - if (encoding == null || encoding.isEmpty()) { - XmlHelper.logger.info(MessageFormat.format( - "No encoding passed. Using default encoding {0}", XmlHelper.DEFAULT_ENCODING)); //$NON-NLS-1$ - encoding = XmlHelper.DEFAULT_ENCODING; + String docEncoding = document.getInputEncoding(); + if (docEncoding == null || docEncoding.isEmpty()) { + XmlHelper.logger.info(MessageFormat.format("No encoding passed. Using default encoding {0}", encoding)); //$NON-NLS-1$ + docEncoding = encoding; } if (!lineSep.equals("\n")) { //$NON-NLS-1$ @@ -150,14 +241,13 @@ public class XmlHelper { 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, encoding); + 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 - StreamResult result = new StreamResult(file); Source xmlSource = new DOMSource(document); - transformer.transform(xmlSource, result); + transformer.transform(xmlSource, streamResult); } catch (Exception e) { @@ -169,26 +259,6 @@ public class XmlHelper { } } - /** - * 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 - * @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 writeElement(Element rootElement, File file, String encoding) throws RuntimeException { - - Document document = createDocument(); - document.appendChild(rootElement); - XmlHelper.writeDocument(document, file); - } - /** * Returns a new document instance * From 2e58db83fd35fea958431d7a2e9edae14ff6a20b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 7 Jul 2015 17:53:08 +0200 Subject: [PATCH 394/457] [Major] change DBC to add bad values for better debugging --- src/main/java/ch/eitchnet/utils/dbc/DBC.java | 33 ++++++++--------- .../java/ch/eitchnet/utils/dbc/DBCTest.java | 35 +++++++++++++------ 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/src/main/java/ch/eitchnet/utils/dbc/DBC.java index 68d1e1928..1b43adf76 100644 --- a/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/src/main/java/ch/eitchnet/utils/dbc/DBC.java @@ -17,6 +17,7 @@ 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; @@ -38,8 +39,8 @@ public enum DBC { if (value2 != null && value2.equals(value1)) return; - String ex = "Values are not equal: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); + String ex = "{0}: {1} != {2}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, value1, value2); throw new DbcException(ex); } @@ -50,8 +51,8 @@ public enum DBC { if (value2 != null && !value2.equals(value1)) return; - String ex = "Values are equal: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); + String ex = "{0}: {1} == {2}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, value1, value2); throw new DbcException(ex); } @@ -73,8 +74,8 @@ public enum DBC { public void assertEmpty(String msg, String value) { if (!StringHelper.isEmpty(value)) { - String ex = "Illegal non-empty value: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); + String ex = "{0}: Illegal non-empty value: {1}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, value); throw new DbcException(ex); } } @@ -82,8 +83,8 @@ public enum DBC { public void assertEmpty(String msg, Object[] array) { assertNotNull(msg, array); if (array.length != 0) { - String ex = "Illegal non-empty value: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); + String ex = "{0}: Illegal non-empty value: {1}"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, Arrays.toString(array)); throw new DbcException(ex); } } @@ -91,15 +92,15 @@ public enum DBC { public void assertEmpty(String msg, Collection collection) { assertNotNull(msg, collection); if (!collection.isEmpty()) { - String ex = "Illegal non-empty value: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); + 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 = "Illegal empty value: {0}"; //$NON-NLS-1$ + String ex = "{0}: Illegal empty value"; //$NON-NLS-1$ ex = MessageFormat.format(ex, msg); throw new DbcException(ex); } @@ -108,7 +109,7 @@ public enum DBC { public void assertNotEmpty(String msg, Object[] array) { assertNotNull(msg, array); if (array.length == 0) { - String ex = "Illegal empty value: {0}"; //$NON-NLS-1$ + String ex = "{0}: Illegal empty value"; //$NON-NLS-1$ ex = MessageFormat.format(ex, msg); throw new DbcException(ex); } @@ -117,7 +118,7 @@ public enum DBC { public void assertNotEmpty(String msg, Collection collection) { assertNotNull(msg, collection); if (collection.isEmpty()) { - String ex = "Illegal empty value: {0}"; //$NON-NLS-1$ + String ex = "{0}: Illegal empty value"; //$NON-NLS-1$ ex = MessageFormat.format(ex, msg); throw new DbcException(ex); } @@ -125,7 +126,7 @@ public enum DBC { public void assertNotNull(String msg, Object value) { if (value == null) { - String ex = "Illegal null value: {0}"; //$NON-NLS-1$ + String ex = "{0}: Illegal null value"; //$NON-NLS-1$ ex = MessageFormat.format(ex, msg); throw new DbcException(ex); } @@ -133,8 +134,8 @@ public enum DBC { public void assertNull(String msg, Object value) { if (value != null) { - String ex = "Illegal situation as value is not null: {0}"; //$NON-NLS-1$ - ex = MessageFormat.format(ex, msg); + String ex = "{0}: {1} != null"; //$NON-NLS-1$ + ex = MessageFormat.format(ex, msg, value); throw new DbcException(ex); } } diff --git a/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java b/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java index 5877e46d2..7c18a3ab2 100644 --- a/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java +++ b/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java @@ -16,6 +16,7 @@ package ch.eitchnet.utils.dbc; import java.io.File; +import java.text.MessageFormat; import org.junit.Rule; import org.junit.Test; @@ -64,10 +65,10 @@ public class DBCTest { @Test public void testAssertEquals_2() throws Exception { this.exception.expect(DbcException.class); - this.exception.expectMessage("Values are not equal:"); - String msg = ""; Object value1 = new Object(); Object value2 = new Object(); + String msg = MessageFormat.format("{0}: {1} != {2}", "", value1, value2); + this.exception.expectMessage(msg); DBC.PRE.assertEquals(msg, value1, value2); @@ -87,12 +88,13 @@ public class DBCTest { @Test public void testAssertEquals_3() throws Exception { this.exception.expect(DbcException.class); - this.exception.expectMessage("Values are not equal:"); - String msg = ""; Object value1 = null; Object value2 = new Object(); + String msg = MessageFormat.format("{0}: {1} != {2}", "", value1, value2); + this.exception.expectMessage(msg); + DBC.PRE.assertEquals(msg, value1, value2); // add additional test code here @@ -108,12 +110,13 @@ public class DBCTest { @Test public void testAssertEquals_4() throws Exception { this.exception.expect(DbcException.class); - this.exception.expectMessage("Values are not equal:"); - String msg = ""; Object value1 = new Object(); Object value2 = null; + String msg = MessageFormat.format("{0}: {1} != {2}", "", value1, value2); + this.exception.expectMessage(msg); + DBC.PRE.assertEquals(msg, value1, value2); // add additional test code here @@ -150,12 +153,15 @@ public class DBCTest { @Test public void testAssertNotEquals_1() throws Exception { this.exception.expect(DbcException.class); - this.exception.expectMessage("Values are equal:"); String msg = ""; Object value1 = null; Object value2 = null; + String ex = "{0}: {1} == {2}"; + ex = MessageFormat.format(ex, msg, value1, value2); + this.exception.expectMessage(ex); + DBC.PRE.assertNotEquals(msg, value1, value2); } @@ -217,12 +223,15 @@ public class DBCTest { @Test public void testAssertNotEquals_5() throws Exception { this.exception.expect(DbcException.class); - this.exception.expectMessage("Values are equal:"); String msg = ""; Object value1 = "bla"; Object value2 = "bla"; + String ex = "{0}: {1} == {2}"; + ex = MessageFormat.format(ex, msg, value1, value2); + this.exception.expectMessage(ex); + DBC.PRE.assertNotEquals(msg, value1, value2); } @@ -379,11 +388,14 @@ public class DBCTest { @Test public void testAssertNotNull_1() throws Exception { this.exception.expect(DbcException.class); - this.exception.expectMessage("Illegal null value:"); String msg = ""; Object value = null; + String ex = "{0}: Illegal null value"; + ex = MessageFormat.format(ex, msg, value); + this.exception.expectMessage(ex); + DBC.PRE.assertNotNull(msg, value); } @@ -414,11 +426,12 @@ public class DBCTest { @Test public void testAssertNull_1() throws Exception { this.exception.expect(DbcException.class); - this.exception.expectMessage("Illegal situation as value is not null:"); - String msg = ""; Object value = new Object(); + String msg = MessageFormat.format("{0}: {1} != null", "", value); + this.exception.expectMessage(msg); + DBC.PRE.assertNull(msg, value); } From 6659b90b83aa253e79e9a705c42e9c0e3b3fd63c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 8 Jul 2015 08:00:14 +0200 Subject: [PATCH 395/457] [Major] Removed redundant XmlPersistenceStreamWriter - Now SaxParser simply writes to XMLStreamWriter --- .../java/ch/eitchnet/xmlpers/api/FileIo.java | 3 +- .../ch/eitchnet/xmlpers/api/SaxParser.java | 3 +- .../api/XmlPersistenceStreamWriter.java | 88 ------------------- .../xmlpers/test/impl/BookSaxParser.java | 4 +- .../xmlpers/test/impl/MyModelSaxParser.java | 7 +- 5 files changed, 9 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java b/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java index 23f917e4f..9aaa97e45 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java @@ -77,10 +77,9 @@ public class FileIo { writer.writeStartDocument(DEFAULT_ENCODING, DEFAULT_XML_VERSION); // then delegate object writing to caller - XmlPersistenceStreamWriter xmlWriter = new XmlPersistenceStreamWriter(writer); SaxParser saxParser = ctx.getParserFactor().getSaxParser(); saxParser.setObject(ctx.getObject()); - saxParser.write(xmlWriter); + saxParser.write(writer); // and now end writer.writeEndDocument(); diff --git a/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java b/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java index 82ea59631..352b43f33 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java +++ b/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java @@ -16,6 +16,7 @@ package ch.eitchnet.xmlpers.api; import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import org.xml.sax.helpers.DefaultHandler; @@ -27,5 +28,5 @@ public interface SaxParser { public DefaultHandler getDefaultHandler(); - public void write(XmlPersistenceStreamWriter xmlWriter) throws XMLStreamException; + public void write(XMLStreamWriter xmlWriter) throws XMLStreamException; } \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java b/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java deleted file mode 100644 index 182ad89c9..000000000 --- a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceStreamWriter.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.xmlpers.api; - -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; - -/** - * @author Robert von Burg - * - */ -public class XmlPersistenceStreamWriter { - - private XMLStreamWriter writer; - - public XmlPersistenceStreamWriter(XMLStreamWriter writer) { - this.writer = writer; - } - - /** - * @param localName - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeEmptyElement(java.lang.String) - */ - public void writeEmptyElement(String localName) throws XMLStreamException { - this.writer.writeEmptyElement(localName); - } - - /** - * @param localName - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeStartElement(java.lang.String) - */ - public void writeElement(String localName) throws XMLStreamException { - this.writer.writeStartElement(localName); - } - - /** - * Note: Don't call this method to close an element written by {@link #writeEmptyElement(String)} - * - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeEndElement() - */ - public void endElement() throws XMLStreamException { - this.writer.writeEndElement(); - } - - /** - * @param localName - * @param value - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeAttribute(java.lang.String, java.lang.String) - */ - public void writeAttribute(String localName, String value) throws XMLStreamException { - this.writer.writeAttribute(localName, value); - } - - /** - * @param data - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeCData(java.lang.String) - */ - public void writeCData(String data) throws XMLStreamException { - this.writer.writeCData(data); - } - - /** - * @param text - * @throws XMLStreamException - * @see javax.xml.stream.XMLStreamWriter#writeCharacters(java.lang.String) - */ - public void writeCharacters(String text) throws XMLStreamException { - this.writer.writeCharacters(text); - } -} diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java index 2a9338683..154df1afa 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java @@ -16,13 +16,13 @@ package ch.eitchnet.xmlpers.test.impl; import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; import ch.eitchnet.xmlpers.test.model.Book; /** @@ -51,7 +51,7 @@ public class BookSaxParser extends DefaultHandler implements SaxParser { @SuppressWarnings("nls") @Override - public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { + public void write(XMLStreamWriter writer) throws XMLStreamException { writer.writeEmptyElement("Book"); writer.writeAttribute("id", Long.toString(this.book.getId())); diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java index 858be533a..0cb2f0780 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java +++ b/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java @@ -16,13 +16,13 @@ package ch.eitchnet.xmlpers.test.impl; import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.api.XmlPersistenceStreamWriter; import ch.eitchnet.xmlpers.test.model.MyModel; import ch.eitchnet.xmlpers.test.model.MyParameter; @@ -47,9 +47,9 @@ class MyModelSaxParser extends DefaultHandler implements SaxParser { @SuppressWarnings("nls") @Override - public void write(XmlPersistenceStreamWriter writer) throws XMLStreamException { + public void write(XMLStreamWriter writer) throws XMLStreamException { - writer.writeElement("Resource"); + writer.writeStartElement("Resource"); writer.writeAttribute("id", this.resource.getId()); writer.writeAttribute("name", this.resource.getName()); writer.writeAttribute("type", this.resource.getType()); @@ -61,6 +61,7 @@ class MyModelSaxParser extends DefaultHandler implements SaxParser { writer.writeAttribute("type", param.getType()); writer.writeAttribute("value", param.getValue()); } + writer.writeEndElement(); } @SuppressWarnings("nls") From d9dc5261e7ed349d43a967ef316698c4d2ed3ff9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 9 Jul 2015 19:29:45 +0200 Subject: [PATCH 396/457] [Minor] fixed broken test - the test has changed because default encoding is now UTF-8 instead of utf-8 --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 4789374ca..d72c9264a 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -154,7 +154,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - assertEquals("2abd3442eec8bcec5bee365aab6db2fd4e1789325425cb1e017e900582525685", fileHash); + assertEquals("22d4ba39605d49c758184d9bd63beae5ccf8786f3dabbab45cd9f59c2afbcbd0", fileHash); } @Test From 21d640e081b3a32f7d668d39862014d4b629c1b9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 9 Jul 2015 19:31:46 +0200 Subject: [PATCH 397/457] [Minor] fixed broken test - the test has changed because default encoding is now UTF-8 instead of utf-8 --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index d72c9264a..91bd4a007 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -355,6 +355,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("c9732a05bf0ed53d89b3d12e7c8d7216150b6a91412d1bf47fbe3e6f3be750ff", fileHash); + assertEquals("fd5f4554312b18ca9fd61f0a6a4c87603e7c191ee206afca6328e5bffb87f86c", fileHash); } } From ffbce7aab6caadb8e365065600e4e42cea5e62a0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 9 Jul 2015 19:50:15 +0200 Subject: [PATCH 398/457] [New] Added a CsvParser --- .../java/ch/eitchnet/utils/csv/CsvParser.java | 154 ++++++++++++++++++ .../ch/eitchnet/utils/csv/CsvParserTest.java | 57 +++++++ src/test/resources/test_data.csv | 6 + 3 files changed, 217 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/csv/CsvParser.java create mode 100644 src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java create mode 100644 src/test/resources/test_data.csv diff --git a/src/main/java/ch/eitchnet/utils/csv/CsvParser.java b/src/main/java/ch/eitchnet/utils/csv/CsvParser.java new file mode 100644 index 000000000..e94e09718 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/csv/CsvParser.java @@ -0,0 +1,154 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.csv; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Scanner; + +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.xml.XmlKeyValue; + +/** + * @author Robert von Burg + */ +public class CsvParser { + + private InputStream inputStream; + + /** + * @param inputStream + */ + public CsvParser(InputStream inputStream) { + DBC.PRE.assertNotNull("InputStream may not be null!", inputStream); + this.inputStream = inputStream; + } + + public CsvData parseData() { + + CsvData data = new CsvData(); + + int lineNr = 0; + boolean headerRead = false; + + try { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(this.inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + + line = line.trim(); + if (line.isEmpty()) + continue; + if (line.charAt(0) == '#') + continue; + + try (Scanner scanner = new Scanner(line)) { + scanner.useDelimiter(";"); + if (headerRead) { + int column = 0; + CsvRow row = new CsvRow(lineNr); + while (scanner.hasNext()) { + row.addColumnValue(data.getHeaderAtIndex(column), scanner.next()); + column++; + } + data.addRow(row); + } else { + while (scanner.hasNext()) { + data.addHeader(scanner.next().trim()); + } + headerRead = true; + } + } + + lineNr++; + } + } + } catch (IOException e) { + throw new RuntimeException("Failed to read csv data at line " + lineNr + " due to " + e.getMessage(), e); + } + + return data; + } + + public class CsvData { + private List headers; + private List rows; + + public CsvData() { + this.headers = new ArrayList<>(); + this.rows = new ArrayList<>(); + } + + public String getHeaderAtIndex(int column) { + if (this.headers.size() < column + 1) { + throw new IllegalArgumentException("No header exists at column index " + column); + } + return this.headers.get(column); + } + + public void addHeader(String header) { + this.headers.add(header); + } + + public void addRow(CsvRow row) { + this.rows.add(row); + } + + public List getHeaders() { + return this.headers; + } + + public List getRows() { + return this.rows; + } + } + + public class CsvRow { + private int index; + private List values; + + public CsvRow(int index) { + this.index = index; + this.values = new ArrayList<>(); + } + + public int getIndex() { + return this.index; + } + + public void addColumnValue(String header, String value) { + this.values.add(new XmlKeyValue(header, value)); + } + + public String getColumnValue(String header) { + for (Iterator iter = this.values.iterator(); iter.hasNext();) { + XmlKeyValue next = iter.next(); + if (next.getKey().equals(header)) + return next.getValue(); + } + throw new IllegalArgumentException("No value exists for header" + header); + } + + public List getValues() { + return this.values; + } + } +} diff --git a/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java b/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java new file mode 100644 index 000000000..2a208c512 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.csv; + +import static org.junit.Assert.assertEquals; + +import java.io.InputStream; +import java.util.Arrays; + +import org.junit.Test; + +import ch.eitchnet.utils.csv.CsvParser.CsvData; +import ch.eitchnet.utils.csv.CsvParser.CsvRow; + +/** + * @author Robert von Burg + */ +public class CsvParserTest { + + @Test + public void shouldParseFile() { + + InputStream inputStream = CsvParserTest.class.getResourceAsStream("/test_data.csv"); + + CsvParser csvParser = new CsvParser(inputStream); + CsvData csvData = csvParser.parseData(); + + assertEquals(3, csvData.getHeaders().size()); + assertEquals(Arrays.asList("title", "description", "nrOfPages"), csvData.getHeaders()); + assertEquals(3, csvData.getRows().size()); + + CsvRow row = csvData.getRows().get(0); + assertEquals("A", row.getColumnValue("title")); + assertEquals("a", row.getColumnValue("description")); + assertEquals("1", row.getColumnValue("nrOfPages")); + + row = csvData.getRows().get(2); + assertEquals("C", row.getColumnValue("title")); + assertEquals("c", row.getColumnValue("description")); + assertEquals("3", row.getColumnValue("nrOfPages")); + + assertEquals(3, row.getValues().size()); + } +} diff --git a/src/test/resources/test_data.csv b/src/test/resources/test_data.csv new file mode 100644 index 000000000..d6b101a1e --- /dev/null +++ b/src/test/resources/test_data.csv @@ -0,0 +1,6 @@ +#Comment +title;description;nrOfPages +A;a;1 +# Another comment +B;b;2 +C;c;3 From 35bbb04d89cd6bd60ecf2ff1b49dba38a68f33a2 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 10 Jul 2015 10:47:44 +0200 Subject: [PATCH 399/457] [Minor] Changed exception handling in CsvParser --- src/main/java/ch/eitchnet/utils/csv/CsvParser.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/csv/CsvParser.java b/src/main/java/ch/eitchnet/utils/csv/CsvParser.java index e94e09718..7398b91b2 100644 --- a/src/main/java/ch/eitchnet/utils/csv/CsvParser.java +++ b/src/main/java/ch/eitchnet/utils/csv/CsvParser.java @@ -16,7 +16,6 @@ package ch.eitchnet.utils.csv; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; @@ -53,6 +52,7 @@ public class CsvParser { try (BufferedReader reader = new BufferedReader(new InputStreamReader(this.inputStream))) { String line; while ((line = reader.readLine()) != null) { + lineNr++; line = line.trim(); if (line.isEmpty()) @@ -64,7 +64,7 @@ public class CsvParser { scanner.useDelimiter(";"); if (headerRead) { int column = 0; - CsvRow row = new CsvRow(lineNr); + CsvRow row = new CsvRow(lineNr - 1); while (scanner.hasNext()) { row.addColumnValue(data.getHeaderAtIndex(column), scanner.next()); column++; @@ -77,11 +77,9 @@ public class CsvParser { headerRead = true; } } - - lineNr++; } } - } catch (IOException e) { + } catch (Exception e) { throw new RuntimeException("Failed to read csv data at line " + lineNr + " due to " + e.getMessage(), e); } From a3c2a2ed8f65c5463609f26c2835ce8f7baa5e69 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 10 Jul 2015 12:37:24 +0200 Subject: [PATCH 400/457] [Minor] moved CsvData and CsvRow to their own classes --- .../java/ch/eitchnet/utils/csv/CsvData.java | 52 ++++++++++++++ .../java/ch/eitchnet/utils/csv/CsvParser.java | 68 ------------------- .../java/ch/eitchnet/utils/csv/CsvRow.java | 53 +++++++++++++++ .../ch/eitchnet/utils/csv/CsvParserTest.java | 3 - 4 files changed, 105 insertions(+), 71 deletions(-) create mode 100644 src/main/java/ch/eitchnet/utils/csv/CsvData.java create mode 100644 src/main/java/ch/eitchnet/utils/csv/CsvRow.java diff --git a/src/main/java/ch/eitchnet/utils/csv/CsvData.java b/src/main/java/ch/eitchnet/utils/csv/CsvData.java new file mode 100644 index 000000000..86b7053f0 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/csv/CsvData.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.csv; + +import java.util.ArrayList; +import java.util.List; + +public class CsvData { + private List headers; + private List rows; + + public CsvData() { + this.headers = new ArrayList<>(); + this.rows = new ArrayList<>(); + } + + public String getHeaderAtIndex(int column) { + if (this.headers.size() < column + 1) { + throw new IllegalArgumentException("No header exists at column index " + column); + } + return this.headers.get(column); + } + + public void addHeader(String header) { + this.headers.add(header); + } + + public void addRow(CsvRow row) { + this.rows.add(row); + } + + public List getHeaders() { + return this.headers; + } + + public List getRows() { + return this.rows; + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/utils/csv/CsvParser.java b/src/main/java/ch/eitchnet/utils/csv/CsvParser.java index 7398b91b2..df088f6be 100644 --- a/src/main/java/ch/eitchnet/utils/csv/CsvParser.java +++ b/src/main/java/ch/eitchnet/utils/csv/CsvParser.java @@ -18,13 +18,9 @@ package ch.eitchnet.utils.csv; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; import java.util.Scanner; import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.xml.XmlKeyValue; /** * @author Robert von Burg @@ -85,68 +81,4 @@ public class CsvParser { return data; } - - public class CsvData { - private List headers; - private List rows; - - public CsvData() { - this.headers = new ArrayList<>(); - this.rows = new ArrayList<>(); - } - - public String getHeaderAtIndex(int column) { - if (this.headers.size() < column + 1) { - throw new IllegalArgumentException("No header exists at column index " + column); - } - return this.headers.get(column); - } - - public void addHeader(String header) { - this.headers.add(header); - } - - public void addRow(CsvRow row) { - this.rows.add(row); - } - - public List getHeaders() { - return this.headers; - } - - public List getRows() { - return this.rows; - } - } - - public class CsvRow { - private int index; - private List values; - - public CsvRow(int index) { - this.index = index; - this.values = new ArrayList<>(); - } - - public int getIndex() { - return this.index; - } - - public void addColumnValue(String header, String value) { - this.values.add(new XmlKeyValue(header, value)); - } - - public String getColumnValue(String header) { - for (Iterator iter = this.values.iterator(); iter.hasNext();) { - XmlKeyValue next = iter.next(); - if (next.getKey().equals(header)) - return next.getValue(); - } - throw new IllegalArgumentException("No value exists for header" + header); - } - - public List getValues() { - return this.values; - } - } } diff --git a/src/main/java/ch/eitchnet/utils/csv/CsvRow.java b/src/main/java/ch/eitchnet/utils/csv/CsvRow.java new file mode 100644 index 000000000..e0939ac4f --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/csv/CsvRow.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.csv; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import ch.eitchnet.utils.xml.XmlKeyValue; + +public class CsvRow { + private long index; + private List values; + + public CsvRow(long index) { + this.index = index; + this.values = new ArrayList<>(); + } + + public long getIndex() { + return this.index; + } + + public void addColumnValue(String header, String value) { + this.values.add(new XmlKeyValue(header, value)); + } + + public String getColumnValue(String header) { + for (Iterator iter = this.values.iterator(); iter.hasNext();) { + XmlKeyValue next = iter.next(); + if (next.getKey().equals(header)) + return next.getValue(); + } + throw new IllegalArgumentException("No value exists for header" + header); + } + + public List getValues() { + return this.values; + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java b/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java index 2a208c512..60d60efb6 100644 --- a/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java +++ b/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java @@ -22,9 +22,6 @@ import java.util.Arrays; import org.junit.Test; -import ch.eitchnet.utils.csv.CsvParser.CsvData; -import ch.eitchnet.utils.csv.CsvParser.CsvRow; - /** * @author Robert von Burg */ From 00a1fd4f72fc83b7f1603b1ddca8df376ed0f927 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 14 Jul 2015 09:15:40 +0200 Subject: [PATCH 401/457] [Major] removed CsvParser as the apache-commons-csv parser is better --- .../java/ch/eitchnet/utils/csv/CsvData.java | 52 ------------ .../java/ch/eitchnet/utils/csv/CsvParser.java | 84 ------------------- .../java/ch/eitchnet/utils/csv/CsvRow.java | 53 ------------ .../ch/eitchnet/utils/csv/CsvParserTest.java | 54 ------------ 4 files changed, 243 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/utils/csv/CsvData.java delete mode 100644 src/main/java/ch/eitchnet/utils/csv/CsvParser.java delete mode 100644 src/main/java/ch/eitchnet/utils/csv/CsvRow.java delete mode 100644 src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java diff --git a/src/main/java/ch/eitchnet/utils/csv/CsvData.java b/src/main/java/ch/eitchnet/utils/csv/CsvData.java deleted file mode 100644 index 86b7053f0..000000000 --- a/src/main/java/ch/eitchnet/utils/csv/CsvData.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.csv; - -import java.util.ArrayList; -import java.util.List; - -public class CsvData { - private List headers; - private List rows; - - public CsvData() { - this.headers = new ArrayList<>(); - this.rows = new ArrayList<>(); - } - - public String getHeaderAtIndex(int column) { - if (this.headers.size() < column + 1) { - throw new IllegalArgumentException("No header exists at column index " + column); - } - return this.headers.get(column); - } - - public void addHeader(String header) { - this.headers.add(header); - } - - public void addRow(CsvRow row) { - this.rows.add(row); - } - - public List getHeaders() { - return this.headers; - } - - public List getRows() { - return this.rows; - } -} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/utils/csv/CsvParser.java b/src/main/java/ch/eitchnet/utils/csv/CsvParser.java deleted file mode 100644 index df088f6be..000000000 --- a/src/main/java/ch/eitchnet/utils/csv/CsvParser.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.csv; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Scanner; - -import ch.eitchnet.utils.dbc.DBC; - -/** - * @author Robert von Burg - */ -public class CsvParser { - - private InputStream inputStream; - - /** - * @param inputStream - */ - public CsvParser(InputStream inputStream) { - DBC.PRE.assertNotNull("InputStream may not be null!", inputStream); - this.inputStream = inputStream; - } - - public CsvData parseData() { - - CsvData data = new CsvData(); - - int lineNr = 0; - boolean headerRead = false; - - try { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(this.inputStream))) { - String line; - while ((line = reader.readLine()) != null) { - lineNr++; - - line = line.trim(); - if (line.isEmpty()) - continue; - if (line.charAt(0) == '#') - continue; - - try (Scanner scanner = new Scanner(line)) { - scanner.useDelimiter(";"); - if (headerRead) { - int column = 0; - CsvRow row = new CsvRow(lineNr - 1); - while (scanner.hasNext()) { - row.addColumnValue(data.getHeaderAtIndex(column), scanner.next()); - column++; - } - data.addRow(row); - } else { - while (scanner.hasNext()) { - data.addHeader(scanner.next().trim()); - } - headerRead = true; - } - } - } - } - } catch (Exception e) { - throw new RuntimeException("Failed to read csv data at line " + lineNr + " due to " + e.getMessage(), e); - } - - return data; - } -} diff --git a/src/main/java/ch/eitchnet/utils/csv/CsvRow.java b/src/main/java/ch/eitchnet/utils/csv/CsvRow.java deleted file mode 100644 index e0939ac4f..000000000 --- a/src/main/java/ch/eitchnet/utils/csv/CsvRow.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.csv; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import ch.eitchnet.utils.xml.XmlKeyValue; - -public class CsvRow { - private long index; - private List values; - - public CsvRow(long index) { - this.index = index; - this.values = new ArrayList<>(); - } - - public long getIndex() { - return this.index; - } - - public void addColumnValue(String header, String value) { - this.values.add(new XmlKeyValue(header, value)); - } - - public String getColumnValue(String header) { - for (Iterator iter = this.values.iterator(); iter.hasNext();) { - XmlKeyValue next = iter.next(); - if (next.getKey().equals(header)) - return next.getValue(); - } - throw new IllegalArgumentException("No value exists for header" + header); - } - - public List getValues() { - return this.values; - } -} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java b/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java deleted file mode 100644 index 60d60efb6..000000000 --- a/src/test/java/ch/eitchnet/utils/csv/CsvParserTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2015 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.csv; - -import static org.junit.Assert.assertEquals; - -import java.io.InputStream; -import java.util.Arrays; - -import org.junit.Test; - -/** - * @author Robert von Burg - */ -public class CsvParserTest { - - @Test - public void shouldParseFile() { - - InputStream inputStream = CsvParserTest.class.getResourceAsStream("/test_data.csv"); - - CsvParser csvParser = new CsvParser(inputStream); - CsvData csvData = csvParser.parseData(); - - assertEquals(3, csvData.getHeaders().size()); - assertEquals(Arrays.asList("title", "description", "nrOfPages"), csvData.getHeaders()); - assertEquals(3, csvData.getRows().size()); - - CsvRow row = csvData.getRows().get(0); - assertEquals("A", row.getColumnValue("title")); - assertEquals("a", row.getColumnValue("description")); - assertEquals("1", row.getColumnValue("nrOfPages")); - - row = csvData.getRows().get(2); - assertEquals("C", row.getColumnValue("title")); - assertEquals("c", row.getColumnValue("description")); - assertEquals("3", row.getColumnValue("nrOfPages")); - - assertEquals(3, row.getValues().size()); - } -} From 01c9da446cc46e2130131e04a830a0dac4e1b6c4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 15 Jul 2015 07:52:25 +0200 Subject: [PATCH 402/457] [New] Added Paging Paging is used to page a list, i.e. return a sublist which has a certain size. --- .../ch/eitchnet/utils/collections/Paging.java | 111 ++++++++++++++++++ .../utils/collections/PagingTest.java | 78 ++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/collections/Paging.java create mode 100644 src/test/java/ch/eitchnet/utils/collections/PagingTest.java diff --git a/src/main/java/ch/eitchnet/utils/collections/Paging.java b/src/main/java/ch/eitchnet/utils/collections/Paging.java new file mode 100644 index 000000000..a7e906677 --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/collections/Paging.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.collections; + +import java.util.List; + +/** + * @author Robert von Burg + */ +public class Paging { + + private int resultsPerPage; + private int indexOfPageToReturn; + private int nrOfPages; + + private List input; + private List page; + + private Paging(int resultsPerPage, int indexOfPageToReturn) { + this.resultsPerPage = resultsPerPage; + this.indexOfPageToReturn = indexOfPageToReturn; + } + + public int getResultsPerPage() { + return this.resultsPerPage; + } + + public void setResultsPerPage(int resultsPerPage) { + this.resultsPerPage = resultsPerPage; + } + + public int getIndexOfPageToReturn() { + return this.indexOfPageToReturn; + } + + public void setIndexOfPageToReturn(int indexOfPageToReturn) { + this.indexOfPageToReturn = indexOfPageToReturn; + } + + public int getNrOfPages() { + return this.nrOfPages; + } + + public void setNrOfPages(int nrOfPages) { + this.nrOfPages = nrOfPages; + } + + public List getInput() { + return this.input; + } + + public List getPage() { + return this.page; + } + + /** + * Creates a sub list of the given list by creating defining start and end from the requested page of the form + * + * @param list + * the list to paginate + * @param resultsPerPage + * The number of items to return in each page + * @param page + * the page to return - start index is 1 + * + * @return a {@link Paging} instance from which the selected page (list) can be retrieved + */ + public static Paging asPage(List list, int resultsPerPage, int page) { + + Paging paging = new Paging(resultsPerPage, page); + + if (paging.resultsPerPage < 0 || paging.indexOfPageToReturn < 0) { + paging.nrOfPages = -1; + paging.input = list; + paging.page = list; + return paging; + } + + int size = list.size(); + + // calculate maximum number of pages + paging.nrOfPages = size / paging.resultsPerPage; + if (size % paging.resultsPerPage != 0) + paging.nrOfPages++; + + // and from this validate requested page + paging.indexOfPageToReturn = Math.min(paging.indexOfPageToReturn, paging.nrOfPages); + + // now we can calculate the start and end of the page + int start = Math.max(0, paging.resultsPerPage * paging.indexOfPageToReturn - paging.resultsPerPage); + int end = Math.min(size, paging.resultsPerPage * paging.indexOfPageToReturn); + + // and return the list + paging.page = list.subList(start, end); + + return paging; + } +} diff --git a/src/test/java/ch/eitchnet/utils/collections/PagingTest.java b/src/test/java/ch/eitchnet/utils/collections/PagingTest.java new file mode 100644 index 000000000..12e4a7036 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/collections/PagingTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.collections; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +/** + * @author Robert von Burg + */ +public class PagingTest { + + @Test + public void shouldReturnAll() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, -1, -1).getPage(); + assertEquals(list, page); + } + + @Test + public void shouldReturnFirstPage1() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 1, 1).getPage(); + assertEquals(Arrays.asList("a"), page); + } + + @Test + public void shouldReturnFirstPage2() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 2, 1).getPage(); + assertEquals(Arrays.asList("a", "b"), page); + } + + @Test + public void shouldReturnSecondPage1() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 1, 2).getPage(); + assertEquals(Arrays.asList("b"), page); + } + + @Test + public void shouldReturnSecondPage2() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 2, 2).getPage(); + assertEquals(Arrays.asList("c", "d"), page); + } + + @Test + public void shouldReturnLastPage1() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 1, 6).getPage(); + assertEquals(Arrays.asList("f"), page); + } + + @Test + public void shouldReturnLastPage2() { + List list = Arrays.asList("a", "b", "c", "d", "e", "f"); + List page = Paging.asPage(list, 2, 3).getPage(); + assertEquals(Arrays.asList("e", "f"), page); + } +} From 2b9d09632ccd2802a89541a03d0eb5f9c9b81b40 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 15 Jul 2015 10:49:47 +0200 Subject: [PATCH 403/457] [Major] refactorings of Paging --- .../ch/eitchnet/utils/collections/Paging.java | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/collections/Paging.java b/src/main/java/ch/eitchnet/utils/collections/Paging.java index a7e906677..a649217a7 100644 --- a/src/main/java/ch/eitchnet/utils/collections/Paging.java +++ b/src/main/java/ch/eitchnet/utils/collections/Paging.java @@ -22,32 +22,33 @@ import java.util.List; */ public class Paging { - private int resultsPerPage; - private int indexOfPageToReturn; + private int pageSize; + private int pageToReturn; private int nrOfPages; + private int nrOfElements; private List input; private List page; - private Paging(int resultsPerPage, int indexOfPageToReturn) { - this.resultsPerPage = resultsPerPage; - this.indexOfPageToReturn = indexOfPageToReturn; + private Paging(int pageSize, int indexOfPageToReturn) { + this.pageSize = pageSize; + this.pageToReturn = indexOfPageToReturn; } - public int getResultsPerPage() { - return this.resultsPerPage; + public int getPageSize() { + return this.pageSize; } - public void setResultsPerPage(int resultsPerPage) { - this.resultsPerPage = resultsPerPage; + public void setPageSize(int pageSize) { + this.pageSize = pageSize; } - public int getIndexOfPageToReturn() { - return this.indexOfPageToReturn; + public int getPageToReturn() { + return this.pageToReturn; } - public void setIndexOfPageToReturn(int indexOfPageToReturn) { - this.indexOfPageToReturn = indexOfPageToReturn; + public void setPageToReturn(int pageToReturn) { + this.pageToReturn = pageToReturn; } public int getNrOfPages() { @@ -58,6 +59,14 @@ public class Paging { this.nrOfPages = nrOfPages; } + public int getNrOfElements() { + return this.nrOfElements; + } + + public void setNrOfElements(int nrOfElements) { + this.nrOfElements = nrOfElements; + } + public List getInput() { return this.input; } @@ -71,19 +80,22 @@ public class Paging { * * @param list * the list to paginate - * @param resultsPerPage + * @param pageSize * The number of items to return in each page * @param page * the page to return - start index is 1 * * @return a {@link Paging} instance from which the selected page (list) can be retrieved */ - public static Paging asPage(List list, int resultsPerPage, int page) { + public static Paging asPage(List list, int pageSize, int page) { - Paging paging = new Paging(resultsPerPage, page); + Paging paging = new Paging(pageSize, page); + paging.nrOfElements = list.size(); - if (paging.resultsPerPage < 0 || paging.indexOfPageToReturn < 0) { - paging.nrOfPages = -1; + if (paging.pageSize <= 0 || paging.pageToReturn <= 0) { + paging.nrOfPages = 1; + paging.pageSize = list.size(); + paging.pageToReturn = 1; paging.input = list; paging.page = list; return paging; @@ -92,16 +104,16 @@ public class Paging { int size = list.size(); // calculate maximum number of pages - paging.nrOfPages = size / paging.resultsPerPage; - if (size % paging.resultsPerPage != 0) + paging.nrOfPages = size / paging.pageSize; + if (size % paging.pageSize != 0) paging.nrOfPages++; // and from this validate requested page - paging.indexOfPageToReturn = Math.min(paging.indexOfPageToReturn, paging.nrOfPages); + 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.resultsPerPage * paging.indexOfPageToReturn - paging.resultsPerPage); - int end = Math.min(size, paging.resultsPerPage * paging.indexOfPageToReturn); + 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); From 2d922df5724ca10d521e768df65d389677b28af6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 15 Jul 2015 10:50:12 +0200 Subject: [PATCH 404/457] [New] Added new ExceptionHelper - moved exception helper methods from StringHelper to ExceptionHelper --- .../utils/helper/ExceptionHelper.java | 87 +++++++++++++++++++ .../eitchnet/utils/helper/StringHelper.java | 54 ++---------- 2 files changed, 93 insertions(+), 48 deletions(-) create mode 100644 src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java b/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java new file mode 100644 index 000000000..cfe5166dd --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.helper; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * @author Robert von Burg + */ +public class ExceptionHelper { + + /** + *

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

        + * + *

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

        + * + * @param t + * @return + */ + public static String getExceptionMessage(Throwable t) { + return StringHelper.isEmpty(t.getMessage()) ? t.getClass().getName() : t.getMessage(); + } + + /** + * 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) { + StringBuilder sb = new StringBuilder(); + sb.append(t.getMessage()); + appendCause(sb, t); + return sb.toString(); + } + + private static void appendCause(StringBuilder sb, Throwable e) { + Throwable cause = e.getCause(); + if (cause == null) + return; + + sb.append("\n"); + + sb.append("cause:\n"); + sb.append(cause.getMessage()); + + if (cause.getCause() != null) + appendCause(sb, cause.getCause()); + } + +} diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index b0adc8e75..ecd20775c 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -15,8 +15,6 @@ */ package ch.eitchnet.utils.helper; -import java.io.PrintWriter; -import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -564,64 +562,24 @@ public class StringHelper { } /** - *

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

        - * - *

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

        - * - * @param t - * @return + * @see ExceptionHelper#formatException(Throwable) */ public static String getExceptionMessage(Throwable t) { - return StringHelper.isEmpty(t.getMessage()) ? t.getClass().getName() : t.getMessage(); + return ExceptionHelper.getExceptionMessage(t); } /** - * 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 + * @see ExceptionHelper#formatException(Throwable) */ public static String formatException(Throwable t) { - StringWriter stringWriter = new StringWriter(); - PrintWriter writer = new PrintWriter(stringWriter); - t.printStackTrace(writer); - return stringWriter.toString(); + return ExceptionHelper.formatException(t); } /** - * 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 + * @see ExceptionHelper#formatExceptionMessage(Throwable) */ public static String formatExceptionMessage(Throwable t) { - StringBuilder sb = new StringBuilder(); - sb.append(t.getMessage()); - appendCause(sb, t); - return sb.toString(); - } - - private static void appendCause(StringBuilder sb, Throwable e) { - Throwable cause = e.getCause(); - if (cause == null) - return; - - sb.append("\n"); - - sb.append("cause:\n"); - sb.append(cause.getMessage()); - - if (cause.getCause() != null) - appendCause(sb, cause.getCause()); + return ExceptionHelper.formatExceptionMessage(t); } /** From f5cf3e3ad0a4865ec75ffb4e58b03557b4d1db2e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 10 Aug 2015 22:20:40 +0200 Subject: [PATCH 405/457] [New] Added a DefaultedHashMap for default values on unmapped keys --- .../utils/collections/DefaultedHashMap.java | 43 ++++++++++++++++ .../collections/DefaultedHashMapTest.java | 51 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java create mode 100644 src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java diff --git a/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java b/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java new file mode 100644 index 000000000..10ef2d75b --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.collections; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Robert von Burg + */ +public class DefaultedHashMap extends HashMap { + + private static final long serialVersionUID = 1L; + private V defaultValue; + + /** + * Constructs this {@link Map} instance to have a default value on inexistent keys + * + * @param defaultValue + * the default to return if a key is not mapped + */ + public DefaultedHashMap(V defaultValue) { + this.defaultValue = defaultValue; + } + + @Override + public V get(Object key) { + return getOrDefault(key, this.defaultValue); + } +} diff --git a/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java b/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java new file mode 100644 index 000000000..ddf7c6569 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.utils.collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +/** + * @author Robert von Burg + */ +public class DefaultedHashMapTest { + + private Map map; + + @Before + public void setUp() { + this.map = new DefaultedHashMap("foobar"); + this.map.put("foo", "foofoo"); + } + + @Test + public void shouldReturnMappedValue() { + assertTrue(this.map.containsKey("foo")); + assertEquals("foofoo", this.map.get("foo")); + } + + @Test + public void shouldReturnDefaultValue() { + assertFalse(this.map.containsKey("bar")); + assertEquals("foobar", this.map.get("bar")); + } +} From 65992ce0ebaf90ea302f5c1ee63b60903664135c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 11 Aug 2015 13:44:56 +0200 Subject: [PATCH 406/457] [Minor] changed logger for logged in system user --- .../eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 6560c16e1..db9d7ce96 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1503,8 +1503,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); // log - String msg = "The system user {0} is logged in with session {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, systemUsername, systemUserCertificate); + String msg = "The system user ''{0}'' is logged in with session {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, systemUsername, systemUserCertificate.getSessionId()); DefaultPrivilegeHandler.logger.info(msg); return privilegeContext; From 4b9e166025c85df5c427ed1e15bc3a1d98ff9252 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 12 Aug 2015 20:40:57 +0200 Subject: [PATCH 407/457] [Minor] fixed Paging so it fits expected result for jQuery DataTable --- src/main/java/ch/eitchnet/utils/collections/Paging.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/collections/Paging.java b/src/main/java/ch/eitchnet/utils/collections/Paging.java index a649217a7..7031c34e4 100644 --- a/src/main/java/ch/eitchnet/utils/collections/Paging.java +++ b/src/main/java/ch/eitchnet/utils/collections/Paging.java @@ -93,9 +93,9 @@ public class Paging { paging.nrOfElements = list.size(); if (paging.pageSize <= 0 || paging.pageToReturn <= 0) { - paging.nrOfPages = 1; + paging.nrOfPages = 0; paging.pageSize = list.size(); - paging.pageToReturn = 1; + paging.pageToReturn = 0; paging.input = list; paging.page = list; return paging; @@ -118,6 +118,10 @@ public class Paging { // 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; } } From 363c21d30a9631d94adf5a4b508f2ddb8494ec27 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 23 Aug 2015 16:28:24 +0200 Subject: [PATCH 408/457] [Minor] code cleanup --- src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java index f4c1efde4..d621a4c58 100644 --- a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java +++ b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java @@ -86,7 +86,7 @@ public class DbSchemaVersionCheck { String realm = entry.getKey(); DataSource ds = entry.getValue(); DbMigrationState dbMigrationState = checkSchemaVersion(realm, ds); - dbMigrationStates.put(realm, dbMigrationState); + this.dbMigrationStates.put(realm, dbMigrationState); } } @@ -248,7 +248,8 @@ public class DbSchemaVersionCheck { 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); + 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, From bf15669ef25701e5f75174bf96eb99bed32a88fc Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 1 Sep 2015 19:20:22 +0200 Subject: [PATCH 409/457] [New] Added new Privileges for enforcing same organisation access --- ...erAccessWithSameOrganisationPrivilege.java | 115 ++++++++++++++++++ ...tificateWithSameOrganisationPrivilege.java | 79 ++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java create mode 100644 src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java new file mode 100644 index 000000000..599c4a7c3 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.collections.Tuple; +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * Validates that any access to a privilege User is done only by users in the same organisation + * + * @author Robert von Burg + */ +public class UserAccessWithSameOrganisationPrivilege extends UserAccessPrivilege { + + private static final String PARAM_ORGANISATION = "organisation"; + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + String privilegeName = PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Tuple)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nontuple"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // get user organisation + String userOrg = ctx.getCertificate().getProperty(PARAM_ORGANISATION); + if (StringHelper.isEmpty(userOrg)) { + throw new AccessDeniedException("No organisation configured for user " + ctx.getUsername()); + } + + Tuple tuple = (Tuple) object; + + switch (privilegeName) { + case PrivilegeHandler.PRIVILEGE_GET_USER: + case PrivilegeHandler.PRIVILEGE_ADD_USER: + case PrivilegeHandler.PRIVILEGE_MODIFY_USER: + case PrivilegeHandler.PRIVILEGE_REMOVE_USER: { + + // make sure old user has same organisation + User oldUser = tuple.getFirst(); + if (oldUser != null) { + String oldOrg = oldUser.getProperty(PARAM_ORGANISATION); + if (!userOrg.equals(oldOrg)) { + throw new AccessDeniedException("User " + ctx.getUsername() + + " may not access users outside of their organisation: " + userOrg + " / " + oldOrg); + } + } + + // make sure new user has same organisation + User newUser = tuple.getSecond(); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + String newdOrg = newUser.getProperty(PARAM_ORGANISATION); + if (!userOrg.equals(newdOrg)) { + throw new AccessDeniedException("User " + ctx.getUsername() + + " may not access users outside of their organisations: " + userOrg + " / " + newdOrg); + } + + break; + } + case PrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER: + case PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER: { + + User user = tuple.getFirst(); + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", user); + String org = user.getProperty(PARAM_ORGANISATION); + if (!userOrg.equals(org)) { + throw new AccessDeniedException("User " + ctx.getUsername() + + " may not access users outside of their organisation: " + userOrg + " / " + org); + } + + break; + } + + default: + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.userAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName); + throw new PrivilegeException(msg); + } + + // now delegate the rest of the validation to the super class + super.validateAction(ctx, privilege, restrictable); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java new file mode 100644 index 000000000..124addf28 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

        + * This {@link PrivilegePolicy} expects a {@link Certificate} as {@link Restrictable#getPrivilegeValue()} and uses the + * basic Allow and Deny to detect if the username of the certificate is allowed. + *

        + * + *

        + * The {@link Certificate} as privilegeValue is not to be confused with the {@link Certificate} of the current user. + * This certificate is of the user to which access is request, i.e. modifying the session of a logged in user. + *

        + * + * @author Robert von Burg + */ +public class UsernameFromCertificateWithSameOrganisationPrivilege extends UsernameFromCertificatePrivilege { + + private static final String PARAM_ORGANISATION = "organisation"; + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Certificate)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.noncertificate"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // get object + Certificate cert = (Certificate) object; + + // get user organisation + String userOrg = ctx.getCertificate().getProperty(PARAM_ORGANISATION); + if (StringHelper.isEmpty(userOrg)) { + throw new AccessDeniedException("No organisation configured for user " + ctx.getUsername()); + } + // assert same organisation + String org = cert.getProperty(PARAM_ORGANISATION); + if (!userOrg.equals(org)) { + throw new AccessDeniedException("User " + ctx.getUsername() + + " may not access users outside of their organisation: " + userOrg + " / " + org); + } + + // now delegate the rest of the validation to the super class + super.validateAction(ctx, privilege, restrictable); + } +} From 67d1052fd3d6e38a8761596b6a27d509fd4318e1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 1 Oct 2015 07:59:07 +0200 Subject: [PATCH 410/457] [New] Added StringHelper.valueOrDash --- .../java/ch/eitchnet/utils/helper/StringHelper.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index ecd20775c..8f82b3487 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -657,6 +657,19 @@ public class StringHelper { 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 * From f59f4c5c0fd4201e9e5a81ed26f853e99226725c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 2 Oct 2015 08:19:07 +0200 Subject: [PATCH 411/457] [New] Added ExceptionHelper.getExceptionMessageWithCauses() and tests --- .../utils/helper/ExceptionHelper.java | 49 ++++++++++++------- .../utils/helper/ExceptionHelperTest.java | 46 +++++++++++++++++ 2 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java b/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java index cfe5166dd..916489e71 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java @@ -40,6 +40,29 @@ public class ExceptionHelper { return StringHelper.isEmpty(t.getMessage()) ? t.getClass().getName() : t.getMessage(); } + /** + *

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

        + * + *

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

        + * + * @param t + * @return + */ + public static String getExceptionMessageWithCauses(Throwable t) { + + if (t.getCause() == null) { + return getExceptionMessage(t); + } + + String root = getExceptionMessageWithCauses(t.getCause()); + return getExceptionMessage(t) + "\n" + root; + } + /** * Formats the given {@link Throwable}'s stack trace to a string * @@ -64,24 +87,12 @@ public class ExceptionHelper { * @return a string representation of the given {@link Throwable}'s messages including causes */ public static String formatExceptionMessage(Throwable t) { - StringBuilder sb = new StringBuilder(); - sb.append(t.getMessage()); - appendCause(sb, t); - return sb.toString(); + + if (t.getCause() == null) { + return getExceptionMessage(t); + } + + String root = formatExceptionMessage(t.getCause()); + return getExceptionMessage(t) + "\ncause:\n" + root; } - - private static void appendCause(StringBuilder sb, Throwable e) { - Throwable cause = e.getCause(); - if (cause == null) - return; - - sb.append("\n"); - - sb.append("cause:\n"); - sb.append(cause.getMessage()); - - if (cause.getCause() != null) - appendCause(sb, cause.getCause()); - } - } diff --git a/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java b/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java new file mode 100644 index 000000000..eba21340e --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java @@ -0,0 +1,46 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class ExceptionHelperTest { + + @Test + public void shouldGetExceptionMsg() { + + Exception e = nestedException(); + assertEquals("Third", ExceptionHelper.getExceptionMessage(e)); + assertEquals("Third\nSecond\nFirst", ExceptionHelper.getExceptionMessageWithCauses(e)); + } + + @Test + public void shouldFormatException() { + + Exception e = nestedException(); + String formatException = ExceptionHelper.formatException(e); + assertTrue(formatException.contains("java.lang.RuntimeException: First")); + assertTrue(formatException.contains("java.lang.RuntimeException: Second")); + assertTrue(formatException.contains("java.lang.RuntimeException: Third")); + + formatException = ExceptionHelper.formatExceptionMessage(e); + assertEquals("Third\ncause:\nSecond\ncause:\nFirst", formatException); + } + + private Exception nestedException() { + try { + try { + try { + throw new RuntimeException("First"); + } catch (Exception e) { + throw new RuntimeException("Second", e); + } + } catch (Exception e) { + throw new RuntimeException("Third", e); + } + } catch (Exception e) { + return e; + } + } +} From 471cc1f37fa954883fac5c61a46050083b671bed Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Oct 2015 12:26:31 +0200 Subject: [PATCH 412/457] [New] added getter for EncryptionHandler and return SystemUserAction --- .../handler/DefaultPrivilegeHandler.java | 34 ++++++++------- .../privilege/handler/PrivilegeHandler.java | 42 ++++++++++++------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index db9d7ce96..dcd77042e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -72,8 +72,6 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class DefaultPrivilegeHandler implements PrivilegeHandler { - /// - /** * slf4j logger */ @@ -111,6 +109,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private PrivilegeConflictResolution privilegeConflictResolution; + @Override + public EncryptionHandler getEncryptionHandler() throws PrivilegeException { + return this.encryptionHandler; + } + @Override public RoleRep getRole(Certificate certificate, String roleName) { @@ -487,8 +490,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, - PrivilegeException { + public UserRep updateUser(Certificate certificate, UserRep userRep) + throws AccessDeniedException, PrivilegeException { // validate user actually has this type of privilege PrivilegeContext prvCtx = getPrivilegeContext(certificate); @@ -504,8 +507,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (StringHelper.isEmpty(userRep.getFirstname()) && StringHelper.isEmpty(userRep.getLastname()) && userRep.getLocale() == null && (userRep.getProperties() == null || userRep.getProperties().isEmpty())) { - throw new PrivilegeException(MessageFormat.format( - "All updateable fields are empty for update of user {0}", userRep.getUsername())); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format("All updateable fields are empty for update of user {0}", //$NON-NLS-1$ + userRep.getUsername())); } String userId = existingUser.getUserId(); @@ -626,7 +629,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // validate that this user may remove this role from this user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE_FROM_USER, new Tuple(existingUser, roleName))); + prvCtx.validateAction( + new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE_FROM_USER, new Tuple(existingUser, roleName))); // ignore if user does not have role Set currentRoles = existingUser.getRoles(); @@ -713,8 +717,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // if the user is not setting their own password, then make sure this user may set this user's password if (!certificate.getUsername().equals(username)) { - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_PASSWORD, new Tuple(existingUser, - newUser))); + prvCtx.validateAction( + new SimpleRestrictable(PRIVILEGE_SET_USER_PASSWORD, new Tuple(existingUser, newUser))); } // delegate user replacement to persistence handler @@ -987,8 +991,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.privilegeContextMap.put(sessionId, privilegeContext); // log - DefaultPrivilegeHandler.logger.info(MessageFormat.format( - "User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$ + DefaultPrivilegeHandler.logger + .info(MessageFormat.format("User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$ } catch (RuntimeException e) { String msg = "User {0} Failed to authenticate: {1}"; //$NON-NLS-1$ @@ -1042,8 +1046,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate password String pwHash = user.getPassword(); if (pwHash == null) - throw new AccessDeniedException(MessageFormat.format( - "User {0} has no password and may not login!", username)); //$NON-NLS-1$ + throw new AccessDeniedException( + MessageFormat.format("User {0} has no password and may not login!", username)); //$NON-NLS-1$ if (!pwHash.equals(passwordHash)) throw new AccessDeniedException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ @@ -1418,7 +1422,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { + public T runAsSystem(String systemUsername, T action) throws PrivilegeException { if (systemUsername == null) throw new PrivilegeException("systemUsername may not be null!"); //$NON-NLS-1$ @@ -1448,6 +1452,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } finally { this.privilegeContextMap.remove(sessionId); } + + return action; } /** diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 4e6b848d9..b6e0f8856 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -223,8 +223,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public UserRep removeUser(Certificate certificate, String username) throws AccessDeniedException, - PrivilegeException; + public UserRep removeUser(Certificate certificate, String username) + throws AccessDeniedException, PrivilegeException; /** * Removes the role with the given roleName from the user with the given username @@ -259,8 +259,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or the role is still in use by a user */ - public RoleRep removeRole(Certificate certificate, String roleName) throws AccessDeniedException, - PrivilegeException; + public RoleRep removeRole(Certificate certificate, String roleName) + throws AccessDeniedException, PrivilegeException; /** * Removes the privilege with the given privilegeName from the role with the given roleName @@ -304,8 +304,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or the user already exists */ - public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, - PrivilegeException; + public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) + throws AccessDeniedException, PrivilegeException; /** *

        @@ -336,8 +336,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the user does not exist */ - public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, - PrivilegeException; + public UserRep updateUser(Certificate certificate, UserRep userRep) + throws AccessDeniedException, PrivilegeException; /** *

        @@ -363,8 +363,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the user does not exist */ - public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, - PrivilegeException; + public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) + throws AccessDeniedException, PrivilegeException; /** * Adds a new role with the information from this {@link RoleRep} @@ -394,8 +394,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the role does not exist */ - public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, - PrivilegeException; + public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) + throws AccessDeniedException, PrivilegeException; /** * Adds the role with the given roleName to the {@link User} with the given username @@ -494,8 +494,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public UserRep setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, - PrivilegeException; + public UserRep setUserLocale(Certificate certificate, String username, Locale locale) + throws AccessDeniedException, PrivilegeException; /** * Authenticates a user by validating that a {@link User} for the given username and password exist and then returns @@ -612,10 +612,22 @@ public interface PrivilegeHandler { * has the state {@link UserState#SYSTEM} and this user must have privilege to perform the concrete implementation * of the given {@link SystemUserAction} instance * + * * @param systemUsername * the username of the system user to perform the action as * @param action * the action to be performed as the system user + * + * @return the action + * + * @throws PrivilegeException */ - public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException; + public T runAsSystem(String systemUsername, T action) throws PrivilegeException; + + /** + * Returns the {@link EncryptionHandler} instance + * + * @return the {@link EncryptionHandler} instance + */ + public EncryptionHandler getEncryptionHandler() throws PrivilegeException; } From 6a62864331d93d180d4382706e9b30b8ed6cab6a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Oct 2015 22:08:39 +0200 Subject: [PATCH 413/457] [Minor] better writing of privilege XML on persist --- .../xml/PrivilegeModelDomWriter.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 480904019..a79efe2d8 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -26,6 +26,7 @@ import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.helper.XmlHelper; /** @@ -57,7 +58,7 @@ public class PrivilegeModelDomWriter { Element usersElement = doc.createElement(XmlConstants.XML_USERS); rootElement.appendChild(usersElement); - for (User user : this.users) { + this.users.stream().sorted((u1, u2) -> u1.getUserId().compareTo(u2.getUserId())).forEach(user -> { // create the user element Element userElement = doc.createElement(XmlConstants.XML_USER); @@ -65,17 +66,22 @@ public class PrivilegeModelDomWriter { userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + if (StringHelper.isNotEmpty(user.getPassword())) + userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); // add first name element - Element firstnameElement = doc.createElement(XmlConstants.XML_FIRSTNAME); - firstnameElement.setTextContent(user.getFirstname()); - userElement.appendChild(firstnameElement); + if (StringHelper.isNotEmpty(user.getFirstname())) { + Element firstnameElement = doc.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setTextContent(user.getFirstname()); + userElement.appendChild(firstnameElement); + } - // add lastname element - Element lastnameElement = doc.createElement(XmlConstants.XML_LASTNAME); - lastnameElement.setTextContent(user.getLastname()); - userElement.appendChild(lastnameElement); + // add last name element + if (StringHelper.isNotEmpty(user.getLastname())) { + Element lastnameElement = doc.createElement(XmlConstants.XML_LASTNAME); + lastnameElement.setTextContent(user.getLastname()); + userElement.appendChild(lastnameElement); + } // add state element Element stateElement = doc.createElement(XmlConstants.XML_STATE); @@ -97,20 +103,22 @@ public class PrivilegeModelDomWriter { } // add the parameters - Element parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); - userElement.appendChild(parametersElement); - for (Entry entry : user.getProperties().entrySet()) { - Element paramElement = doc.createElement(XmlConstants.XML_PARAMETER); - paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); - paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); - parametersElement.appendChild(paramElement); + if (!user.getProperties().isEmpty()) { + Element parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + userElement.appendChild(parametersElement); + for (Entry entry : user.getProperties().entrySet()) { + Element paramElement = doc.createElement(XmlConstants.XML_PARAMETER); + paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(paramElement); + } } - } + }); Element rolesElement = doc.createElement(XmlConstants.XML_ROLES); rootElement.appendChild(rolesElement); - for (Role role : this.roles) { + this.roles.stream().sorted((r1, r2) -> r1.getName().compareTo(r2.getName())).forEach(role -> { // create the role element Element roleElement = doc.createElement(XmlConstants.XML_ROLE); @@ -129,9 +137,11 @@ public class PrivilegeModelDomWriter { privilegeElement.setAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); // add the all allowed element - Element allAllowedElement = doc.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setTextContent(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.appendChild(allAllowedElement); + if (privilege.isAllAllowed()) { + Element allAllowedElement = doc.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setTextContent(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.appendChild(allAllowedElement); + } // add all the deny values for (String denyValue : privilege.getDenyList()) { @@ -147,7 +157,7 @@ public class PrivilegeModelDomWriter { privilegeElement.appendChild(allowValueElement); } } - } + }); // write the container file to disk XmlHelper.writeDocument(doc, this.modelFile); From c6f531c08eab8e834b17df9d15cb7b766cf7c821 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Oct 2015 22:15:35 +0200 Subject: [PATCH 414/457] [Minor] fixed broken tests --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 91bd4a007..5186dba16 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -247,8 +247,7 @@ public class XmlTest { Role systemAdminPrivileges = findRole("system_admin_privileges", roles); assertEquals("system_admin_privileges", systemAdminPrivileges.getName()); assertEquals(2, systemAdminPrivileges.getPrivilegeNames().size()); - assertThat( - systemAdminPrivileges.getPrivilegeNames(), + assertThat(systemAdminPrivileges.getPrivilegeNames(), containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction", "ch.eitchnet.privilege.test.model.TestSystemRestrictable")); @@ -355,6 +354,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("fd5f4554312b18ca9fd61f0a6a4c87603e7c191ee206afca6328e5bffb87f86c", fileHash); + assertEquals("9fac0049862b2b54b41b08b0e12a9cb105894d57a1500d6603c473661f4c7313", fileHash); } } From 13b0003494611567b2f48ea5e7081f9ed3d568c3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 15 Oct 2015 07:57:04 +0200 Subject: [PATCH 415/457] [New] Added AESCryptoHelper with tests --- .../utils/helper/AesCryptoHelper.java | 351 ++++++++++++++ .../utils/helper/AesCryptoHelperTest.java | 127 +++++ src/test/resources/crypto_test_image.ico | Bin 0 -> 5430 bytes src/test/resources/crypto_test_long.txt | 447 ++++++++++++++++++ src/test/resources/crypto_test_middle.txt | 3 + src/test/resources/crypto_test_short.txt | 1 + 6 files changed, 929 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java create mode 100644 src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java create mode 100644 src/test/resources/crypto_test_image.ico create mode 100644 src/test/resources/crypto_test_long.txt create mode 100644 src/test/resources/crypto_test_middle.txt create mode 100644 src/test/resources/crypto_test_short.txt diff --git a/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java b/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java new file mode 100644 index 000000000..1f580405c --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java @@ -0,0 +1,351 @@ +package ch.eitchnet.utils.helper; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.spec.KeySpec; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.dbc.DBC; + +public class AesCryptoHelper { + + private static final String CIPHER = "AES/CBC/PKCS5Padding"; + + private static final Logger logger = LoggerFactory.getLogger(AesCryptoHelper.class); + + public static OutputStream wrapEncrypt(char[] password, byte[] salt, OutputStream outputStream) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return wrapEncrypt(secret, outputStream); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static OutputStream wrapEncrypt(SecretKey secret, OutputStream outputStream) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + + // write the initialization vector, but not through the cipher output stream! + outputStream.write(initVector); + outputStream.flush(); + + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); + return cipherOutputStream; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static InputStream wrapDecrypt(char[] password, byte[] salt, InputStream inputStream) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return wrapDecrypt(secret, inputStream); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static InputStream wrapDecrypt(SecretKey secret, InputStream inputStream) { + + try { + + // read the initialization vector from the normal input stream + byte[] initVector = new byte[16]; + inputStream.read(initVector); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + return cipherInputStream; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void encrypt(char[] password, byte[] salt, String clearTextFileS, String encryptedFileS) { + + try (FileInputStream inFile = new FileInputStream(clearTextFileS); + FileOutputStream outFile = new FileOutputStream(encryptedFileS)) { + + encrypt(password, salt, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException("Failed to encrypt file " + clearTextFileS + " to " + encryptedFileS, e); + } + + logger.info("Encrypted file " + clearTextFileS + " to " + encryptedFileS); + } + + public static void encrypt(SecretKey secret, String clearTextFileS, String encryptedFileS) { + + try (FileInputStream inFile = new FileInputStream(clearTextFileS); + FileOutputStream outFile = new FileOutputStream(encryptedFileS)) { + + encrypt(secret, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException("Failed to encrypt file " + clearTextFileS + " to " + encryptedFileS, e); + } + + logger.info("Encrypted file " + clearTextFileS + " to " + encryptedFileS); + } + + public static void encrypt(char[] password, byte[] salt, InputStream inFile, OutputStream outFile) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + encrypt(secret, inFile, outFile); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void encrypt(SecretKey secret, InputStream inFile, OutputStream outFile) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + outFile.write(initVector); + + byte[] input = new byte[64]; + int bytesRead; + + while ((bytesRead = inFile.read(input)) != -1) { + byte[] output = cipher.update(input, 0, bytesRead); + if (output != null) + outFile.write(output); + } + + byte[] output = cipher.doFinal(); + if (output != null) + outFile.write(output); + + outFile.flush(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void decrypt(char[] password, byte[] salt, String encryptedFileS, String decryptedFileS) { + + try (FileInputStream fis = new FileInputStream(encryptedFileS); + FileOutputStream fos = new FileOutputStream(decryptedFileS)) { + + decrypt(password, salt, fis, fos); + + } catch (Exception e) { + throw new RuntimeException("Failed to decrypt file " + decryptedFileS + " to " + decryptedFileS, e); + } + + logger.info("Decrypted file " + encryptedFileS + " to " + decryptedFileS); + + } + + public static void decrypt(SecretKey secret, String encryptedFileS, String decryptedFileS) { + + try (FileInputStream fis = new FileInputStream(encryptedFileS); + FileOutputStream fos = new FileOutputStream(decryptedFileS)) { + + decrypt(secret, fis, fos); + + } catch (Exception e) { + throw new RuntimeException("Failed to decrypt file " + decryptedFileS + " to " + decryptedFileS, e); + } + + logger.info("Decrypted file " + encryptedFileS + " to " + decryptedFileS); + + } + + public static void decrypt(char[] password, byte[] salt, InputStream fis, OutputStream fos) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + decrypt(secret, fis, fos); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void decrypt(SecretKey secret, InputStream fis, OutputStream fos) { + + try { + + // read the initialization vector + byte[] initVector = new byte[16]; + fis.read(initVector); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + byte[] in = new byte[64]; + int read; + while ((read = fis.read(in)) != -1) { + byte[] output = cipher.update(in, 0, read); + if (output != null) + fos.write(output); + } + + byte[] output = cipher.doFinal(); + if (output != null) + fos.write(output); + + fos.flush(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] encrypt(char[] password, byte[] salt, String clearText) { + return encrypt(password, salt, clearText.getBytes()); + } + + public static byte[] encrypt(SecretKey secret, byte[] salt, String clearText) { + return encrypt(secret, clearText.getBytes()); + } + + public static byte[] encrypt(char[] password, byte[] salt, byte[] clearTextBytes) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return encrypt(secret, clearTextBytes); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] encrypt(SecretKey secret, byte[] clearTextBytes) { + + try { + + // set up cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secret); + + // set up the initialization vector + AlgorithmParameters params = cipher.getParameters(); + byte[] initVector = params.getParameterSpec(IvParameterSpec.class).getIV(); + DBC.INTERIM.assertEquals("IV must be 16 bytes long!", 16, initVector.length); + + // encrypt + byte[] encryptedBytes = cipher.doFinal(clearTextBytes); + + // create result bytes + ByteBuffer byteBuffer = ByteBuffer.allocate(initVector.length + encryptedBytes.length); + byteBuffer.put(initVector); + byteBuffer.put(encryptedBytes); + + return byteBuffer.array(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] decrypt(char[] password, byte[] salt, byte[] encryptedBytes) { + + try { + + // set up key + SecretKey secret = buildSecret(password, salt); + + return decrypt(secret, encryptedBytes); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static byte[] decrypt(SecretKey secret, byte[] encryptedBytes) { + + try { + + // read initialization vector + byte[] initVector = new byte[16]; + System.arraycopy(encryptedBytes, 0, initVector, 0, 16); + + // init cipher + Cipher cipher = Cipher.getInstance(CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(initVector)); + + byte[] decryptedBytes = cipher.doFinal(encryptedBytes, 16, encryptedBytes.length - 16); + return decryptedBytes; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static SecretKey buildSecret(char[] password, byte[] salt) { + try { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + KeySpec keySpec = new PBEKeySpec(password, salt, 65536, 256); + SecretKey secretKey = factory.generateSecret(keySpec); + SecretKey secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); + + return secret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java new file mode 100644 index 000000000..797f79d86 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java @@ -0,0 +1,127 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import org.junit.Test; + +public class AesCryptoHelperTest { + + private static final char[] password = "A2589309-17AE-4819-B9E4-E577CFA7778F".toCharArray(); + private static final byte[] salt; + + static { + try { + salt = "E68761B3-4E8E-4122-9B12-8B89E0AEB233".getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Test + public void shouldWrapStreams() throws Exception { + + byte[] clearTextBytes = "Some text".getBytes(); + + // encrypt data + ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); + OutputStream outputStream = AesCryptoHelper.wrapEncrypt(password, salt, encryptedOut); + outputStream.write(clearTextBytes); + outputStream.flush(); + outputStream.close(); + + // decrypt data + byte[] encryptedBytes = encryptedOut.toByteArray(); + ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes); + InputStream inputStream = AesCryptoHelper.wrapDecrypt(password, salt, encryptedIn); + + ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); + byte[] readBuffer = new byte[64]; + int read = 0; + while ((read = inputStream.read(readBuffer)) != -1) { + decryptedOut.write(readBuffer, 0, read); + } + + byte[] decryptedBytes = decryptedOut.toByteArray(); + + assertArrayEquals(clearTextBytes, decryptedBytes); + } + + @Test + public void shouldEncryptBytes() { + + byte[] clearTextBytes = "Some text".getBytes(); + + byte[] encryptedBytes = AesCryptoHelper.encrypt(password, salt, clearTextBytes); + byte[] decryptedBytes = AesCryptoHelper.decrypt(password, salt, encryptedBytes); + + assertArrayEquals(clearTextBytes, decryptedBytes); + } + + @Test + public void shouldEncryptShortFile() { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_short.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_short.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_short.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptMiddleFile() { + + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_middle.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_middle.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_middle.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptLongFile() { + + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_long.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_long.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_long.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + @Test + public void shouldEncryptBinaryFile() { + + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_image.ico"; + // encrypted file + String encryptedFileS = "target/encrypted_image.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_image.ico"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + } + + private static void testCrypto(String clearTextFileS, String encryptedFileS, String decryptedFileS) { + AesCryptoHelper.encrypt(password, salt, clearTextFileS, encryptedFileS); + AesCryptoHelper.decrypt(password, salt, encryptedFileS, decryptedFileS); + + String inputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(clearTextFileS))); + String doutputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(decryptedFileS))); + assertEquals(inputSha256, doutputSha256); + } +} diff --git a/src/test/resources/crypto_test_image.ico b/src/test/resources/crypto_test_image.ico new file mode 100644 index 0000000000000000000000000000000000000000..b48b3442ad90fd9b5bcbef8a9f703c1852de1561 GIT binary patch literal 5430 zcmchb`BM}}6vrn=RerU~e}JE;{1#6HHI^o#f*c}>As#Wtm_$)Ph+G1TXayBe6BCaj zBvFZYti~%*t5QJ(qGiBR5RfGTyDo=F)D_J}U0&Yn+3uO1JtUi$srpv;&d&7rGu>}y z-Wx@ENqJcb3R18QQYuF%O0c3RBe134AHS+74cLDj_u(PRR21!J8}2u50HBb1J8i-H z^GnJy-(Fx}HF~CN<)|6J^~tDkV0r`F0EJ_@GTutC@5?VP>+P@wb6;w&{Zbd38W!W= ze*Rj)LQDa&q=ZI0uKrvT%db0iVypwN8I1DBq5m%sCSss@IAAI*OG0mw(cjx|y|2Ii zaF}QV#&}i&4y?7>yht*>uTqszL3?#w9`!A4Z<>(o%HDl46i*XPO*DN?& zb`b9M^*>*|{m|8FfeVLE!M5e;>^k)8=WDIjjEw|M5!hG7nJyeUiDCNBwxOXRXuNnC zGN#S<8B?4mdE9JB9v2D8$U)Lr)P8jU{+za>sTGn#qWs2o3iBrhy)+)9{~fCxj&9xU z)-G&!!HFGTLyh?iIR3cLtwmc=xLu7YA90j2J_=IC&sK#D#G_}bK6FZqMIB51b;BHK zYnC^A2{PQ1j&H2K>^HVk(O)l>M`QBlFV@Vbk$#>yQ|#@a7WaH<^Ca#$WJ(K(L4T6Q!TZE>@$hMBJPY}= zHPhyZui3gO@i5TcD;-a9;XSrhp0o)uAW873Oh$iOYddUTmabD8?EEMb4i@eJEB>e7 zT+g1t*2Qal#x`-KPly$gINV${hU?dl@2~H2x!_poUca$TYxZgA9H#V8SHqH^P+ore`&~wWw7>02EzsdYeFz}e>EoaI=f0E$P z_mRh&zn)LOX2**62!HCd)YW^$w()+;m^c@N!m!VzF?sqod7UR>I=cBQ$^VYcF3pv< z_|8Osy#xz!AxrJiAEhzd8_?e{N3?E*(aXTzc1t>z)`Q29w>2Z4tV!|cZ?+Yr}0hH@s)ccGAh%fLfy1}1r~v~}ZarnOoeQH&d78-Kfs z=B(%;e{TwmN#l85Y;THLZa=;>VAT__liOn98CMPQolSFYlORrV7lW##0>eLEH9wJJq<#YCS8{-=$+ zs?f6&z3DpzeU=D9iSHEn4a2zMGiBu7wFTzY<073@yA0=hMAFy@=N>$Votw77Eb1}c zw+&tg`5TkQNC}B@vf9*L8;h~rb-R8X-sd$qaj?2hmxH%mxdwj@K9ckh-z`+P6}`KP z)opwnYFU1*tI;~KwxW%4pBuGTptVZ_u;qTt=VdsCFRV4C$s;9-fAMBM=kVy!~kovz!EF=d`RZ&qtT_5G>kr(HMHW9fZ7(A|gi`CXu#m#%$FVv>}5QhmUm z_dLh5y3en~yq)I;2Xc#<|L>S%sCkmRCc!#{J-c=J5kLP>;pZYB+LyhVoj-X0ff$ot zcb1f6%mse_gZ=^N>#)Iqy%(rnppEi2DPO=mTz*H#!&S^Jy4yP?*_tgZU)m74RL&_w z;~;NpqEme~xt~cM?=~b;aDKnO#LS+}hG(*IOzqw1eh=YwAIRQneste4 nvNEsKToAs Date: Fri, 16 Oct 2015 17:21:14 +0200 Subject: [PATCH 416/457] [Minor] removed useless logging statement --- src/main/java/ch/eitchnet/utils/helper/XmlHelper.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java index da2f03efc..15410fd5b 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java @@ -225,8 +225,7 @@ public class XmlHelper { try { String docEncoding = document.getInputEncoding(); - if (docEncoding == null || docEncoding.isEmpty()) { - XmlHelper.logger.info(MessageFormat.format("No encoding passed. Using default encoding {0}", encoding)); //$NON-NLS-1$ + if (StringHelper.isEmpty(docEncoding)) { docEncoding = encoding; } From 5dc94514e13d142de8e2532b3bec18b28c7855dd Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Oct 2015 13:16:27 +0200 Subject: [PATCH 417/457] [Major] Added persisting of sessions --- config/PrivilegeConfig.xml | 6 +- config/PrivilegeConfigMerge.xml | 2 +- .../handler/DefaultPrivilegeHandler.java | 239 ++++++++++++++++-- .../privilege/handler/PrivilegeHandler.java | 56 ++++ .../handler/XmlPersistenceHandler.java | 8 +- .../privilege/helper/XmlConstants.java | 40 +++ .../eitchnet/privilege/model/Certificate.java | 32 +-- .../eitchnet/privilege/model/UserState.java | 4 + .../xml/CertificateStubsDomWriter.java | 79 ++++++ .../xml/CertificateStubsSaxReader.java | 114 +++++++++ .../xml/PrivilegeConfigSaxReader.java | 11 +- .../privilege/test/AbstractPrivilegeTest.java | 112 ++++++++ .../privilege/test/PersistSessionsTest.java | 47 ++++ .../test/PrivilegeConflictMergeTest.java | 99 +------- .../privilege/test/PrivilegeTest.java | 111 ++------ .../ch/eitchnet/privilege/test/XmlTest.java | 2 +- 16 files changed, 729 insertions(+), 233 deletions(-) create mode 100644 src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java diff --git a/config/PrivilegeConfig.xml b/config/PrivilegeConfig.xml index 9dd3e2b11..4d293aca2 100644 --- a/config/PrivilegeConfig.xml +++ b/config/PrivilegeConfig.xml @@ -5,6 +5,10 @@ + + + + @@ -17,7 +21,7 @@ - + diff --git a/config/PrivilegeConfigMerge.xml b/config/PrivilegeConfigMerge.xml index 923189b53..8838bc468 100644 --- a/config/PrivilegeConfigMerge.xml +++ b/config/PrivilegeConfigMerge.xml @@ -17,7 +17,7 @@ - + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index dcd77042e..818e3fb17 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -15,6 +15,11 @@ */ package ch.eitchnet.privilege.handler; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -31,6 +36,8 @@ import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.crypto.SecretKey; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +56,11 @@ import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.privilege.xml.CertificateStubsDomWriter; +import ch.eitchnet.privilege.xml.CertificateStubsSaxReader; +import ch.eitchnet.privilege.xml.CertificateStubsSaxReader.CertificateStub; import ch.eitchnet.utils.collections.Tuple; +import ch.eitchnet.utils.helper.AesCryptoHelper; import ch.eitchnet.utils.helper.StringHelper; /** @@ -107,6 +118,21 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private boolean autoPersistOnUserChangesData; + /** + * flag to define if sessions should be persisted + */ + private boolean persistSessions; + + /** + * Path to sessions file for persistence + */ + private File persistSessionsPath; + + /** + * Secret key + */ + private SecretKey secretKey; + private PrivilegeConflictResolution privilegeConflictResolution; @Override @@ -159,6 +185,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return policyDef; } + @Override + public List getCertificates(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_GET_CERTIFICATES)); + + return this.privilegeContextMap.values().stream().map(p -> p.getCertificate()).collect(Collectors.toList()); + } + @Override public List getRoles(Certificate certificate) { @@ -958,8 +994,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public Certificate authenticate(String username, byte[] password) { - // create certificate - Certificate certificate; try { // username must be at least 2 characters in length if (username == null || username.length() < 2) { @@ -984,16 +1018,23 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = UUID.randomUUID().toString(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, new Date(), username, user.getFirstname(), user.getLastname(), - authToken, user.getLocale(), userRoles, new HashMap<>(user.getProperties())); + Certificate certificate = new Certificate(sessionId, username, user.getFirstname(), user.getLastname(), + user.getUserState(), authToken, new Date(), user.getLocale(), userRoles, + new HashMap<>(user.getProperties())); + certificate.setLastAccess(new Date()); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); + persistSessions(); + // log DefaultPrivilegeHandler.logger .info(MessageFormat.format("User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$ + // return the certificate + return certificate; + } catch (RuntimeException e) { String msg = "User {0} Failed to authenticate: {1}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, username, e.getMessage()); @@ -1002,9 +1043,88 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } finally { clearPassword(password); } + } - // return the certificate - return certificate; + private boolean persistSessions() { + if (!this.persistSessions) + return false; + + List sessions = this.privilegeContextMap.values().stream().map(p -> p.getCertificate()) + .filter(c -> !c.getUserState().isSystem()).collect(Collectors.toList()); + + try (OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, + new FileOutputStream(this.persistSessionsPath))) { + + CertificateStubsDomWriter writer = new CertificateStubsDomWriter(sessions, outputStream); + writer.write(); + outputStream.flush(); + + } catch (Exception e) { + throw new PrivilegeException("Failed to persist sessions!", e); + } + + return true; + } + + private boolean loadSessions() { + if (!this.persistSessions) { + logger.info("Persisteding of sessions not enabled, so not loading!."); + return false; + } + + if (!this.persistSessionsPath.exists()) { + logger.info("No persisted sessions exist to be loaded."); + return false; + } + + if (!this.persistSessionsPath.isFile()) + throw new PrivilegeException( + "Sessions data file is not a file but exists at " + this.persistSessionsPath.getAbsolutePath()); + + List certificateStubs; + try (InputStream inputStream = AesCryptoHelper.wrapDecrypt(this.secretKey, + new FileInputStream(this.persistSessionsPath))) { + + CertificateStubsSaxReader reader = new CertificateStubsSaxReader(inputStream); + certificateStubs = reader.read(); + + } catch (Exception e) { + throw new PrivilegeException("Failed to load sessions!", e); + } + + if (certificateStubs.isEmpty()) { + logger.info("No persisted sessions exist to be loaded."); + return false; + } + + for (CertificateStub certificateStub : certificateStubs) { + String username = certificateStub.getUsername(); + String sessionId = certificateStub.getSessionId(); + String authToken = certificateStub.getAuthToken(); + User user = this.persistenceHandler.getUser(username); + if (user == null) { + logger.error("Ignoring session data for missing user " + username); + continue; + } + + Set userRoles = user.getRoles(); + if (userRoles.isEmpty()) { + logger.error("Ignoring session data for user " + username + " which has not roles defined!"); + continue; + } + + // create a new certificate, with details of the user + Certificate certificate = new Certificate(sessionId, username, user.getFirstname(), user.getLastname(), + user.getUserState(), authToken, certificateStub.getLoginTime(), certificateStub.getLocale(), + userRoles, new HashMap<>(user.getProperties())); + certificate.setLastAccess(certificateStub.getLastAccess()); + + PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); + this.privilegeContextMap.put(sessionId, privilegeContext); + } + + logger.info("Loaded " + this.privilegeContextMap.size() + " sessions."); + return true; } /** @@ -1146,6 +1266,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // remove registration PrivilegeContext privilegeContext = this.privilegeContextMap.remove(certificate.getSessionId()); + // persist sessions + persistSessions(); + // return true if object was really removed boolean loggedOut = privilegeContext != null; if (loggedOut) @@ -1236,6 +1359,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return this.persistenceHandler.persist(); } + @Override + public boolean persistSessions(Certificate certificate) { + + // validate who is doing this + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST_SESSIONS)); + + return persistSessions(); + } + @Override public boolean reload(Certificate certificate) { @@ -1273,8 +1406,29 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; + handleAutoPersistOnUserDataChange(parameterMap); + handlePersistSessionsParam(parameterMap); + handleConflictResolutionParam(parameterMap); + handleSecretParams(parameterMap); + + // validate policies on privileges of Roles + for (Role role : persistenceHandler.getAllRoles()) { + validatePolicies(role); + } + + // validate privilege conflicts + validatePrivilegeConflicts(); + + this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); + + loadSessions(); + + this.initialized = true; + } + + private void handleAutoPersistOnUserDataChange(Map parameterMap) { String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA); - if (autoPersistS == null || autoPersistS.equals(Boolean.FALSE.toString())) { + if (StringHelper.isEmpty(autoPersistS) || autoPersistS.equals(Boolean.FALSE.toString())) { this.autoPersistOnUserChangesData = false; } else if (autoPersistS.equals(Boolean.TRUE.toString())) { this.autoPersistOnUserChangesData = true; @@ -1283,8 +1437,48 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA, autoPersistS, Boolean.FALSE); logger.error(msg); + this.autoPersistOnUserChangesData = false; } + } + private void handlePersistSessionsParam(Map parameterMap) { + String persistSessionsS = parameterMap.get(PARAM_PERSIST_SESSIONS); + if (StringHelper.isEmpty(persistSessionsS) || persistSessionsS.equals(Boolean.FALSE.toString())) { + this.persistSessions = false; + } else if (persistSessionsS.equals(Boolean.TRUE.toString())) { + this.persistSessions = true; + + String persistSessionsPathS = parameterMap.get(PARAM_PERSIST_SESSIONS_PATH); + if (StringHelper.isEmpty(persistSessionsPathS)) { + String msg = "Parameter {0} has illegal value {1}."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPathS); + throw new PrivilegeException(msg); + } + + File persistSessionsPath = new File(persistSessionsPathS); + if (!persistSessionsPath.getParentFile().isDirectory()) { + String msg = "Path for param {0} is invalid as parent does not exist or is not a directory. Value: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + if (persistSessionsPath.exists() && (!persistSessionsPath.isFile() || !persistSessionsPath.canWrite())) { + String msg = "Path for param {0} is invalid as file exists but is not a file or not writeable. Value: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + this.persistSessionsPath = persistSessionsPath; + logger.info("Enabling persistence of sessions."); //$NON-NLS-1$ + } else { + String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS, persistSessionsS, Boolean.FALSE); + logger.error(msg); + this.persistSessions = false; + } + } + + private void handleConflictResolutionParam(Map parameterMap) { String privilegeConflictResolutionS = parameterMap.get(PARAM_PRIVILEGE_CONFLICT_RESOLUTION); if (privilegeConflictResolutionS == null) { this.privilegeConflictResolution = PrivilegeConflictResolution.STRICT; @@ -1301,17 +1495,28 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } logger.info("Privilege conflict resolution set to " + this.privilegeConflictResolution); //$NON-NLS-1$ + } - // validate policies on privileges of Roles - for (Role role : persistenceHandler.getAllRoles()) { - validatePolicies(role); + private void handleSecretParams(Map parameterMap) { + + if (!this.persistSessions) + return; + + String secretKeyS = parameterMap.get(PARAM_SECRET_KEY); + if (StringHelper.isEmpty(secretKeyS)) { + String msg = "Parameter {0} may not be empty if parameter {1} is enabled."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_SECRET_KEY, PARAM_PRIVILEGE_CONFLICT_RESOLUTION); + throw new PrivilegeException(msg); } - // validate privilege conflicts - validatePrivilegeConflicts(); + String secretSaltS = parameterMap.get(PARAM_SECRET_SALT); + if (StringHelper.isEmpty(secretSaltS)) { + String msg = "Parameter {0} may not be empty if parameter {1} is enabled."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_SECRET_SALT, PARAM_PRIVILEGE_CONFLICT_RESOLUTION); + throw new PrivilegeException(msg); + } - this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); - this.initialized = true; + this.secretKey = AesCryptoHelper.buildSecret(secretKeyS.toCharArray(), secretSaltS.getBytes()); } private void validatePrivilegeConflicts() { @@ -1502,8 +1707,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = UUID.randomUUID().toString(); // create a new certificate, with details of the user - Certificate systemUserCertificate = new Certificate(sessionId, new Date(), systemUsername, user.getFirstname(), - user.getLastname(), authToken, user.getLocale(), user.getRoles(), new HashMap<>(user.getProperties())); + Certificate systemUserCertificate = new Certificate(sessionId, systemUsername, user.getFirstname(), + user.getLastname(), user.getUserState(), authToken, new Date(), user.getLocale(), user.getRoles(), + new HashMap<>(user.getProperties())); + systemUserCertificate.setLastAccess(new Date()); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index b6e0f8856..cb612278d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -55,6 +55,11 @@ public interface PrivilegeHandler { * allAllowed */ public static final String PRIVILEGE_ACTION_PERSIST = "Persist"; + /** + * For Privilege "PrivilegeAction" value required to be able to persist session if not exempted by + * allAllowed + */ + public static final String PRIVILEGE_ACTION_PERSIST_SESSIONS = "PersistSessions"; /** * For Privilege "PrivilegeAction" value required to be able to reload changes if not exempted by * allAllowed @@ -65,6 +70,14 @@ public interface PrivilegeHandler { * allAllowed */ public static final String PRIVILEGE_ACTION_GET_POLICIES = "GetPolicies"; + /** + * For Privilege "PrivilegeAction" value required to get a certificate if not allAllowed + */ + public static final String PRIVILEGE_ACTION_GET_CERTIFICATE = "GetCertificate"; + /** + * For Privilege "PrivilegeAction" value required to get all certificates if not allAllowed + */ + public static final String PRIVILEGE_ACTION_GET_CERTIFICATES = "GetCertificates"; /// @@ -131,11 +144,31 @@ public interface PrivilegeHandler { /// + /** + * configuration parameter to define a secret_key + */ + public static final String PARAM_SECRET_KEY = "secretKey"; //$NON-NLS-1$ + + /** + * configuration parameter to define a secret salt + */ + public static final String PARAM_SECRET_SALT = "secretSalt"; //$NON-NLS-1$ + /** * configuration parameter to define automatic persisting on password change */ public static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ + /** + * configuration parameter to define if sessions should be persisted + */ + public static final String PARAM_PERSIST_SESSIONS = "persistSessions"; //$NON-NLS-1$ + + /** + * configuration parameter to define where sessions are to be persisted + */ + public static final String PARAM_PERSIST_SESSIONS_PATH = "persistSessionsPath"; //$NON-NLS-1$ + /** * configuration parameter to define {@link PrivilegeConflictResolution} */ @@ -175,6 +208,16 @@ public interface PrivilegeHandler { */ public Map getPolicyDefs(Certificate certificate); + /** + * Returns the list of {@link Certificate Certificates} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return the list of {@link Certificate Certificates} + */ + public List getCertificates(Certificate certificate); + /** * Returns all {@link RoleRep RoleReps} * @@ -607,6 +650,19 @@ public interface PrivilegeHandler { */ public boolean persist(Certificate certificate) throws AccessDeniedException; + /** + * Persists all currently active sessions + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return true if changes were persisted, false if not (i.e. not enabled) + * + * @throws AccessDeniedException + * if the users of the given certificate does not have the privilege to perform this action + */ + public boolean persistSessions(Certificate certificate) throws AccessDeniedException; + /** * Special method to perform work as a System user, meaning the given systemUsername corresponds to an account which * has the state {@link UserState#SYSTEM} and this user must have privilege to perform the concrete implementation diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 0d31c1048..40e4b1dc4 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -104,8 +104,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void replaceUser(User user) { if (!this.userMap.containsKey(user.getUsername())) - throw new IllegalStateException(MessageFormat.format( - "The user {0} can not be replaced as it does not exiset!", user.getUsername())); + throw new IllegalStateException(MessageFormat + .format("The user {0} can not be replaced as it does not exiset!", user.getUsername())); this.userMap.put(user.getUsername(), user); this.userMapDirty = true; } @@ -121,8 +121,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void replaceRole(Role role) { if (!this.roleMap.containsKey(role.getName())) - throw new IllegalStateException(MessageFormat.format( - "The role {0} can not be replaced as it does not exist!", role.getName())); + throw new IllegalStateException( + MessageFormat.format("The role {0} can not be replaced as it does not exist!", role.getName())); this.roleMap.put(role.getName(), role); this.roleMapDirty = true; } diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index d4a0c1e9d..83ff72744 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -48,6 +48,11 @@ public class XmlConstants { */ public static final String XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles"; + /** + * XML_ROOT_CERTIFICATES = "Certificates" : + */ + public static final String XML_ROOT_CERTIFICATES = "Certificates"; + /** * XML_HANDLER_PERSISTENCE = "PersistenceHandler" : */ @@ -78,6 +83,16 @@ public class XmlConstants { */ public static final String XML_USERS = "Users"; + /** + * XML_CERTIFICATE = "Certificate" : + */ + public static final String XML_CERTIFICATE = "Certificate"; + + /** + * XML_SESSION_DATA = "SessionData" : + */ + public static final String XML_SESSION_DATA = "SessionData"; + /** * XML_USER = "User" */ @@ -153,6 +168,16 @@ public class XmlConstants { */ public static final String XML_ATTR_CLASS = "class"; + /** + * XML_ATTR_LOGIN_TIME = "loginTime" : + */ + public static final String XML_ATTR_LOGIN_TIME = "loginTime"; + + /** + * XML_ATTR_LAST_ACCESS = "lastAccess" : + */ + public static final String XML_ATTR_LAST_ACCESS = "lastAccess"; + /** * XML_ATTR_NAME = "name" : */ @@ -173,11 +198,26 @@ public class XmlConstants { */ public static final String XML_ATTR_USER_ID = "userId"; + /** + * XML_ATTR_SESSION_ID = "sessionId" : + */ + public static final String XML_ATTR_SESSION_ID = "sessionId"; + /** * XML_ATTR_USERNAME = "username" : */ public static final String XML_ATTR_USERNAME = "username"; + /** + * XML_ATTR_AUTH_TOKEN = "authToken" : + */ + public static final String XML_ATTR_AUTH_TOKEN = "authToken"; + + /** + * XML_ATTR_LOCALE = "locale" : + */ + public static final String XML_ATTR_LOCALE = "locale"; + /** * XML_ATTR_PASSWORD = "password" : */ diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 2255300ca..d170ab45b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -18,7 +18,6 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -40,15 +39,15 @@ public final class Certificate implements Serializable { private static final long serialVersionUID = 1L; private final String sessionId; - private final Date loginTime; private final String username; private final String firstname; private final String lastname; + private final UserState userState; private final String authToken; + private final Date loginTime; private final Set userRoles; private final Map propertyMap; - private final Map sessionDataMap; private Locale locale; private Date lastAccess; @@ -79,8 +78,8 @@ public final class Certificate implements Serializable { * a {@link Map} containing string value pairs of properties for the logged in user. These properties can * be edited and can be used for the user to change settings of this session */ - public Certificate(String sessionId, Date loginTime, String username, String firstname, String lastname, - String authToken, Locale locale, Set userRoles, Map propertyMap) { + public Certificate(String sessionId, String username, String firstname, String lastname, UserState userState, + String authToken, Date loginTime, Locale locale, Set userRoles, Map propertyMap) { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { @@ -92,13 +91,17 @@ public final class Certificate implements Serializable { if (StringHelper.isEmpty(authToken)) { throw new PrivilegeException("authToken is null!"); //$NON-NLS-1$ } + if (userState == null) { + throw new PrivilegeException("userState is null!"); //$NON-NLS-1$ + } this.sessionId = sessionId; - this.loginTime = loginTime; this.username = username; this.firstname = firstname; this.lastname = lastname; + this.userState = userState; this.authToken = authToken; + this.loginTime = loginTime; // if no locale is given, set default if (locale == null) @@ -112,7 +115,6 @@ public final class Certificate implements Serializable { this.propertyMap = Collections.unmodifiableMap(propertyMap); this.userRoles = Collections.unmodifiableSet(userRoles); - this.sessionDataMap = new HashMap<>(); } /** @@ -157,15 +159,6 @@ public final class Certificate implements Serializable { return this.propertyMap.get(key); } - /** - * Returns a mutable {@link Map} for storing session relevant data - * - * @return the sessionDataMap - */ - public Map getSessionDataMap() { - return this.sessionDataMap; - } - /** * @return the locale */ @@ -209,6 +202,13 @@ public final class Certificate implements Serializable { return this.lastname; } + /** + * @return the userState + */ + public UserState getUserState() { + return userState; + } + /** * @return the loginTime */ diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index 2272002d3..b78e492ff 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -53,4 +53,8 @@ public enum UserState { * This is the System user state which is special and thus exempted from normal uses */ SYSTEM; + + public boolean isSystem() { + return this == UserState.SYSTEM; + } } diff --git a/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java new file mode 100644 index 000000000..7dab6d30e --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.xml; + +import java.io.OutputStream; +import java.util.List; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; + +/** + * @author Robert von Burg + */ +public class CertificateStubsDomWriter { + + private List certificates; + private OutputStream outputStream; + + public CertificateStubsDomWriter(List certificates, OutputStream outputStream) { + this.certificates = certificates; + this.outputStream = outputStream; + } + + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_ROOT_CERTIFICATES); + doc.appendChild(rootElement); + + this.certificates.stream().sorted((c1, c2) -> c1.getSessionId().compareTo(c2.getSessionId())).forEach(cert -> { + + // create the certificate element + Element certElement = doc.createElement(XmlConstants.XML_CERTIFICATE); + rootElement.appendChild(certElement); + + // sessionId; + certElement.setAttribute(XmlConstants.XML_ATTR_SESSION_ID, cert.getSessionId()); + + // username; + certElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, cert.getUsername()); + + // authToken; + certElement.setAttribute(XmlConstants.XML_ATTR_AUTH_TOKEN, cert.getAuthToken()); + + // locale; + certElement.setAttribute(XmlConstants.XML_ATTR_LOCALE, cert.getLocale().toString()); + + // loginTime; + certElement.setAttribute(XmlConstants.XML_ATTR_LOGIN_TIME, + ISO8601FormatFactory.getInstance().formatDate(cert.getLoginTime())); + + // lastAccess; + certElement.setAttribute(XmlConstants.XML_ATTR_LAST_ACCESS, + ISO8601FormatFactory.getInstance().formatDate(cert.getLastAccess())); + }); + + // write the container file to disk + XmlHelper.writeDocument(doc, this.outputStream); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java new file mode 100644 index 000000000..0cbfdb3c2 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.xml; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; + +/** + * @author Robert von Burg + */ +public class CertificateStubsSaxReader extends DefaultHandler { + + private InputStream inputStream; + private List stubs; + + public CertificateStubsSaxReader(InputStream inputStream) { + this.inputStream = inputStream; + } + + public List read() { + this.stubs = new ArrayList<>(); + XmlHelper.parseDocument(this.inputStream, this); + return stubs; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (qName) { + case XmlConstants.XML_ROOT_CERTIFICATES: + break; + case XmlConstants.XML_CERTIFICATE: + + CertificateStub stub = new CertificateStub(); + stub.sessionId = attributes.getValue(XmlConstants.XML_ATTR_SESSION_ID); + stub.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME); + stub.authToken = attributes.getValue(XmlConstants.XML_ATTR_AUTH_TOKEN); + stub.locale = new Locale(attributes.getValue(XmlConstants.XML_ATTR_LOCALE)); + stub.loginTime = ISO8601FormatFactory.getInstance() + .parseDate(attributes.getValue(XmlConstants.XML_ATTR_LOGIN_TIME)); + stub.lastAccess = ISO8601FormatFactory.getInstance() + .parseDate(attributes.getValue(XmlConstants.XML_ATTR_LAST_ACCESS)); + + DBC.INTERIM.assertNotEmpty("sessionId missing on sessions data!", stub.sessionId); + DBC.INTERIM.assertNotEmpty("username missing on sessions data!", stub.username); + DBC.INTERIM.assertNotEmpty("authToken missing on sessions data!", stub.authToken); + + this.stubs.add(stub); + break; + + default: + throw new PrivilegeException("Unhandled tag " + qName); + } + } + + public class CertificateStub { + private String sessionId; + private String username; + private String authToken; + private Locale locale; + private Date loginTime; + private Date lastAccess; + + public String getSessionId() { + return sessionId; + } + + public String getUsername() { + return username; + } + + public String getAuthToken() { + return authToken; + } + + public Locale getLocale() { + return locale; + } + + public Date getLoginTime() { + return loginTime; + } + + public Date getLastAccess() { + return lastAccess; + } + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index c1b31c5a2..660a4544d 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -32,8 +32,6 @@ import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; */ public class PrivilegeConfigSaxReader extends DefaultHandler { - // private static final Logger logger = LoggerFactory.getLogger(PrivilegeConfigSaxReader.class); - private Deque buildersStack = new ArrayDeque(); private PrivilegeContainerModel containerModel; @@ -109,7 +107,8 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { private String currentElement; @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { if (qName.equals(XmlConstants.XML_CONTAINER)) { this.currentElement = qName; } else if (qName.equals(XmlConstants.XML_HANDLER_ENCRYPTION)) { @@ -147,7 +146,8 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { private Map parameterMap = new HashMap(); @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { if (qName.equals(XmlConstants.XML_PARAMETER)) { String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); @@ -168,7 +168,8 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { // @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { if (qName.equals(XmlConstants.XML_POLICY)) { String policyName = attributes.getValue(XmlConstants.XML_ATTR_NAME); String policyClassName = attributes.getValue(XmlConstants.XML_ATTR_CLASS); diff --git a/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java new file mode 100644 index 000000000..b7b2e1c57 --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java @@ -0,0 +1,112 @@ +package ch.eitchnet.privilege.test; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.utils.helper.FileHelper; + +public class AbstractPrivilegeTest { + + protected static final Logger logger = LoggerFactory.getLogger(AbstractPrivilegeTest.class); + + protected PrivilegeHandler privilegeHandler; + protected PrivilegeContext ctx; + + protected void login(String username, byte[] password) { + Certificate certificate = privilegeHandler.authenticate(username, password); + assertTrue("Certificate is null!", certificate != null); + PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); + this.ctx = privilegeContext; + } + + protected void logout() { + if (this.ctx != null) { + try { + PrivilegeContext privilegeContext = this.ctx; + this.ctx = null; + privilegeHandler.invalidateSession(privilegeContext.getCertificate()); + } catch (PrivilegeException e) { + String msg = "There is no PrivilegeContext currently bound to the ThreadLocal!"; + if (!e.getMessage().equals(msg)) + throw e; + } + } + } + + protected static void prepareConfigs(String dst, String configFilename, String modelFilename) { + try { + String pwd = System.getProperty("user.dir"); + + File configPath = new File(pwd, "config"); + + File privilegeConfigFile = new File(configPath, configFilename); + File privilegeModelFile = new File(configPath, modelFilename); + + File targetPath = new File(pwd, "target/" + dst); + if (!targetPath.mkdirs()) + throw new RuntimeException("Could not create parent " + targetPath); + + File dstConfig = new File(targetPath, configFilename); + File dstModel = new File(targetPath, modelFilename); + + // write config + String config = new String(Files.readAllBytes(privilegeConfigFile.toPath()), "UTF-8"); + config = config.replace("${target}", dst); + Files.write(dstConfig.toPath(), config.getBytes("UTF-8")); + + // copy model + Files.copy(privilegeModelFile.toPath(), dstModel.toPath()); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Initialization failed", e); + } + } + + protected static void removeConfigs(String dst) { + try { + String pwd = System.getProperty("user.dir"); + File targetPath = new File(pwd, "target"); + targetPath = new File(targetPath, dst); + if (targetPath.exists() && !FileHelper.deleteFile(targetPath, true)) { + throw new RuntimeException( + "Tmp configuration still exists and can not be deleted at " + targetPath.getAbsolutePath()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Initialization failed", e); + } + } + + protected static File getPrivilegeConfigFile(String dst, String configFilename) { + try { + String pwd = System.getProperty("user.dir"); + File targetPath = new File(pwd, "target"); + targetPath = new File(targetPath, dst); + return new File(targetPath, configFilename); + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Initialization failed", e); + } + } + + protected void initialize(String dst, String configFilename) { + try { + File privilegeConfigFile = getPrivilegeConfigFile(dst, configFilename); + this.privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Initialization failed", e); + } + } +} diff --git a/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java b/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java new file mode 100644 index 000000000..a032c980a --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java @@ -0,0 +1,47 @@ +package ch.eitchnet.privilege.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class PersistSessionsTest extends AbstractPrivilegeTest { + + @BeforeClass + public static void init() throws Exception { + removeConfigs(PersistSessionsTest.class.getSimpleName()); + prepareConfigs(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeModel.xml"); + } + + @AfterClass + public static void destroy() throws Exception { + removeConfigs(PersistSessionsTest.class.getSimpleName()); + } + + @Before + public void setup() throws Exception { + initialize(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml"); + } + + @Test + public void shouldPersistAndReloadSessions() { + + // assert no sessions file + File sessionsFile = new File("target/PersistSessionsTest/sessions.dat"); + assertFalse("Sessions File should no yet exist", sessionsFile.exists()); + + // login and assert sessions file was written + login("admin", "admin".getBytes()); + this.privilegeHandler.isCertificateValid(ctx.getCertificate()); + assertTrue("Sessions File should have been created!", sessionsFile.isFile()); + + // re-initialize and assert still logged in + initialize(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml"); + this.privilegeHandler.isCertificateValid(ctx.getCertificate()); + } +} diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java index ab9a352e1..f78edbb23 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java @@ -19,120 +19,33 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.io.File; - import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; -import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.helper.FileHelper; /** * @author Robert von Burg */ -public class PrivilegeConflictMergeTest { - - private static final Logger logger = LoggerFactory.getLogger(PrivilegeConflictMergeTest.class); +public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { @BeforeClass public static void init() throws Exception { - try { - destroy(); - - // copy configuration to tmp - String pwd = System.getProperty("user.dir"); - - File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModelMerge.xml"); - File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModelMerge.xml"); - if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { - throw new RuntimeException("Tmp configuration still exists and can not be deleted at " - + tmpPrivilegeModelFile.getAbsolutePath()); - } - - File parentFile = tmpPrivilegeModelFile.getParentFile(); - if (!parentFile.exists()) { - if (!parentFile.mkdirs()) - throw new RuntimeException("Could not create parent for tmp " + tmpPrivilegeModelFile); - } - - if (!FileHelper.copy(origPrivilegeModelFile, tmpPrivilegeModelFile, true)) - throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); - - } catch (Exception e) { - logger.error(e.getMessage(), e); - - throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); - } + removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); + prepareConfigs(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml", + "PrivilegeModelMerge.xml"); } @AfterClass public static void destroy() throws Exception { - - // delete temporary file - String pwd = System.getProperty("user.dir"); - - File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModelMerge.xml"); - if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { - throw new RuntimeException("Tmp configuration still exists and can not be deleted at " - + tmpPrivilegeModelFile.getAbsolutePath()); - } - - // and temporary parent - File parentFile = tmpPrivilegeModelFile.getParentFile(); - if (parentFile.exists() && !parentFile.delete()) { - throw new RuntimeException("Could not remove temporary parent for tmp " + tmpPrivilegeModelFile); - } + removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); } - private PrivilegeHandler privilegeHandler; - private PrivilegeContext ctx; - @Before public void setup() throws Exception { - try { - - String pwd = System.getProperty("user.dir"); - - File privilegeConfigFile = new File(pwd + "/config/PrivilegeConfigMerge.xml"); - - // initialize privilege - privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); - - } catch (Exception e) { - logger.error(e.getMessage(), e); - - throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); - } - } - - private void login(String username, byte[] password) { - Certificate certificate = privilegeHandler.authenticate(username, password); - assertTrue("Certificate is null!", certificate != null); - PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); - this.ctx = privilegeContext; - } - - private void logout() { - if (this.ctx != null) { - try { - PrivilegeContext privilegeContext = this.ctx; - this.ctx = null; - privilegeHandler.invalidateSession(privilegeContext.getCertificate()); - } catch (PrivilegeException e) { - String msg = "There is no PrivilegeContext currently bound to the ThreadLocal!"; - if (!e.getMessage().equals(msg)) - throw e; - } - } + initialize(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml"); } @Test diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 3d29e60d7..0cb60c782 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -17,10 +17,8 @@ package ch.eitchnet.privilege.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -42,10 +40,8 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; import ch.eitchnet.privilege.i18n.PrivilegeMessages; import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; @@ -55,7 +51,6 @@ import ch.eitchnet.privilege.test.model.TestRestrictable; import ch.eitchnet.privilege.test.model.TestSystemUserAction; import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; import ch.eitchnet.utils.helper.ArraysHelper; -import ch.eitchnet.utils.helper.FileHelper; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum.br /> @@ -65,7 +60,7 @@ import ch.eitchnet.utils.helper.FileHelper; * @author Robert von Burg */ @SuppressWarnings("nls") -public class PrivilegeTest { +public class PrivilegeTest extends AbstractPrivilegeTest { private static final String ROLE_PRIVILEGE_ADMIN = "PrivilegeAdmin"; private static final String PRIVILEGE_USER_ACCESS = "UserAccessPrivilege"; @@ -91,96 +86,20 @@ public class PrivilegeTest { @Rule public ExpectedException exception = ExpectedException.none(); - private static PrivilegeHandler privilegeHandler; - private PrivilegeContext ctx; - @BeforeClass public static void init() throws Exception { - try { - destroy(); - - // copy configuration to tmp - String pwd = System.getProperty("user.dir"); - - File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModel.xml"); - File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModel.xml"); - if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { - throw new RuntimeException("Tmp configuration still exists and can not be deleted at " - + tmpPrivilegeModelFile.getAbsolutePath()); - } - - File parentFile = tmpPrivilegeModelFile.getParentFile(); - if (!parentFile.exists()) { - if (!parentFile.mkdirs()) - throw new RuntimeException("Could not create parent for tmp " + tmpPrivilegeModelFile); - } - - if (!FileHelper.copy(origPrivilegeModelFile, tmpPrivilegeModelFile, true)) - throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); - - } catch (Exception e) { - logger.error(e.getMessage(), e); - - throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); - } + removeConfigs(PrivilegeTest.class.getSimpleName()); + prepareConfigs(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeModel.xml"); } @AfterClass public static void destroy() throws Exception { - - // delete temporary file - String pwd = System.getProperty("user.dir"); - - File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModel.xml"); - if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { - throw new RuntimeException("Tmp configuration still exists and can not be deleted at " - + tmpPrivilegeModelFile.getAbsolutePath()); - } - - // and temporary parent - File parentFile = tmpPrivilegeModelFile.getParentFile(); - if (parentFile.exists() && !parentFile.delete()) { - throw new RuntimeException("Could not remove temporary parent for tmp " + tmpPrivilegeModelFile); - } + removeConfigs(PrivilegeTest.class.getSimpleName()); } @Before public void setup() throws Exception { - try { - - String pwd = System.getProperty("user.dir"); - - File privilegeConfigFile = new File(pwd + "/config/PrivilegeConfig.xml"); - - // initialize privilege - privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); - - } catch (Exception e) { - logger.error(e.getMessage(), e); - - throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); - } - } - - private void login(String username, byte[] password) { - Certificate certificate = privilegeHandler.authenticate(username, password); - assertTrue("Certificate is null!", certificate != null); - PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); - this.ctx = privilegeContext; - } - - private void logout() { - if (this.ctx != null) { - try { - PrivilegeContext privilegeContext = this.ctx; - this.ctx = null; - privilegeHandler.invalidateSession(privilegeContext.getCertificate()); - } catch (PrivilegeException e) { - String msg = "There is no PrivilegeContext currently bound to the ThreadLocal!"; - if (!e.getMessage().equals(msg)) - throw e; - } - } + initialize(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml"); } @Test @@ -258,8 +177,8 @@ public class PrivilegeTest { @Test public void testPerformSystemRestrictableFailPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); - this.exception - .expectMessage("User system_admin does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction"); + this.exception.expectMessage( + "User system_admin does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -277,8 +196,8 @@ public class PrivilegeTest { @Test public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); - this.exception - .expectMessage("User system_admin2 does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction needed for Restrictable ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); + this.exception.expectMessage( + "User system_admin2 does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction needed for Restrictable ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -398,8 +317,8 @@ public class PrivilegeTest { Certificate certificate = this.ctx.getCertificate(); - UserRep selectorRep = new UserRep(null, null, null, null, null, new HashSet<>( - Arrays.asList("PrivilegeAdmin")), null, null); + UserRep selectorRep = new UserRep(null, null, null, null, null, + new HashSet<>(Arrays.asList("PrivilegeAdmin")), null, null); List users = privilegeHandler.queryUsers(certificate, selectorRep); assertEquals(1, users.size()); assertEquals(ADMIN, users.get(0).getUsername()); @@ -590,8 +509,8 @@ public class PrivilegeTest { PrivilegeRep passwordRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD, PRIVILEGE_USER_ACCESS, false, Collections.emptySet(), Collections.emptySet()); - PrivilegeRep localeRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_LOCALE, - PRIVILEGE_USER_ACCESS, false, Collections.emptySet(), Collections.emptySet()); + PrivilegeRep localeRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_LOCALE, PRIVILEGE_USER_ACCESS, + false, Collections.emptySet(), Collections.emptySet()); RoleRep roleRep = new RoleRep(ROLE_CHANGE_PW, Arrays.asList(passwordRep, localeRep)); @@ -818,8 +737,8 @@ public class PrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep(null, BOB, "Bob", "Newman", UserState.NEW, new HashSet( - Arrays.asList(ROLE_MY)), null, new HashMap()); + UserRep userRep = new UserRep(null, BOB, "Bob", "Newman", UserState.NEW, + new HashSet(Arrays.asList(ROLE_MY)), null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + BOB); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 5186dba16..4dfc029b3 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -120,7 +120,7 @@ public class XmlTest { assertNotNull(containerModel.getPersistenceHandlerClassName()); assertNotNull(containerModel.getPersistenceHandlerParameterMap()); - assertEquals(2, containerModel.getParameterMap().size()); + assertEquals(6, containerModel.getParameterMap().size()); assertEquals(3, containerModel.getPolicies().size()); assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); From 1de56d00434c42d20c9b289eb3d43725a97cfed3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Oct 2015 17:31:58 +0200 Subject: [PATCH 418/457] [New] Handling illegal key size in AesCryptoHelperTest --- .../utils/helper/AesCryptoHelperTest.java | 163 ++++++++++++------ 1 file changed, 111 insertions(+), 52 deletions(-) diff --git a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java index 797f79d86..f8207a12d 100644 --- a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java @@ -9,11 +9,16 @@ import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class AesCryptoHelperTest { + private static final Logger logger = LoggerFactory.getLogger(AesCryptoHelperTest.class); + private static final char[] password = "A2589309-17AE-4819-B9E4-E577CFA7778F".toCharArray(); private static final byte[] salt; @@ -28,92 +33,146 @@ public class AesCryptoHelperTest { @Test public void shouldWrapStreams() throws Exception { - byte[] clearTextBytes = "Some text".getBytes(); + try { + byte[] clearTextBytes = "Some text".getBytes(); - // encrypt data - ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); - OutputStream outputStream = AesCryptoHelper.wrapEncrypt(password, salt, encryptedOut); - outputStream.write(clearTextBytes); - outputStream.flush(); - outputStream.close(); + // encrypt data + ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); + OutputStream outputStream = AesCryptoHelper.wrapEncrypt(password, salt, encryptedOut); + outputStream.write(clearTextBytes); + outputStream.flush(); + outputStream.close(); - // decrypt data - byte[] encryptedBytes = encryptedOut.toByteArray(); - ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes); - InputStream inputStream = AesCryptoHelper.wrapDecrypt(password, salt, encryptedIn); + // decrypt data + byte[] encryptedBytes = encryptedOut.toByteArray(); + ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes); + InputStream inputStream = AesCryptoHelper.wrapDecrypt(password, salt, encryptedIn); - ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); - byte[] readBuffer = new byte[64]; - int read = 0; - while ((read = inputStream.read(readBuffer)) != -1) { - decryptedOut.write(readBuffer, 0, read); + ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); + byte[] readBuffer = new byte[64]; + int read = 0; + while ((read = inputStream.read(readBuffer)) != -1) { + decryptedOut.write(readBuffer, 0, read); + } + + byte[] decryptedBytes = decryptedOut.toByteArray(); + + assertArrayEquals(clearTextBytes, decryptedBytes); + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidKeyException + && e.getCause().getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; } - - byte[] decryptedBytes = decryptedOut.toByteArray(); - - assertArrayEquals(clearTextBytes, decryptedBytes); } @Test public void shouldEncryptBytes() { + try { - byte[] clearTextBytes = "Some text".getBytes(); + byte[] clearTextBytes = "Some text".getBytes(); - byte[] encryptedBytes = AesCryptoHelper.encrypt(password, salt, clearTextBytes); - byte[] decryptedBytes = AesCryptoHelper.decrypt(password, salt, encryptedBytes); + byte[] encryptedBytes = AesCryptoHelper.encrypt(password, salt, clearTextBytes); + byte[] decryptedBytes = AesCryptoHelper.decrypt(password, salt, encryptedBytes); - assertArrayEquals(clearTextBytes, decryptedBytes); + assertArrayEquals(clearTextBytes, decryptedBytes); + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidKeyException + && e.getCause().getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; + } } @Test public void shouldEncryptShortFile() { - // file to be encrypted - String clearTextFileS = "src/test/resources/crypto_test_short.txt"; - // encrypted file - String encryptedFileS = "target/encrypted_short.aes"; - // encrypted file - String decryptedFileS = "target/decrypted_short.txt"; + try { - testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_short.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_short.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_short.txt"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidKeyException + && e.getCause().getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; + } } @Test public void shouldEncryptMiddleFile() { + try { - // file to be encrypted - String clearTextFileS = "src/test/resources/crypto_test_middle.txt"; - // encrypted file - String encryptedFileS = "target/encrypted_middle.aes"; - // encrypted file - String decryptedFileS = "target/decrypted_middle.txt"; + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_middle.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_middle.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_middle.txt"; - testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidKeyException + && e.getCause().getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; + } } @Test public void shouldEncryptLongFile() { + try { - // file to be encrypted - String clearTextFileS = "src/test/resources/crypto_test_long.txt"; - // encrypted file - String encryptedFileS = "target/encrypted_long.aes"; - // encrypted file - String decryptedFileS = "target/decrypted_long.txt"; + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_long.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_long.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_long.txt"; - testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidKeyException + && e.getCause().getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; + } } @Test public void shouldEncryptBinaryFile() { - // file to be encrypted - String clearTextFileS = "src/test/resources/crypto_test_image.ico"; - // encrypted file - String encryptedFileS = "target/encrypted_image.aes"; - // encrypted file - String decryptedFileS = "target/decrypted_image.ico"; + try { - testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_image.ico"; + // encrypted file + String encryptedFileS = "target/encrypted_image.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_image.ico"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + + } catch (RuntimeException e) { + if (e.getCause() instanceof InvalidKeyException + && e.getCause().getMessage().equals("Illegal key size or default parameters")) + logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); + else + throw e; + } } private static void testCrypto(String clearTextFileS, String encryptedFileS, String decryptedFileS) { From d0691e4d35220d94d29d642694749524b66fc66c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Oct 2015 17:39:06 +0200 Subject: [PATCH 419/457] [New] Handling illegal key size in AesCryptoHelperTest --- .../utils/helper/ExceptionHelper.java | 16 +++ .../utils/helper/AesCryptoHelperTest.java | 115 +++++++----------- 2 files changed, 57 insertions(+), 74 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java b/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java index 916489e71..35bb99a6d 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java @@ -95,4 +95,20 @@ public class ExceptionHelper { String root = formatExceptionMessage(t.getCause()); return getExceptionMessage(t) + "\ncause:\n" + root; } + + /** + * Returns the root cause for the given {@link Throwable} + * + * @param t + * the {@link Throwable} for which to get the root cause + * + * @return the root cause of the given {@link Throwable} + */ + public static Throwable getRootCause(Throwable t) { + while (t.getCause() != null) { + t = t.getCause(); + } + + return t; + } } diff --git a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java index f8207a12d..8e6162040 100644 --- a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java @@ -9,7 +9,6 @@ import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.security.InvalidKeyException; import org.junit.Test; import org.slf4j.Logger; @@ -59,8 +58,7 @@ public class AesCryptoHelperTest { assertArrayEquals(clearTextBytes, decryptedBytes); } catch (RuntimeException e) { - if (e.getCause() instanceof InvalidKeyException - && e.getCause().getMessage().equals("Illegal key size or default parameters")) + if (ExceptionHelper.getRootCause(e).getMessage().equals("Illegal key size or default parameters")) logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); else throw e; @@ -78,8 +76,7 @@ public class AesCryptoHelperTest { assertArrayEquals(clearTextBytes, decryptedBytes); } catch (RuntimeException e) { - if (e.getCause() instanceof InvalidKeyException - && e.getCause().getMessage().equals("Illegal key size or default parameters")) + if (ExceptionHelper.getRootCause(e).getMessage().equals("Illegal key size or default parameters")) logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); else throw e; @@ -88,99 +85,69 @@ public class AesCryptoHelperTest { @Test public void shouldEncryptShortFile() { - try { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_short.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_short.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_short.txt"; - // file to be encrypted - String clearTextFileS = "src/test/resources/crypto_test_short.txt"; - // encrypted file - String encryptedFileS = "target/encrypted_short.aes"; - // encrypted file - String decryptedFileS = "target/decrypted_short.txt"; - - testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); - - } catch (RuntimeException e) { - if (e.getCause() instanceof InvalidKeyException - && e.getCause().getMessage().equals("Illegal key size or default parameters")) - logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); - else - throw e; - } + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); } @Test public void shouldEncryptMiddleFile() { - try { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_middle.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_middle.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_middle.txt"; - // file to be encrypted - String clearTextFileS = "src/test/resources/crypto_test_middle.txt"; - // encrypted file - String encryptedFileS = "target/encrypted_middle.aes"; - // encrypted file - String decryptedFileS = "target/decrypted_middle.txt"; - - testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); - - } catch (RuntimeException e) { - if (e.getCause() instanceof InvalidKeyException - && e.getCause().getMessage().equals("Illegal key size or default parameters")) - logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); - else - throw e; - } + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); } @Test public void shouldEncryptLongFile() { - try { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_long.txt"; + // encrypted file + String encryptedFileS = "target/encrypted_long.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_long.txt"; - // file to be encrypted - String clearTextFileS = "src/test/resources/crypto_test_long.txt"; - // encrypted file - String encryptedFileS = "target/encrypted_long.aes"; - // encrypted file - String decryptedFileS = "target/decrypted_long.txt"; - - testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); - - } catch (RuntimeException e) { - if (e.getCause() instanceof InvalidKeyException - && e.getCause().getMessage().equals("Illegal key size or default parameters")) - logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); - else - throw e; - } + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); } @Test public void shouldEncryptBinaryFile() { + // file to be encrypted + String clearTextFileS = "src/test/resources/crypto_test_image.ico"; + // encrypted file + String encryptedFileS = "target/encrypted_image.aes"; + // encrypted file + String decryptedFileS = "target/decrypted_image.ico"; + + testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + + } + + private static void testCrypto(String clearTextFileS, String encryptedFileS, String decryptedFileS) { try { - // file to be encrypted - String clearTextFileS = "src/test/resources/crypto_test_image.ico"; - // encrypted file - String encryptedFileS = "target/encrypted_image.aes"; - // encrypted file - String decryptedFileS = "target/decrypted_image.ico"; + AesCryptoHelper.encrypt(password, salt, clearTextFileS, encryptedFileS); + AesCryptoHelper.decrypt(password, salt, encryptedFileS, decryptedFileS); - testCrypto(clearTextFileS, encryptedFileS, decryptedFileS); + String inputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(clearTextFileS))); + String doutputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(decryptedFileS))); + assertEquals(inputSha256, doutputSha256); } catch (RuntimeException e) { - if (e.getCause() instanceof InvalidKeyException - && e.getCause().getMessage().equals("Illegal key size or default parameters")) + if (ExceptionHelper.getRootCause(e).getMessage().equals("Illegal key size or default parameters")) logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); else throw e; } } - - private static void testCrypto(String clearTextFileS, String encryptedFileS, String decryptedFileS) { - AesCryptoHelper.encrypt(password, salt, clearTextFileS, encryptedFileS); - AesCryptoHelper.decrypt(password, salt, encryptedFileS, decryptedFileS); - - String inputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(clearTextFileS))); - String doutputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(decryptedFileS))); - assertEquals(inputSha256, doutputSha256); - } } From 19331b964812a1d9225f651375aa43e1ac2a0a8f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Oct 2015 18:20:25 +0200 Subject: [PATCH 420/457] [Fix] Simply delete the sessions file if not readable on load --- .../eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 818e3fb17..2f2253ee3 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1089,7 +1089,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { certificateStubs = reader.read(); } catch (Exception e) { - throw new PrivilegeException("Failed to load sessions!", e); + logger.error("Failed to load sessions!", e); + this.persistSessionsPath.delete(); + return false; } if (certificateStubs.isEmpty()) { From d5491e4f0d0106866eea4f59c32826aa20a9d139 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 23 Oct 2015 18:21:02 +0200 Subject: [PATCH 421/457] [New] Use InvalidCredentialsException --- .../base/InvalidCredentialsException.java | 19 +++++++++++++++++++ .../handler/DefaultPrivilegeHandler.java | 13 +++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java diff --git a/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java b/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java new file mode 100644 index 000000000..013e9dce8 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java @@ -0,0 +1,19 @@ +package ch.eitchnet.privilege.base; + +/** + * Exception thrown if the given credentials are invalid + * + * @author Robert von Burg + */ +public class InvalidCredentialsException extends AccessDeniedException { + + private static final long serialVersionUID = 1L; + + /** + * @param msg + * the message to accompany the exception + */ + public InvalidCredentialsException(String msg) { + super(msg); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2f2253ee3..649d2abea 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -42,6 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.InvalidCredentialsException; import ch.eitchnet.privilege.base.PrivilegeConflictResolution; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -1141,8 +1142,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @throws AccessDeniedException * if anything is wrong with the credentials or the user state + * @throws InvalidCredentialsException + * if the given credentials are invalid, the user does not exist, or has no password set */ - private User checkCredentialsAndUserState(String username, byte[] password) throws AccessDeniedException { + private User checkCredentialsAndUserState(String username, byte[] password) + throws InvalidCredentialsException, AccessDeniedException { // and validate the password validatePassword(password); @@ -1155,14 +1159,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // no user means no authentication if (user == null) { String msg = MessageFormat.format("There is no user defined with the username {0}", username); //$NON-NLS-1$ - throw new AccessDeniedException(msg); + throw new InvalidCredentialsException(msg); } // make sure not a system user - they may not login in if (user.getUserState() == UserState.SYSTEM) { String msg = "User {0} is a system user and may not login!"; //$NON-NLS-1$ msg = MessageFormat.format(msg, username); - throw new AccessDeniedException(msg); + throw new InvalidCredentialsException(msg); } // validate password @@ -1171,7 +1175,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new AccessDeniedException( MessageFormat.format("User {0} has no password and may not login!", username)); //$NON-NLS-1$ if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ + throw new InvalidCredentialsException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ // validate if user is allowed to login // this also capture the trying to login of SYSTEM user @@ -1180,6 +1184,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { msg = MessageFormat.format(msg, username, UserState.ENABLED); throw new AccessDeniedException(msg); } + return user; } From 540dbeab32f542b7f4e3747c191d06c66b5108a9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 30 Nov 2015 21:26:01 +0100 Subject: [PATCH 422/457] [New] Added StringHelper.replacePropertiesIn() with special prefix - and added a test --- .../eitchnet/utils/helper/StringHelper.java | 31 +++- .../utils/helper/ReplacePropertiesInTest.java | 138 ++++++++++++++++++ 2 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java index 8f82b3487..a7a3fcc94 100644 --- a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/StringHelper.java @@ -385,6 +385,27 @@ public class StringHelper { */ public static String replacePropertiesIn(Properties properties, String value) { + return replacePropertiesIn(properties, '$', value); + } + + /** + * Traverses the given string searching for occurrences of prefix{...} sequences. Theses sequences are + * replaced with a {@link Properties#getProperty(String)} value if such a value exists in the properties map. If the + * value of the sequence is not in the properties, then the sequence is not replaced + * + * @param properties + * the {@link Properties} in which to get the value + * @param prefix + * the prefix to use, for instance use $ to replace occurrences of ${...} + * @param value + * the value in which to replace any system properties + * + * @return a new string with all defined properties replaced or if an error occurred the original value is returned + */ + public static String replacePropertiesIn(Properties properties, char prefix, String value) { + + String prefixS = String.valueOf(prefix); + // get a copy of the value String tmpValue = value; @@ -393,7 +414,7 @@ public class StringHelper { int stop = 0; // loop on $ character positions - while ((pos = tmpValue.indexOf('$', pos + 1)) != -1) { + while ((pos = tmpValue.indexOf(prefix, pos + 1)) != -1) { // if pos+1 is not { character then continue if (tmpValue.charAt(pos + 1) != '{') { @@ -414,9 +435,9 @@ public class StringHelper { String sequence = tmpValue.substring(pos + 2, stop); // make sure sequence doesn't contain $ { } characters - if (sequence.contains("$") || 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}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, pos, stop, sequence); + 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; @@ -432,7 +453,7 @@ public class StringHelper { } // property exists, so replace in value - tmpValue = tmpValue.replace("${" + sequence + "}", property); //$NON-NLS-1$ //$NON-NLS-2$ + tmpValue = tmpValue.replace(prefixS + "{" + sequence + "}", property); //$NON-NLS-1$ //$NON-NLS-2$ } return tmpValue; diff --git a/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java b/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java new file mode 100644 index 000000000..f9525899d --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java @@ -0,0 +1,138 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertEquals; + +import java.util.Properties; + +import org.junit.Test; + +public class ReplacePropertiesInTest { + + @Test + public void shouldReplaceProps1() { + + String expr = "bla ${foo}"; + String expected = "bla bar"; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps2() { + + String expr = "${foo} bla "; + String expected = "bar bla "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps3() { + + String expr = "bla ${foo} "; + String expected = "bla bar "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps4() { + + String expr = "bla${foo}abr"; + String expected = "blabarabr"; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps5() { + + String expr = "bla '${foo}' "; + String expected = "bla 'bar' "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps6() { + + String expr = "${foo}bla ${foo} "; + String expected = "barbla bar "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps7() { + + String expr = "${foo}bla ${food} "; + String expected = "barbla foofoo "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + properties.setProperty("food", "foofoo"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps8() { + + String expr = "foo"; + String expected = "foo"; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + + String result = StringHelper.replacePropertiesIn(properties, expr); + + assertEquals(expected, result); + } + + @Test + public void shouldReplaceProps9() { + + String expr = "%{foo}bla %{food} "; + String expected = "barbla foofoo "; + + Properties properties = new Properties(); + properties.setProperty("foo", "bar"); + properties.setProperty("food", "foofoo"); + + String result = StringHelper.replacePropertiesIn(properties, '%', expr); + + assertEquals(expected, result); + } +} From 2008da00a357accb2783147acd0b6bd67580f261 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 2 Dec 2015 22:03:22 +0100 Subject: [PATCH 423/457] [Minor] added checking that working dir is a dir in ProcessHelper --- .../java/ch/eitchnet/utils/helper/ProcessHelper.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index a4e3f3294..020422deb 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -40,8 +40,8 @@ public class ProcessHelper { final int[] returnValue = new int[1]; try (final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); - final BufferedReader inputStream = new BufferedReader(new InputStreamReader( - process.getInputStream()));) { + final BufferedReader inputStream = new BufferedReader( + new InputStreamReader(process.getInputStream()));) { Thread errorIn = new Thread("errorIn") { //$NON-NLS-1$ @Override @@ -79,8 +79,8 @@ public class ProcessHelper { public static ProcessResult runCommand(File workingDirectory, String... commandAndArgs) { - if (!workingDirectory.exists()) { - String msg = "Working directory does not exist at {0}"; //$NON-NLS-1$ + if (!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); } @@ -99,8 +99,8 @@ public class ProcessHelper { int[] returnValue = new int[1]; try (final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); - final BufferedReader inputStream = new BufferedReader(new InputStreamReader( - process.getInputStream()));) { + final BufferedReader inputStream = new BufferedReader( + new InputStreamReader(process.getInputStream()));) { Thread errorIn = new Thread("errorIn") { //$NON-NLS-1$ @Override From a4b1857b8432ca1a7105cdfd945e597cb6b61dff Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 4 Dec 2015 17:10:14 +0100 Subject: [PATCH 424/457] [Minor] Added a timeout in ProcessHelper --- .../eitchnet/utils/helper/ProcessHelper.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index 020422deb..40dfba36e 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.text.MessageFormat; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,6 +79,11 @@ public class ProcessHelper { } public static ProcessResult runCommand(File workingDirectory, String... commandAndArgs) { + return runCommand(1, TimeUnit.MINUTES, workingDirectory, commandAndArgs); + } + + public static ProcessResult runCommand(long timeout, TimeUnit unit, File workingDirectory, + String... commandAndArgs) { if (!workingDirectory.isDirectory()) { String msg = "Working directory does not exist or is not a directory at {0}"; //$NON-NLS-1$ @@ -98,27 +104,19 @@ public class ProcessHelper { final Process process = processBuilder.start(); int[] returnValue = new int[1]; - try (final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); - final BufferedReader inputStream = new BufferedReader( - new InputStreamReader(process.getInputStream()));) { + try (BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); + BufferedReader inputStream = new BufferedReader(new InputStreamReader(process.getInputStream()));) { - Thread errorIn = new Thread("errorIn") { //$NON-NLS-1$ - @Override - public void run() { - readStream(sb, "[ERROR] ", errorStream); //$NON-NLS-1$ - } - }; + Thread errorIn = new Thread(() -> readStream(sb, "[ERROR] ", errorStream), "errorIn"); errorIn.start(); - Thread infoIn = new Thread("infoIn") { //$NON-NLS-1$ - @Override - public void run() { - readStream(sb, "[INFO] ", inputStream); //$NON-NLS-1$ - } - }; + Thread infoIn = new Thread(() -> readStream(sb, "[INFO] ", inputStream), "infoIn"); infoIn.start(); - returnValue[0] = process.waitFor(); + boolean ok = process.waitFor(timeout, unit); + if (!ok) + sb.append("[ERROR] Command failed to end before timeout or failed to execute."); + returnValue[0] = process.exitValue(); errorIn.join(100l); infoIn.join(100l); From 041b9ec2e5327ef558a52a6621e76286c4e56629 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 4 Dec 2015 17:26:45 +0100 Subject: [PATCH 425/457] [Minor] Added a timeout in ProcessHelper --- src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index 40dfba36e..b1b2906cb 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -20,7 +20,9 @@ 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; @@ -101,6 +103,9 @@ public class ProcessHelper { processBuilder.environment(); processBuilder.directory(workingDirectory); + long start = System.nanoTime(); + logger.info(MessageFormat.format("Starting command (Timeout {0}m) {1}", unit.toMinutes(timeout), + Arrays.stream(commandAndArgs).collect(Collectors.joining(" ")))); final Process process = processBuilder.start(); int[] returnValue = new int[1]; @@ -123,6 +128,7 @@ public class ProcessHelper { 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) { From 3b4d9f366188725e786eceb807bc51d8112e1518 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 4 Dec 2015 18:18:41 +0100 Subject: [PATCH 426/457] [Minor] Added a timeout in ProcessHelper --- .../eitchnet/utils/helper/ProcessHelper.java | 72 ++++++------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index b1b2906cb..680e73fdb 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -34,60 +34,22 @@ public class ProcessHelper { private static final Logger logger = LoggerFactory.getLogger(ProcessHelper.class); - public static ProcessResult runCommand(String command) { - final StringBuffer sb = new StringBuffer(); - sb.append("=====================================\n"); //$NON-NLS-1$ - try { - - final Process process = Runtime.getRuntime().exec(command); - final int[] returnValue = new int[1]; - - try (final BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); - final BufferedReader inputStream = new BufferedReader( - new InputStreamReader(process.getInputStream()));) { - - Thread errorIn = new Thread("errorIn") { //$NON-NLS-1$ - @Override - public void run() { - readStream(sb, "[ERROR] ", errorStream); //$NON-NLS-1$ - } - }; - errorIn.start(); - - Thread infoIn = new Thread("infoIn") { //$NON-NLS-1$ - @Override - public void run() { - readStream(sb, "[INFO] ", inputStream); //$NON-NLS-1$ - } - }; - infoIn.start(); - - returnValue[0] = process.waitFor(); - - errorIn.join(100l); - infoIn.join(100l); - sb.append("=====================================\n"); //$NON-NLS-1$ - } - return new ProcessResult(returnValue[0], sb.toString(), null); - - } catch (IOException e) { - String msg = MessageFormat.format("Failed to perform command: {0}", e.getMessage()); //$NON-NLS-1$ - throw new RuntimeException(msg, e); - } 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 ProcessResult runCommand(String... commandAndArgs) { + return runCommand(null, commandAndArgs); } public static ProcessResult runCommand(File workingDirectory, String... commandAndArgs) { return runCommand(1, TimeUnit.MINUTES, 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.isDirectory()) { + 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); @@ -99,14 +61,14 @@ public class ProcessHelper { sb.append("=====================================\n"); //$NON-NLS-1$ try { - ProcessBuilder processBuilder = new ProcessBuilder(commandAndArgs); - processBuilder.environment(); - processBuilder.directory(workingDirectory); + ProcessBuilder pb = new ProcessBuilder(commandAndArgs); + pb.environment(); + pb.directory(workingDirectory); long start = System.nanoTime(); logger.info(MessageFormat.format("Starting command (Timeout {0}m) {1}", unit.toMinutes(timeout), Arrays.stream(commandAndArgs).collect(Collectors.joining(" ")))); - final Process process = processBuilder.start(); + final Process process = pb.start(); int[] returnValue = new int[1]; try (BufferedReader errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream())); @@ -120,8 +82,16 @@ public class ProcessHelper { boolean ok = process.waitFor(timeout, unit); if (!ok) - sb.append("[ERROR] Command failed to end before timeout or failed to execute."); - returnValue[0] = process.exitValue(); + 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); From 4e7063ffdebe2e952da60a9e41311c7f44ce812b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 4 Dec 2015 18:19:13 +0100 Subject: [PATCH 427/457] [Minor] Added a timeout in ProcessHelper --- src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index 680e73fdb..d779d9492 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -39,7 +39,7 @@ public class ProcessHelper { } public static ProcessResult runCommand(File workingDirectory, String... commandAndArgs) { - return runCommand(1, TimeUnit.MINUTES, workingDirectory, commandAndArgs); + return runCommand(30, TimeUnit.SECONDS, workingDirectory, commandAndArgs); } public static ProcessResult runCommand(long timeout, TimeUnit unit, String... commandAndArgs) { From 9e368bd5c60ac765c2208454fb8a270e2826b749 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 4 Dec 2015 18:21:41 +0100 Subject: [PATCH 428/457] [Minor] Added a timeout in ProcessHelper --- src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index d779d9492..1b8af1325 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -66,8 +66,8 @@ public class ProcessHelper { pb.directory(workingDirectory); long start = System.nanoTime(); - logger.info(MessageFormat.format("Starting command (Timeout {0}m) {1}", unit.toMinutes(timeout), - Arrays.stream(commandAndArgs).collect(Collectors.joining(" ")))); + logger.info(MessageFormat.format("Starting command (Timeout {0} {1}) {2}", unit.toMinutes(timeout), + unit.name(), Arrays.stream(commandAndArgs).collect(Collectors.joining(" ")))); final Process process = pb.start(); int[] returnValue = new int[1]; From 3336cfda1b1311ad43c1625288705573b5ba471b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 4 Dec 2015 18:24:56 +0100 Subject: [PATCH 429/457] [Minor] Added a timeout in ProcessHelper --- src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java index 1b8af1325..2d84b0624 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java @@ -66,8 +66,8 @@ public class ProcessHelper { pb.directory(workingDirectory); long start = System.nanoTime(); - logger.info(MessageFormat.format("Starting command (Timeout {0} {1}) {2}", unit.toMinutes(timeout), - unit.name(), Arrays.stream(commandAndArgs).collect(Collectors.joining(" ")))); + 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]; From c947260853d57f13cd9c563fa81fa07dd0493d42 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 10 Feb 2016 20:28:11 +0100 Subject: [PATCH 430/457] [Minor] Code cleanup --- .../communication/ConnectionMode.java | 3 ++ .../communication/SimpleMessageArchive.java | 4 +-- .../ch/eitchnet/db/DbSchemaVersionCheck.java | 7 +++-- src/main/java/ch/eitchnet/utils/Version.java | 27 +++++++---------- .../ch/eitchnet/utils/collections/Paging.java | 2 +- .../utils/helper/ExceptionHelper.java | 13 ++++---- .../ch/eitchnet/utils/helper/FileHelper.java | 4 +-- .../utils/iso8601/ISO8601Duration.java | 30 +++++++++---------- .../utils/iso8601/ISO8601Worktime.java | 18 +++++------ .../utils/objectfilter/ObjectFilter.java | 24 +++++++-------- .../collections/DefaultedHashMapTest.java | 2 +- .../utils/helper/AesCryptoHelperTest.java | 29 ++++++++++-------- .../GenerateReverseBaseEncodingAlphabets.java | 2 +- 13 files changed, 79 insertions(+), 86 deletions(-) diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMode.java b/src/main/java/ch/eitchnet/communication/ConnectionMode.java index 59d5d89d6..8cf2e2640 100644 --- a/src/main/java/ch/eitchnet/communication/ConnectionMode.java +++ b/src/main/java/ch/eitchnet/communication/ConnectionMode.java @@ -39,6 +39,7 @@ public enum ConnectionMode { * or do any other kind of work */ OFF { + @Override public boolean isSimulation() { return false; } @@ -50,6 +51,7 @@ public enum ConnectionMode { * re-established should an {@link IOException} occur */ ON { + @Override public boolean isSimulation() { return false; } @@ -60,6 +62,7 @@ public enum ConnectionMode { * {@link CommunicationConnection} accepts messages, but silently swallows them, instead of processing them */ SIMULATION { + @Override public boolean isSimulation() { return true; } diff --git a/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java b/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java index 27414f3db..00ad303b6 100644 --- a/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java +++ b/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java @@ -77,7 +77,7 @@ public class SimpleMessageArchive implements IoMessageArchive { @Override public synchronized List getBy(String connectionId) { - List all = new ArrayList(); + List all = new ArrayList<>(); for (IoMessage msg : this.messageArchive) { if (msg.getConnectionId().equals(connectionId)) all.add(msg); @@ -87,7 +87,7 @@ public class SimpleMessageArchive implements IoMessageArchive { @Override public synchronized List getBy(String connectionId, CommandKey key) { - List all = new ArrayList(); + List all = new ArrayList<>(); for (IoMessage msg : this.messageArchive) { if (msg.getConnectionId().equals(connectionId) && msg.getKey().equals(key)) all.add(msg); diff --git a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java index d621a4c58..8f870fec0 100644 --- a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java +++ b/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java @@ -162,9 +162,10 @@ public class DbSchemaVersionCheck { Version currentVersion = null; try (PreparedStatement st = con.prepareStatement(sql)) { st.setString(1, app); - ResultSet rs = st.executeQuery(); - if (rs.next()) - currentVersion = Version.valueOf(rs.getString(2)); + try (ResultSet rs = st.executeQuery()) { + if (rs.next()) + currentVersion = Version.valueOf(rs.getString(2)); + } } return currentVersion; diff --git a/src/main/java/ch/eitchnet/utils/Version.java b/src/main/java/ch/eitchnet/utils/Version.java index e3507bbfb..7b6b93dd6 100644 --- a/src/main/java/ch/eitchnet/utils/Version.java +++ b/src/main/java/ch/eitchnet/utils/Version.java @@ -125,14 +125,10 @@ public class Version implements Comparable { * 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) { - if (qualifier == null) { - qualifier = ""; - } - this.major = major; this.minor = minor; this.micro = micro; - this.qualifier = qualifier; + this.qualifier = qualifier == null ? "" : qualifier; this.versionString = null; validate(); } @@ -155,8 +151,8 @@ public class Version implements Comparable { String qual = StringHelper.EMPTY; try { - StringTokenizer st = new StringTokenizer(version, SEPARATOR + MAVEN_QUALIFIER_SEPARATOR - + OSGI_QUALIFIER_SEPARATOR, true); + StringTokenizer st = new StringTokenizer(version, + SEPARATOR + MAVEN_QUALIFIER_SEPARATOR + OSGI_QUALIFIER_SEPARATOR, true); maj = Integer.parseInt(st.nextToken()); if (st.hasMoreTokens()) { // minor @@ -248,16 +244,14 @@ public class Version implements Comparable { * If {@code version} is improperly formatted. */ public static Version valueOf(String version) { - if (version == null) { + if (version == null) return emptyVersion; - } - version = version.trim(); - if (version.length() == 0) { + String trimmedVersion = version.trim(); + if (trimmedVersion.length() == 0) return emptyVersion; - } - return new Version(version); + return new Version(trimmedVersion); } /** @@ -478,12 +472,11 @@ public class Version implements Comparable { private String createQualifier(boolean withOsgiStyle) { if (this.qualifier.equals(MAVEN_SNAPSHOT_QUALIFIER) || this.qualifier.equals(OSGI_SNAPSHOT_QUALIFIER)) { - if (withOsgiStyle) { + if (withOsgiStyle) return OSGI_SNAPSHOT_QUALIFIER; - } else { - return MAVEN_SNAPSHOT_QUALIFIER; - } + return MAVEN_SNAPSHOT_QUALIFIER; } + return this.qualifier; } diff --git a/src/main/java/ch/eitchnet/utils/collections/Paging.java b/src/main/java/ch/eitchnet/utils/collections/Paging.java index 7031c34e4..dde14cd4f 100644 --- a/src/main/java/ch/eitchnet/utils/collections/Paging.java +++ b/src/main/java/ch/eitchnet/utils/collections/Paging.java @@ -89,7 +89,7 @@ public class Paging { */ public static Paging asPage(List list, int pageSize, int page) { - Paging paging = new Paging(pageSize, page); + Paging paging = new Paging<>(pageSize, page); paging.nrOfElements = list.size(); if (paging.pageSize <= 0 || paging.pageToReturn <= 0) { diff --git a/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java b/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java index 35bb99a6d..a498c20cb 100644 --- a/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java @@ -54,10 +54,8 @@ public class ExceptionHelper { * @return */ public static String getExceptionMessageWithCauses(Throwable t) { - - if (t.getCause() == null) { + if (t.getCause() == null) return getExceptionMessage(t); - } String root = getExceptionMessageWithCauses(t.getCause()); return getExceptionMessage(t) + "\n" + root; @@ -87,10 +85,8 @@ public class ExceptionHelper { * @return a string representation of the given {@link Throwable}'s messages including causes */ public static String formatExceptionMessage(Throwable t) { - - if (t.getCause() == null) { + if (t.getCause() == null) return getExceptionMessage(t); - } String root = formatExceptionMessage(t.getCause()); return getExceptionMessage(t) + "\ncause:\n" + root; @@ -99,12 +95,13 @@ public class ExceptionHelper { /** * Returns the root cause for the given {@link Throwable} * - * @param t + * @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 t) { + public static Throwable getRootCause(Throwable throwable) { + Throwable t = throwable; while (t.getCause() != null) { t = t.getCause(); } diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java index 397ed6382..57cf58bc9 100644 --- a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/src/main/java/ch/eitchnet/utils/helper/FileHelper.java @@ -378,7 +378,7 @@ public class FileHelper { File file = files.get(i); // get list of parents for this file - List parents = new ArrayList(); + List parents = new ArrayList<>(); File parent = file.getParentFile(); while (parent != null) { parents.add(parent); @@ -389,7 +389,7 @@ public class FileHelper { // and now the same for the next file File fileNext = files.get(i + 1); - List parentsNext = new ArrayList(); + List parentsNext = new ArrayList<>(); File parentNext = fileNext.getParentFile(); while (parentNext != null) { parentsNext.add(parentNext); diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java index 61788c737..61e63f94d 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java @@ -54,9 +54,13 @@ public class ISO8601Duration implements DurationFormat { * @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'); + 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; @@ -74,17 +78,15 @@ public class ISO8601Duration implements DurationFormat { char duration = isostring.charAt(unitIndex); switch (duration) { case 'S': - if (isostring.substring(0, unitIndex).contains("T")) { + if (isostring.substring(0, unitIndex).contains("T")) return SECOND; - } else - throw new NumberFormatException(duration - + " is not a valid unit of time in ISO8601 without a preceeding T (e.g.: PT1S)"); + 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")) { + if (isostring.substring(0, unitIndex).contains("T")) return HOUR; - } else - throw new NumberFormatException(duration - + " is not a valid unit of time in ISO8601 without a preceeding T (e.g.: PT1H)"); + 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': @@ -92,11 +94,9 @@ public class ISO8601Duration implements DurationFormat { case 'Y': return YEAR; case 'M': - if (isostring.substring(0, unitIndex).contains("T")) { + if (isostring.substring(0, unitIndex).contains("T")) return MINUTE; - } else { - return MONTH; - } + return MONTH; default: throw new NumberFormatException(duration + " is not a valid unit of time in ISO8601"); } diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java index 31dde2a1c..e107a7be7 100644 --- a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java +++ b/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java @@ -73,17 +73,15 @@ public class ISO8601Worktime implements WorktimeFormat { char duration = isostring.charAt(unitIndex); switch (duration) { case 'S': - if (isostring.substring(0, unitIndex).contains("T")) { + if (isostring.substring(0, unitIndex).contains("T")) return SECOND; - } else - throw new NumberFormatException(duration - + " is not a valid unit of time in ISO8601 without a preceeding T (e.g.: PT1S)"); + 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")) { + if (isostring.substring(0, unitIndex).contains("T")) return HOUR; - } else - throw new NumberFormatException(duration - + " is not a valid unit of time in ISO8601 without a preceeding T (e.g.: PT1H)"); + 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: @@ -143,11 +141,9 @@ public class ISO8601Worktime implements WorktimeFormat { } while (newposition < s.length()); return newResult; - - } else { - throw new NumberFormatException(s + " cannot be parsed to ISO 8601 Duration"); } + throw new NumberFormatException(s + " cannot be parsed to ISO 8601 Duration"); } /** diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java index b2282e9ca..6ea65d1ac 100644 --- a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java @@ -88,8 +88,8 @@ public class ObjectFilter { * Default constructor initializing the filter */ public ObjectFilter() { - this.cache = new HashMap(); - this.keySet = new HashSet(); + this.cache = new HashMap<>(); + this.keySet = new HashSet<>(); } /** @@ -439,7 +439,7 @@ public class ObjectFilter { * @return The list of all objects registered under the given key and that need to be added. */ public List getAdded(String key) { - List addedObjects = new LinkedList(); + List addedObjects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { @@ -460,7 +460,7 @@ public class ObjectFilter { * @return The list of all objects registered under the given key and that need to be added. */ public List getAdded(Class clazz, String key) { - List addedObjects = new LinkedList(); + List addedObjects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.ADD && (objectCache.getKey().equals(key))) { @@ -483,7 +483,7 @@ public class ObjectFilter { * @return The list of all objects registered under the given key and that need to be updated. */ public List getUpdated(String key) { - List updatedObjects = new LinkedList(); + List updatedObjects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { @@ -504,7 +504,7 @@ public class ObjectFilter { * @return The list of all objects registered under the given key and that need to be updated. */ public List getUpdated(Class clazz, String key) { - List updatedObjects = new LinkedList(); + List updatedObjects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.MODIFY && (objectCache.getKey().equals(key))) { @@ -527,7 +527,7 @@ public class ObjectFilter { * @return The list of object registered under the given key that have, as a final action, removal. */ public List getRemoved(String key) { - List removedObjects = new LinkedList(); + List removedObjects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { @@ -548,7 +548,7 @@ public class ObjectFilter { * @return The list of object registered under the given key that have, as a final action, removal. */ public List getRemoved(Class clazz, String key) { - List removedObjects = new LinkedList(); + List removedObjects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getOperation() == Operation.REMOVE && (objectCache.getKey().equals(key))) { @@ -573,7 +573,7 @@ public class ObjectFilter { * @return The list of object registered under the given key that have, as a final action, removal. */ public List getAll(Class clazz, String key) { - List objects = new LinkedList(); + List objects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getKey().equals(key)) { @@ -596,7 +596,7 @@ public class ObjectFilter { * @return The list of all objects that of the given class */ public List getAll(Class clazz) { - List objects = new LinkedList(); + List objects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getObject().getClass() == clazz) { @@ -618,7 +618,7 @@ public class ObjectFilter { * @return The list of objects matching the given key. */ public List getAll(String key) { - List allObjects = new LinkedList(); + List allObjects = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getKey().equals(key)) { @@ -638,7 +638,7 @@ public class ObjectFilter { * @return The list of objects matching the given key. */ public List getCache(String key) { - List allCache = new LinkedList(); + List allCache = new LinkedList<>(); Collection allObjs = this.cache.values(); for (ObjectCache objectCache : allObjs) { if (objectCache.getKey().equals(key)) { diff --git a/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java b/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java index ddf7c6569..1048d10e1 100644 --- a/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java +++ b/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java @@ -33,7 +33,7 @@ public class DefaultedHashMapTest { @Before public void setUp() { - this.map = new DefaultedHashMap("foobar"); + this.map = new DefaultedHashMap<>("foobar"); this.map.put("foo", "foofoo"); } diff --git a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java index 8e6162040..31b069146 100644 --- a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java @@ -37,26 +37,27 @@ public class AesCryptoHelperTest { // encrypt data ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); - OutputStream outputStream = AesCryptoHelper.wrapEncrypt(password, salt, encryptedOut); - outputStream.write(clearTextBytes); - outputStream.flush(); - outputStream.close(); + try (OutputStream outputStream = AesCryptoHelper.wrapEncrypt(password, salt, encryptedOut)) { + outputStream.write(clearTextBytes); + outputStream.flush(); + } // decrypt data byte[] encryptedBytes = encryptedOut.toByteArray(); ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes); - InputStream inputStream = AesCryptoHelper.wrapDecrypt(password, salt, encryptedIn); + try (InputStream inputStream = AesCryptoHelper.wrapDecrypt(password, salt, encryptedIn)) { - ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); - byte[] readBuffer = new byte[64]; - int read = 0; - while ((read = inputStream.read(readBuffer)) != -1) { - decryptedOut.write(readBuffer, 0, read); + ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); + byte[] readBuffer = new byte[64]; + int read = 0; + while ((read = inputStream.read(readBuffer)) != -1) { + decryptedOut.write(readBuffer, 0, read); + } + + byte[] decryptedBytes = decryptedOut.toByteArray(); + assertArrayEquals(clearTextBytes, decryptedBytes); } - byte[] decryptedBytes = decryptedOut.toByteArray(); - - assertArrayEquals(clearTextBytes, decryptedBytes); } catch (RuntimeException e) { if (ExceptionHelper.getRootCause(e).getMessage().equals("Illegal key size or default parameters")) logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); @@ -75,6 +76,7 @@ public class AesCryptoHelperTest { byte[] decryptedBytes = AesCryptoHelper.decrypt(password, salt, encryptedBytes); assertArrayEquals(clearTextBytes, decryptedBytes); + } catch (RuntimeException e) { if (ExceptionHelper.getRootCause(e).getMessage().equals("Illegal key size or default parameters")) logger.warn("YOU ARE MISSING THE UNLIMITED JCE POLICIES and can not do AES encryption!"); @@ -141,6 +143,7 @@ public class AesCryptoHelperTest { String inputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(clearTextFileS))); String doutputSha256 = StringHelper.getHexString(FileHelper.hashFileSha256(new File(decryptedFileS))); + assertEquals(inputSha256, doutputSha256); } catch (RuntimeException e) { diff --git a/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java b/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java index 5a492a2d7..e013c5659 100644 --- a/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java +++ b/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java @@ -39,7 +39,7 @@ public class GenerateReverseBaseEncodingAlphabets { public static String generateReverseAlphabet(String name, byte[] alphabet) { - Map valueToIndex = new HashMap(); + Map valueToIndex = new HashMap<>(); for (byte i = 0; i < alphabet.length; i++) { Byte value = Byte.valueOf(i); Byte key = Byte.valueOf(alphabet[value]); From 461eef4b23498a5ab7324e5c00101d85ab30d191 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 10 Feb 2016 20:33:27 +0100 Subject: [PATCH 431/457] [Minor] Code cleanup --- .gitignore | 2 +- .../handler/DefaultPrivilegeHandler.java | 16 +++++----- .../handler/XmlPersistenceHandler.java | 6 ++-- .../helper/BootstrapConfigurationHelper.java | 6 ++-- .../privilege/helper/PasswordCreaterUI.java | 2 +- .../helper/PrivilegeInitializationHelper.java | 4 +-- .../privilege/model/PrivilegeContext.java | 4 +-- .../internal/PrivilegeContainerModel.java | 2 +- .../model/internal/PrivilegeImpl.java | 4 +-- .../privilege/model/internal/Role.java | 4 +-- .../privilege/model/internal/User.java | 6 ++-- .../xml/PrivilegeConfigSaxReader.java | 4 +-- .../xml/PrivilegeModelSaxReader.java | 20 ++++++------- .../privilege/test/PrivilegeTest.java | 4 +-- .../ch/eitchnet/privilege/test/XmlTest.java | 30 +++++++++---------- 15 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index 2945707a2..18d2ca6cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ target/ .classpath .project -.settings/ \ No newline at end of file +.settings/ diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 649d2abea..9a83aaa90 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -677,7 +677,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - Set newRoles = new HashSet(currentRoles); + Set newRoles = new HashSet<>(currentRoles); newRoles.remove(roleName); User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles, @@ -973,7 +973,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new set of privileges with out the to removed privilege Set privilegeNames = existingRole.getPrivilegeNames(); - Map newPrivileges = new HashMap(privilegeNames.size() - 1); + Map newPrivileges = new HashMap<>(privilegeNames.size() - 1); for (String name : privilegeNames) { IPrivilege privilege = existingRole.getPrivilege(name); if (!privilege.getName().equals(privilegeName)) @@ -1053,8 +1053,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { List sessions = this.privilegeContextMap.values().stream().map(p -> p.getCertificate()) .filter(c -> !c.getUserState().isSystem()).collect(Collectors.toList()); - try (OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, - new FileOutputStream(this.persistSessionsPath))) { + try (FileOutputStream fout = new FileOutputStream(this.persistSessionsPath); + OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, fout)) { CertificateStubsDomWriter writer = new CertificateStubsDomWriter(sessions, outputStream); writer.write(); @@ -1083,8 +1083,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { "Sessions data file is not a file but exists at " + this.persistSessionsPath.getAbsolutePath()); List certificateStubs; - try (InputStream inputStream = AesCryptoHelper.wrapDecrypt(this.secretKey, - new FileInputStream(this.persistSessionsPath))) { + try (FileInputStream fin = new FileInputStream(this.persistSessionsPath); + InputStream inputStream = AesCryptoHelper.wrapDecrypt(this.secretKey, fin)) { CertificateStubsSaxReader reader = new CertificateStubsSaxReader(inputStream); certificateStubs = reader.read(); @@ -1199,8 +1199,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private PrivilegeContext buildPrivilegeContext(Certificate certificate, User user) { Set userRoles = user.getRoles(); - Map privileges = new HashMap(); - Map policies = new HashMap(); + Map privileges = new HashMap<>(); + Map policies = new HashMap<>(); // get a cache of the privileges and policies for this user for (String roleName : userRoles) { diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 40e4b1dc4..c161b5beb 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -58,14 +58,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public List getAllUsers() { synchronized (this.userMap) { - return new LinkedList(this.userMap.values()); + return new LinkedList<>(this.userMap.values()); } } @Override public List getAllRoles() { synchronized (this.roleMap) { - return new LinkedList(this.roleMap.values()); + return new LinkedList<>(this.roleMap.values()); } } @@ -138,7 +138,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { public void initialize(Map paramsMap) { // copy parameter map - this.parameterMap = Collections.unmodifiableMap(new HashMap(paramsMap)); + this.parameterMap = Collections.unmodifiableMap(new HashMap<>(paramsMap)); // get and validate base bath String basePath = this.parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index f9d3b158b..6504e12d4 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -77,9 +77,9 @@ public class BootstrapConfigurationHelper { throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); } - Map parameterMap = new HashMap(); - Map encryptionHandlerParameterMap = new HashMap(); - Map persistenceHandlerParameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); + Map encryptionHandlerParameterMap = new HashMap<>(); + Map persistenceHandlerParameterMap = new HashMap<>(); // TODO ask other questions... parameterMap.put("autoPersistOnPasswordChange", "true"); diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index d409a196e..62eab7fa8 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -61,7 +61,7 @@ public class PasswordCreaterUI { JLabel hash = new JLabel("Hash:", SwingConstants.RIGHT); String[] digests = new String[] { "MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512" }; - final JComboBox digestCombo = new JComboBox(digests); + final JComboBox digestCombo = new JComboBox<>(digests); digestCombo.setSelectedIndex(3); final JPasswordField passwordField = new JPasswordField(); final JTextField hashField = new JTextField(150); diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index 03bc8d21e..5cdf1b556 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -59,8 +59,8 @@ public class PrivilegeInitializationHelper { } // delegate using input stream - try { - return initializeFromXml(new FileInputStream(privilegeXmlFile)); + try (FileInputStream fin = new FileInputStream(privilegeXmlFile)) { + return initializeFromXml(fin); } catch (Exception e) { String msg = "Failed to load configuration from {0}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, privilegeXmlFile.getAbsolutePath()); diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 32eef9e6a..95a7a5efd 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -51,8 +51,8 @@ public class PrivilegeContext { Map policies) { this.userRep = userRep; this.certificate = certificate; - this.privileges = Collections.unmodifiableMap(new HashMap(privileges)); - this.policies = Collections.unmodifiableMap(new HashMap(policies)); + this.privileges = Collections.unmodifiableMap(new HashMap<>(privileges)); + this.policies = Collections.unmodifiableMap(new HashMap<>(policies)); } public UserRep getUserRep() { diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index efc2d7954..cec81e18d 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -46,7 +46,7 @@ public class PrivilegeContainerModel { * Default constructor */ public PrivilegeContainerModel() { - this.policies = new HashMap>(); + this.policies = new HashMap<>(); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index 9cfe3bca5..89b67366a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -109,8 +109,8 @@ public final class PrivilegeImpl implements IPrivilege { */ @Override public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet(this.denyList), - new HashSet(this.allowList)); + return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet<>(this.denyList), + new HashSet<>(this.allowList)); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index 6048f672f..bd8acdf8f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -86,7 +86,7 @@ public final class Role { } // build privileges from rep - Map privilegeMap = new HashMap(roleRep.getPrivileges().size()); + Map privilegeMap = new HashMap<>(roleRep.getPrivileges().size()); for (PrivilegeRep privilege : roleRep.getPrivileges()) { privilegeMap.put(privilege.getName(), new PrivilegeImpl(privilege)); } @@ -136,7 +136,7 @@ public final class Role { * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - List privileges = new ArrayList(); + List privileges = new ArrayList<>(); for (Entry entry : this.privilegeMap.entrySet()) { privileges.add(entry.getValue().asPrivilegeRep()); } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index c81ad1ee8..5afe247aa 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -116,7 +116,7 @@ public final class User { if (roles == null) this.roles = Collections.emptySet(); else - this.roles = Collections.unmodifiableSet(new HashSet(roles)); + this.roles = Collections.unmodifiableSet(new HashSet<>(roles)); if (locale == null) this.locale = Locale.getDefault(); @@ -126,7 +126,7 @@ public final class User { if (propertyMap == null) this.propertyMap = Collections.emptyMap(); else - this.propertyMap = Collections.unmodifiableMap(new HashMap(propertyMap)); + this.propertyMap = Collections.unmodifiableMap(new HashMap<>(propertyMap)); } /** @@ -234,7 +234,7 @@ public final class User { */ public UserRep asUserRep() { return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, - new HashSet(this.roles), this.locale, new HashMap(this.propertyMap)); + new HashSet<>(this.roles), this.locale, new HashMap<>(this.propertyMap)); } /** diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index 660a4544d..421aff46f 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -32,7 +32,7 @@ import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; */ public class PrivilegeConfigSaxReader extends DefaultHandler { - private Deque buildersStack = new ArrayDeque(); + private Deque buildersStack = new ArrayDeque<>(); private PrivilegeContainerModel containerModel; @@ -143,7 +143,7 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { // - private Map parameterMap = new HashMap(); + private Map parameterMap = new HashMap<>(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index be05f0b62..438b91d7a 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -47,7 +47,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { protected static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); - private Deque buildersStack = new ArrayDeque(); + private Deque buildersStack = new ArrayDeque<>(); private List users; private List roles; @@ -55,8 +55,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { private boolean insideUser; public PrivilegeModelSaxReader() { - this.users = new ArrayList(); - this.roles = new ArrayList(); + this.users = new ArrayList<>(); + this.roles = new ArrayList<>(); } /** @@ -147,7 +147,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } private void init() { - this.privileges = new HashMap(); + this.privileges = new HashMap<>(); this.text = null; @@ -155,8 +155,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.privilegeName = null; this.privilegePolicy = null; this.allAllowed = false; - this.denyList = new HashSet(); - this.allowList = new HashSet(); + this.denyList = new HashSet<>(); + this.allowList = new HashSet<>(); } @Override @@ -201,8 +201,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.privilegeName = null; this.privilegePolicy = null; this.allAllowed = false; - this.denyList = new HashSet(); - this.allowList = new HashSet(); + this.denyList = new HashSet<>(); + this.allowList = new HashSet<>(); } else if (qName.equals(XmlConstants.XML_ROLE)) { @@ -245,7 +245,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { Map parameters; public UserParser() { - this.userRoles = new HashSet(); + this.userRoles = new HashSet<>(); } @Override @@ -307,7 +307,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { // - public Map parameterMap = new HashMap(); + public Map parameterMap = new HashMap<>(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 0cb60c782..674ccaebb 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -622,7 +622,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // testAddUserTedAsBob login(BOB, ArraysHelper.copyOf(PASS_BOB)); // let's add a new user ted - HashSet roles = new HashSet(); + HashSet roles = new HashSet<>(); roles.add(ROLE_USER); userRep = new UserRep(null, TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); @@ -738,7 +738,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // let's add a new user bob UserRep userRep = new UserRep(null, BOB, "Bob", "Newman", UserState.NEW, - new HashSet(Arrays.asList(ROLE_MY)), null, new HashMap()); + new HashSet<>(Arrays.asList(ROLE_MY)), null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + BOB); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 4dfc029b3..e1ddd7e72 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -131,9 +131,9 @@ public class XmlTest { @Test public void canWriteConfig() { - Map parameterMap = new HashMap(); - Map encryptionHandlerParameterMap = new HashMap(); - Map persistenceHandlerParameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); + Map encryptionHandlerParameterMap = new HashMap<>(); + Map persistenceHandlerParameterMap = new HashMap<>(); parameterMap.put("autoPersistOnPasswordChange", "true"); encryptionHandlerParameterMap.put("hashAlgorithm", "SHA-256"); @@ -189,7 +189,7 @@ public class XmlTest { assertEquals("en_gb", admin.getLocale().toString()); assertThat(admin.getRoles(), containsInAnyOrder("PrivilegeAdmin", "AppUser")); Map properties = admin.getProperties(); - assertEquals(new HashSet(Arrays.asList("organization", "organizationalUnit")), properties.keySet()); + assertEquals(new HashSet<>(Arrays.asList("organization", "organizationalUnit")), properties.keySet()); assertEquals("eitchnet.ch", properties.get("organization")); assertEquals("Development", properties.get("organizationalUnit")); @@ -233,7 +233,7 @@ public class XmlTest { // AppUser Role appUser = findRole("AppUser", roles); assertEquals("AppUser", appUser.getName()); - assertEquals(new HashSet(Arrays.asList("ch.eitchnet.privilege.test.model.TestRestrictable")), + assertEquals(new HashSet<>(Arrays.asList("ch.eitchnet.privilege.test.model.TestRestrictable")), appUser.getPrivilegeNames()); IPrivilege testRestrictable = appUser.getPrivilege("ch.eitchnet.privilege.test.model.TestRestrictable"); @@ -320,31 +320,31 @@ public class XmlTest { Set userRoles; Map privilegeMap; - List users = new ArrayList(); - propertyMap = new HashMap(); + List users = new ArrayList<>(); + propertyMap = new HashMap<>(); propertyMap.put("prop1", "value1"); - userRoles = new HashSet(); + userRoles = new HashSet<>(); userRoles.add("role1"); users.add(new User("1", "user1", "blabla", "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, propertyMap)); - propertyMap = new HashMap(); + propertyMap = new HashMap<>(); propertyMap.put("prop2", "value2"); - userRoles = new HashSet(); + userRoles = new HashSet<>(); userRoles.add("role2"); users.add(new User("2", "user2", "haha", "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, propertyMap)); - List roles = new ArrayList(); + List roles = new ArrayList<>(); Set list = Collections.emptySet(); - privilegeMap = new HashMap(); + privilegeMap = new HashMap<>(); privilegeMap.put("priv1", new PrivilegeImpl("priv1", "DefaultPrivilege", true, list, list)); roles.add(new Role("role1", privilegeMap)); - privilegeMap = new HashMap(); - Set denyList = new HashSet(); + privilegeMap = new HashMap<>(); + Set denyList = new HashSet<>(); denyList.add("myself"); - Set allowList = new HashSet(); + Set allowList = new HashSet<>(); allowList.add("other"); privilegeMap.put("priv2", new PrivilegeImpl("priv2", "DefaultPrivilege", false, denyList, allowList)); roles.add(new Role("role2", privilegeMap)); From 779bdbb3c10d4b2f497e0eb332c201d5fd01dec1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 3 Mar 2016 14:36:21 +0100 Subject: [PATCH 432/457] [New] Added new XmlDomSigner with tests --- .../eitchnet/utils/helper/XmlDomSigner.java | 245 ++++++++++++++++++ .../utils/helper/XmlSignHelperTest.java | 185 +++++++++++++ src/test/resources/SignedXmlFile.xml | 20 ++ .../resources/SignedXmlFileWithNamespaces.xml | 20 ++ src/test/resources/test.jks | Bin 0 -> 2263 bytes 5 files changed, 470 insertions(+) create mode 100644 src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java create mode 100644 src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java create mode 100644 src/test/resources/SignedXmlFile.xml create mode 100644 src/test/resources/SignedXmlFileWithNamespaces.xml create mode 100644 src/test/resources/test.jks diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java b/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java new file mode 100644 index 000000000..bb0bf014e --- /dev/null +++ b/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java @@ -0,0 +1,245 @@ +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.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.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; + +public class XmlDomSigner { + + private static final Logger logger = LoggerFactory.getLogger(XmlDomSigner.class); + + private PrivateKeyEntry keyEntry; + private X509Certificate cert; + + public XmlDomSigner(File keyStorePath, String alias, char[] password) { + try { + + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(new FileInputStream(keyStorePath), password); + this.keyEntry = (PrivateKeyEntry) ks.getEntry(ks.aliases().nextElement(), + new KeyStore.PasswordProtection(password)); + + this.cert = (X509Certificate) this.keyEntry.getCertificate(); + + } catch (Exception e) { + throw new RuntimeException( + "Failed to read certificate and private key from keystore " + keyStorePath + " and alias " + alias); + } + } + + public void sign(Document document) throws RuntimeException { + + try { + + String id = "Signed_" + UUID.randomUUID().toString(); + Element rootElement = document.getDocumentElement(); + rootElement.setAttribute("ID", id); + rootElement.setIdAttribute("ID", true); + + // Create a DOM XMLSignatureFactory that will be used to + // generate the enveloped signature. + XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); + + // Create a Reference to the enveloped document (in this case, + // you are signing the whole document, so a URI of "" signifies + // that, and also specify the SHA1 digest algorithm and + // the ENVELOPED Transform. + List transforms = new ArrayList<>(); + transforms.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)); + transforms.add(fac.newTransform(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null)); + DigestMethod digestMethod = fac.newDigestMethod(DigestMethod.SHA1, null); + Reference ref = fac.newReference("#" + id, digestMethod, transforms, null, null); + //Reference ref = fac.newReference("", 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. + + // Create the KeyInfo containing the X509Data. + KeyInfoFactory kif = fac.getKeyInfoFactory(); + List x509Content = new ArrayList<>(); + x509Content.add(this.cert.getSubjectX500Principal().getName()); + x509Content.add(this.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(this.keyEntry.getPrivateKey(), 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. + PublicKey publicKey = this.keyEntry.getCertificate().getPublicKey(); + + // Create a DOMValidateContext and specify a KeySelector + // and document context. + Node signatureNode = nl.item(0); + DOMValidateContext valContext = new DOMValidateContext(publicKey, signatureNode); + valContext.setDefaultNamespacePrefix("samlp"); + valContext.putNamespacePrefix(XMLSignature.XMLNS, "ds"); + valContext.putNamespacePrefix("urn:oasis:names:tc:SAML:2.0:protocol", "samlp"); + valContext.putNamespacePrefix("urn:oasis:names:tc:SAML:2.0:assertion", "saml"); + valContext.setIdAttributeNS(doc.getDocumentElement(), null, "ID"); + + // 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) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.transform(new DOMSource(doc), new StreamResult(out)); + return out.toByteArray(); + } catch (TransformerFactoryConfigurationError | TransformerException e) { + throw new RuntimeException("Failed to transform document to bytes!", e); + } + } + + public static void writeTo(Document doc, File file) { + try { + writeTo(doc, new FileOutputStream(file)); + } catch (FileNotFoundException e) { + throw new RuntimeException("Failed to write document to " + file.getAbsolutePath(), e); + } + } + + public static void writeTo(Document doc, OutputStream out) { + try { + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.transform(new DOMSource(doc), new StreamResult(out)); + } catch (Exception e) { + throw new RuntimeException("Failed to write document to output stream!", e); + } + } + + public static Document parse(byte[] bytes) { + return parse(new ByteArrayInputStream(bytes)); + } + + public static Document parse(File signedXmlFile) { + try { + return parse(new FileInputStream(signedXmlFile)); + } catch (Exception e) { + throw new RuntimeException("Failed to parse signed file at " + signedXmlFile.getAbsolutePath(), e); + } + } + + public static Document parse(InputStream in) { + + Document doc; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + doc = dbf.newDocumentBuilder().parse(in); + } catch (Exception e) { + throw new RuntimeException("Failed to parse input stream", e); + } + + return doc; + } +} diff --git a/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java b/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java new file mode 100644 index 000000000..561413319 --- /dev/null +++ b/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java @@ -0,0 +1,185 @@ +package ch.eitchnet.utils.helper; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.TimeZone; + +import javax.xml.crypto.dsig.XMLSignature; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +public class XmlSignHelperTest { + + private static XmlDomSigner helper; + + @BeforeClass + public static void beforeClass() { + helper = new XmlDomSigner(new File("src/test/resources/test.jks"), "client", "changeit".toCharArray()); + } + + @Test + public void shouldSign() { + Document document = createDoc(); + helper.sign(document); + + assertSignatureElemExists(document); + } + + @Test + public void shouldSignWithNamespaces() { + + Document document = createDocWithNamespaces(); + + // hack for signing with namespaces problem + document = XmlDomSigner.parse(XmlDomSigner.transformToBytes(document)); + + helper.sign(document); + + assertSignatureElemExists(document); + } + + private void assertSignatureElemExists(Document document) { + NodeList nl = document.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + assertEquals("Expected exactly one Signature element!", 1, nl.getLength()); + } + + @Test + public void shouldValidate() { + + File signedXmlFile = new File("src/test/resources/SignedXmlFile.xml"); + Document document = XmlDomSigner.parse(signedXmlFile); + helper.validate(document); + } + + @Test + public void shouldValidateWithNamespaces() { + + File signedXmlFile = new File("src/test/resources/SignedXmlFileWithNamespaces.xml"); + Document document = XmlDomSigner.parse(signedXmlFile); + helper.validate(document); + } + + @Test + public void shouldSignAndValidate() { + + Document document = createDoc(); + + helper.sign(document); + helper.validate(document); + } + + @Test + public void shouldSignAndValidateWithNamespaces() { + + Document document = createDocWithNamespaces(); + + // hack for signing with namespaces problem + document = XmlDomSigner.parse(XmlDomSigner.transformToBytes(document)); + + helper.sign(document); + helper.validate(document); + } + + public static Document createDoc() { + + String issuer = "test"; + String destination = "test"; + String assertionConsumerServiceUrl = "test"; + Calendar issueInstant = Calendar.getInstance(); + + // create dates + SimpleDateFormat simpleDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + simpleDf.setTimeZone(TimeZone.getTimeZone("UTC")); + String issueInstantS = simpleDf.format(issueInstant.getTime()); + String notBeforeS = issueInstantS; + issueInstant.add(Calendar.SECOND, 10); + String notOnOrAfterS = simpleDf.format(issueInstant.getTime()); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + DocumentBuilder docBuilder; + try { + docBuilder = dbf.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new RuntimeException("Failed to configure document builder!", e); + } + Document doc = docBuilder.newDocument(); + + Element authnReqE = doc.createElement("AuthnRequest"); + authnReqE.setAttribute("Version", "2.0"); + authnReqE.setAttribute("IssueInstant", issueInstantS); + authnReqE.setAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + authnReqE.setAttribute("AssertionConsumerServiceURL", assertionConsumerServiceUrl); + authnReqE.setAttribute("Destination", destination); + doc.appendChild(authnReqE); + + Element issuerE = doc.createElement("Issuer"); + issuerE.setTextContent(issuer); + authnReqE.appendChild(issuerE); + + Element conditionsE = doc.createElement("Conditions"); + conditionsE.setAttribute("NotBefore", notBeforeS); + conditionsE.setAttribute("NotOnOrAfter", notOnOrAfterS); + authnReqE.appendChild(conditionsE); + + return doc; + } + + public static Document createDocWithNamespaces() { + + String issuer = "test"; + String destination = "test"; + String assertionConsumerServiceUrl = "test"; + Calendar issueInstant = Calendar.getInstance(); + + // create dates + SimpleDateFormat simpleDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + simpleDf.setTimeZone(TimeZone.getTimeZone("UTC")); + String issueInstantS = simpleDf.format(issueInstant.getTime()); + String notBeforeS = issueInstantS; + issueInstant.add(Calendar.SECOND, 10); + String notOnOrAfterS = simpleDf.format(issueInstant.getTime()); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + DocumentBuilder docBuilder; + try { + docBuilder = dbf.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new RuntimeException("Failed to configure document builder!", e); + } + Document doc = docBuilder.newDocument(); + + Element authnReqE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest"); + authnReqE.setPrefix("samlp"); + authnReqE.setAttribute("Version", "2.0"); + authnReqE.setAttribute("IssueInstant", issueInstantS); + authnReqE.setAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); + authnReqE.setAttribute("AssertionConsumerServiceURL", assertionConsumerServiceUrl); + authnReqE.setAttribute("Destination", destination); + doc.appendChild(authnReqE); + + Element issuerE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer"); + issuerE.setPrefix("saml"); + issuerE.setTextContent(issuer); + authnReqE.appendChild(issuerE); + + Element conditionsE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "Conditions"); + conditionsE.setPrefix("saml"); + conditionsE.setAttribute("NotBefore", notBeforeS); + conditionsE.setAttribute("NotOnOrAfter", notOnOrAfterS); + authnReqE.appendChild(conditionsE); + + return doc; + } +} diff --git a/src/test/resources/SignedXmlFile.xml b/src/test/resources/SignedXmlFile.xml new file mode 100644 index 000000000..63197c399 --- /dev/null +++ b/src/test/resources/SignedXmlFile.xml @@ -0,0 +1,20 @@ +test6bxgcmypqGyvyQFgEqdOk1zLTOg=RAWnchzzSHwi84ZJcog6OnkYrGx7rBGBHDsysn1lmP05+AydKzcK7Jw3kbpkGnaaz1DIV/8A/hhA +UmOjwAl8RNygtziXuS5fZfvDUidhPugv2EUKeUkH7CwMkLSB5TONC+AS8eEhfyZbl/4GYMd4Jcqx +OQJgBRMNT6zfybFY+wfJDceFUqCCmyXFgcGBmtSjJqQivwH4B8k1ui49hO67ItCBcCo0aKpqoIxF +UA2IZCDvmrdR/qCq/oA9ssjUzpC+yJMvwPtZ6LDdoWt+MDzDBkCKA6lKUqtU51rZ8GwoRLve8FXH +vCG/ZiqxKn4JwBQL2DiFuZVXhrrMvLpYyi4ZRA==CN=test,OU=ch.eitchnet.utils,O=ch.eitchnet,L=Solothurn,ST=Solothurn,C=CHMIIDizCCAnOgAwIBAgIEPPVdzDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJDSDESMBAGA1UE +CBMJU29sb3RodXJuMRIwEAYDVQQHEwlTb2xvdGh1cm4xFDASBgNVBAoTC2NoLmVpdGNobmV0MRow +GAYDVQQLExFjaC5laXRjaG5ldC51dGlsczENMAsGA1UEAxMEdGVzdDAeFw0xNjAzMDMxMzEwMTRa +Fw0xNjA2MDExMzEwMTRaMHYxCzAJBgNVBAYTAkNIMRIwEAYDVQQIEwlTb2xvdGh1cm4xEjAQBgNV +BAcTCVNvbG90aHVybjEUMBIGA1UEChMLY2guZWl0Y2huZXQxGjAYBgNVBAsTEWNoLmVpdGNobmV0 +LnV0aWxzMQ0wCwYDVQQDEwR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxW8E +F/odm00xQTRQ95b9RtpmiQo3OQfpkE344vyp48BpOHMm8Q+sjTHde6hGUQQ3FSp6jUZtJBmuXDp+ +hFa4zNp1vleASqmUw4VCZZN1BgHx6coWA1zw/GWyCOFHvr+JTP6/LAfemudOnQVWKx6/NkMEgjNm +OR3qsbfj1km/VlfnIAOG9SvFvydp+Jan7y+nIKllEEXpcQFeiWouvC572aptu9k9qe43mRoQHJ96 +IngJHLONi27SBsF31ipvoHjkYEaTYfv8Izf5wt7h+8tZipFHu0+5r7LbZDRhSWzopC5KZakgVgLG +0JYEzcLotOCf9hmDDZZAAv3OyLoMqN8F0QIDAQABoyEwHzAdBgNVHQ4EFgQUd38xdRA7VcrWcjmz +CYmbBMD4SaUwDQYJKoZIhvcNAQELBQADggEBACqDrXrrYG2sfFBRTIQVni291q8tDqJ1etim1fND +s9ZdYa2oKTaLjMswdlE5hVXtEvRrN+XX3TIAK0lfiDwF4E4JBDww4a3SefEbwPvx110WN6CTE1NI +P6IPCUB7e5QOlg4uKAJoZfnY6HboRiHbeOQxIeis3Q9XpqQSYrO4/NzxFt66m48BHLqf8Hwi90GY +VYMljqr+hHvUTQWGzFD3NKr9Fq6yO2GcHGc5ifmLjwoz2EDAsSubrccbN+RQRRg3II6gFxyL9PYN +HgkGjqdg3v8TiWRxWAFL2MrgNLRzOX9Sl7NMFo6JwiizfLWTgcxZZVIkcU1ZP4heXi5iKUzgsJM= \ No newline at end of file diff --git a/src/test/resources/SignedXmlFileWithNamespaces.xml b/src/test/resources/SignedXmlFileWithNamespaces.xml new file mode 100644 index 000000000..012c1783b --- /dev/null +++ b/src/test/resources/SignedXmlFileWithNamespaces.xml @@ -0,0 +1,20 @@ +testKseWAs8E4H1ZGAbyl2EnlZ3RiG4=KhSDJRxo1u7eNVy1swN5RqA+37oCCeyY8QNCtT1RFz8UVZFqmXGiurscbctKA+tiYSekW4OkxEg9 +Nv03OGJcYlksdZ5CCGlsioac+NY/z2QngtlDaFudKIHwj9yZ9zMdiKT/4kdwnUQP+p9tzYV9GeA9 +gesLOielMdj382XoFQ/CIbrJevE4vpn9FSitbwHXV4kZ3/NxlBPYIgiM9yiTTT0NafFENTS38U+P +k1tL32FcDfHytWN6Twl2ZbHrRYltba/ncxaqkauMA37r9v2f+HS+hXXNluLTazRzAxnhSaetjPOt +GFP/nkG0TcRbiZFTP3YwIHeo94v2d/fs6rKHiQ==CN=test,OU=ch.eitchnet.utils,O=ch.eitchnet,L=Solothurn,ST=Solothurn,C=CHMIIDizCCAnOgAwIBAgIEPPVdzDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJDSDESMBAGA1UE +CBMJU29sb3RodXJuMRIwEAYDVQQHEwlTb2xvdGh1cm4xFDASBgNVBAoTC2NoLmVpdGNobmV0MRow +GAYDVQQLExFjaC5laXRjaG5ldC51dGlsczENMAsGA1UEAxMEdGVzdDAeFw0xNjAzMDMxMzEwMTRa +Fw0xNjA2MDExMzEwMTRaMHYxCzAJBgNVBAYTAkNIMRIwEAYDVQQIEwlTb2xvdGh1cm4xEjAQBgNV +BAcTCVNvbG90aHVybjEUMBIGA1UEChMLY2guZWl0Y2huZXQxGjAYBgNVBAsTEWNoLmVpdGNobmV0 +LnV0aWxzMQ0wCwYDVQQDEwR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxW8E +F/odm00xQTRQ95b9RtpmiQo3OQfpkE344vyp48BpOHMm8Q+sjTHde6hGUQQ3FSp6jUZtJBmuXDp+ +hFa4zNp1vleASqmUw4VCZZN1BgHx6coWA1zw/GWyCOFHvr+JTP6/LAfemudOnQVWKx6/NkMEgjNm +OR3qsbfj1km/VlfnIAOG9SvFvydp+Jan7y+nIKllEEXpcQFeiWouvC572aptu9k9qe43mRoQHJ96 +IngJHLONi27SBsF31ipvoHjkYEaTYfv8Izf5wt7h+8tZipFHu0+5r7LbZDRhSWzopC5KZakgVgLG +0JYEzcLotOCf9hmDDZZAAv3OyLoMqN8F0QIDAQABoyEwHzAdBgNVHQ4EFgQUd38xdRA7VcrWcjmz +CYmbBMD4SaUwDQYJKoZIhvcNAQELBQADggEBACqDrXrrYG2sfFBRTIQVni291q8tDqJ1etim1fND +s9ZdYa2oKTaLjMswdlE5hVXtEvRrN+XX3TIAK0lfiDwF4E4JBDww4a3SefEbwPvx110WN6CTE1NI +P6IPCUB7e5QOlg4uKAJoZfnY6HboRiHbeOQxIeis3Q9XpqQSYrO4/NzxFt66m48BHLqf8Hwi90GY +VYMljqr+hHvUTQWGzFD3NKr9Fq6yO2GcHGc5ifmLjwoz2EDAsSubrccbN+RQRRg3II6gFxyL9PYN +HgkGjqdg3v8TiWRxWAFL2MrgNLRzOX9Sl7NMFo6JwiizfLWTgcxZZVIkcU1ZP4heXi5iKUzgsJM= \ No newline at end of file diff --git a/src/test/resources/test.jks b/src/test/resources/test.jks new file mode 100644 index 0000000000000000000000000000000000000000..0e44e5f08687cfb55bec62c63c664b5fccf208ca GIT binary patch literal 2263 zcmc&#_fwOL63v&U0YWd*tAdpH#Q-rBDM}SYz=UGx1VJg%yGSU~LBUv1`c*&>X%~>L zQiKS|RU}9Ur3nZky*#}0-i-gj`{Dd>&hG5&oZZ>oU)*1WKp@aV0RIB%H zY7dLvK_CbKq{8<=RwR=g5`Y7;P!0e=1HFV)UuN=Q`v3^WlaBI5Jh=+(`p_v-!&!4Ozorij~4dw49uyE zNfDCQ2UTavy5^*ke3IHr#sk7o2ig!rocPfc47K!gOEUS+qP6DKwzOM~&S2u)xSd~A zBMwcCVOns;XJMOpq<;j2uG7cuscHv%dM*(iZ;gDLl%%%&h&BUy``X&$z>%MN5{OwS zxwx;R;%aMXB$m-b}&j-XhUEq7Jq|>F}yBBKqRdE*FY2-i-8Q^d=B~f{T z`NJ)UtF4$wqR9CKw!u?V#oEjNKp&K<@HEN&U4n+wPd&18YU7sSu-KK>;;(1goN6^? zWZ}&Z>ifP2#0#z5qtbQw}YJJ{{J41!UVAy6o=|RbslOPz{@4X|V+7@^a=>MQ0HwS>Hu=f5E3} z9NPkhUm`z5JB*OMb6p7UXe@zhO3YczEbqJ;bst?wrMZTrH-HD(utCSW(8U-d$FqXx z**(3A4ZoQ;i{=*oG8_woo2zIS2V8!#2fFdEnpou6RV_(WX>Rd9FKD~i+0Rwq;@oS3 z?^qg+pRjEaZrCL`@j zWV$TD%{?A3`83IRBEYtbZ9R+Y+6_XIZ(?3Ck4gWp+@+c+rss4~mvFy*ZioV3qV+wR zGG(yPVwmUFyCHHMN2P)>5W|h~+579va6cf=`>3Kpf_D4kWz))M>_g_lDro^38{BkscBW3%|464ai0pJ)4ubIZ`1ffAymj`SsT+H4VJtETb4VqkuEdS zCcHss$PuAyi7Kc_`OdFOrpoLO8&WgPc$OIgJB}qCa_4B`SpybO_28b-at~T~dR5qZ zRoyKtJanNev-^r`*M#y5qvakABl^8BQOWAJf8Gn{UvfNVZAq>p>N_e{YrGK39paAU zUGwLycT|#^n{(BKVhJbS8jNL&UMXqkBNbd}vy{J&W4#mcX?b8?BTfn7?z+2)(Chd{ zCoR9)3$g9FajPe-MXUBfo=Oq`8!$elSl+)A?`l5Ku0&)Fz-APG$b*pldC7i4=Vs3`>ir_ar~iT{QcbAeX(ee z`F9M)1^1))`hg;RXsj}b194a!7Q`xA|4WrY?0@P1tuHDd@ux+H4+KU9*dQPkV1iNs z0Mh0K=i3&}G{$NvUfQ7T=?=NXGvNtG=F*HeCwGgcTHMa~o>*ZkNyd(Z7V4V9@jS92 z$+{j={4dBVH={|l^dbKyi(7_88E;~=De3-5U}dh07e@ZEOQ|?Iq1V(LZ?xYme`NI0 z%*7`NlAK7h@;Nva=Ry#kFRz&zFlZ)O%!t8af628qOS^5-o-LnxCRRjY*O~JMY~!zC z8Ze=ZVvl-;deIX8p#Zy3c8Ej}N~kh9(X$`f8aN>9l@l~=ubb|$vnz?;dObR^(`}V- zN3Y(buB_sVv!a86`)mQmkWwT@g0}b3;O}40R*h$`^GBg+n$W$T&Q~mjV~7t>7ytqC z#X(U}_;8%UtZ-g9cVGn8pIw#MHQ+<2M8#*qTQ&{;`qRY2Jv%fJfXGIbhAh~7l!RR} zHHzlRQfM3~Q((>W4;g&=>F2r10UL+X!jsC0N!?(8DItcq$hmeM|Mm08X^5PGU7Q+X z{2~gj22Par2d@aW?5upY;l<~qbD8UF#Hiyb#rx5rAB_>Q^h+Cx#e2LjDpVaF z3tc6|ZzZNO;RZEZ%H=XkI|T95mvoNd#Zq$kgc8@*(IO~h$}{`X1Fm>yZ%g39VAr@} zl`kQ}?0%&YZ%X`YnaZ&0^hmlD#Z1cE*h(YL))wP<(rEl;dWp<6l)BDA#CLh|u?Z7( JGST@q<3HE1+SUL7 literal 0 HcmV?d00001 From cd5f98225dd5116b3190da5530af19b051e1f449 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 7 Mar 2016 17:43:24 +0100 Subject: [PATCH 433/457] [Major] Changed XmlDomSigner to taked alias names for key finding --- .../eitchnet/utils/helper/XmlDomSigner.java | 51 ++++++++++-------- .../utils/helper/XmlSignHelperTest.java | 23 +++++++- src/test/resources/test.jks | Bin 2263 -> 3205 bytes 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java b/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java index bb0bf014e..303d19190 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java @@ -10,6 +10,8 @@ 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; @@ -48,26 +50,33 @@ 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 PrivateKeyEntry keyEntry; - private X509Certificate cert; + private KeyStore keyStore; + private String privateKeyAlias; + private String trustAlias; - public XmlDomSigner(File keyStorePath, String alias, char[] password) { + 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 ks = KeyStore.getInstance("JKS"); - ks.load(new FileInputStream(keyStorePath), password); - this.keyEntry = (PrivateKeyEntry) ks.getEntry(ks.aliases().nextElement(), - new KeyStore.PasswordProtection(password)); - - this.cert = (X509Certificate) this.keyEntry.getCertificate(); + 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 certificate and private key from keystore " + keyStorePath + " and alias " + alias); + throw new RuntimeException("Failed to read keystore " + keyStorePath); } } @@ -93,7 +102,6 @@ public class XmlDomSigner { transforms.add(fac.newTransform(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null)); DigestMethod digestMethod = fac.newDigestMethod(DigestMethod.SHA1, null); Reference ref = fac.newReference("#" + id, digestMethod, transforms, null, null); - //Reference ref = fac.newReference("", digestMethod, transforms, null, null); // Create the SignedInfo. SignedInfo signedInfo = fac.newSignedInfo( @@ -102,18 +110,23 @@ public class XmlDomSigner { Collections.singletonList(ref)); // Load the KeyStore and get the signing key and certificate. + PrivateKeyEntry keyEntry = (PrivateKeyEntry) this.keyStore.getEntry(this.privateKeyAlias, + new KeyStore.PasswordProtection(this.password)); + PrivateKey privateKey = keyEntry.getPrivateKey(); + X509Certificate cert = (X509Certificate) keyEntry.getCertificate(); // Create the KeyInfo containing the X509Data. KeyInfoFactory kif = fac.getKeyInfoFactory(); List x509Content = new ArrayList<>(); - x509Content.add(this.cert.getSubjectX500Principal().getName()); - x509Content.add(this.cert); + + 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(this.keyEntry.getPrivateKey(), rootElement); + DOMSignContext dsc = new DOMSignContext(privateKey, rootElement); //dsc.setDefaultNamespacePrefix("samlp"); dsc.putNamespacePrefix(XMLSignature.XMLNS, "ds"); @@ -145,17 +158,13 @@ public class XmlDomSigner { } // Load the KeyStore and get the signing key and certificate. - PublicKey publicKey = this.keyEntry.getCertificate().getPublicKey(); + 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); - valContext.setDefaultNamespacePrefix("samlp"); - valContext.putNamespacePrefix(XMLSignature.XMLNS, "ds"); - valContext.putNamespacePrefix("urn:oasis:names:tc:SAML:2.0:protocol", "samlp"); - valContext.putNamespacePrefix("urn:oasis:names:tc:SAML:2.0:assertion", "saml"); - valContext.setIdAttributeNS(doc.getDocumentElement(), null, "ID"); // Unmarshal the XMLSignature. valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE); diff --git a/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java b/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java index 561413319..5964354ab 100644 --- a/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java +++ b/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java @@ -24,7 +24,8 @@ public class XmlSignHelperTest { @BeforeClass public static void beforeClass() { - helper = new XmlDomSigner(new File("src/test/resources/test.jks"), "client", "changeit".toCharArray()); + helper = new XmlDomSigner(new File("src/test/resources/test.jks"), "client", "server", + "changeit".toCharArray()); } @Test @@ -58,6 +59,7 @@ public class XmlSignHelperTest { File signedXmlFile = new File("src/test/resources/SignedXmlFile.xml"); Document document = XmlDomSigner.parse(signedXmlFile); + setIdAttr(document); helper.validate(document); } @@ -66,9 +68,28 @@ public class XmlSignHelperTest { File signedXmlFile = new File("src/test/resources/SignedXmlFileWithNamespaces.xml"); Document document = XmlDomSigner.parse(signedXmlFile); + setIdAttrNs(document); + helper.validate(document); } + private void setIdAttr(Document document) { + NodeList authnRequestNodes = document.getElementsByTagName("AuthnRequest"); + if (authnRequestNodes.getLength() != 1) + throw new IllegalStateException("Multiple or no AuthnRequest Node found in document!"); + Element authnRequestNode = (Element) authnRequestNodes.item(0); + authnRequestNode.setIdAttribute("ID", true); + } + + private void setIdAttrNs(Document document) { + NodeList authnRequestNodes = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol", + "AuthnRequest"); + if (authnRequestNodes.getLength() != 1) + throw new IllegalStateException("Multiple or no AuthnRequest Node found in document!"); + Element authnRequestNode = (Element) authnRequestNodes.item(0); + authnRequestNode.setIdAttribute("ID", true); + } + @Test public void shouldSignAndValidate() { diff --git a/src/test/resources/test.jks b/src/test/resources/test.jks index 0e44e5f08687cfb55bec62c63c664b5fccf208ca..dfb55f343f49a1ff5ba7b0007e08e739ad17bee6 100644 GIT binary patch delta 68 zcmcaE*ec2M@9n?03=9lRAiR-hJBJ9E!&aPHRF+!Az`z(B`2Ozg&6=#Um_?l95Iq0@ From 49b325eca13ffe7fe9cfc51ab964b8f7decc8522 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 10 Mar 2016 10:43:52 +0100 Subject: [PATCH 434/457] [Minor] Added method XmlDomSigner.transformToBytes(Document, boolean) --- .../java/ch/eitchnet/utils/helper/XmlDomSigner.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java b/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java index 303d19190..175df4922 100644 --- a/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java +++ b/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java @@ -36,6 +36,7 @@ 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; @@ -188,7 +189,6 @@ public class XmlDomSigner { } throw new RuntimeException("Uh-oh validation, failed!"); } - } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException) e; @@ -197,10 +197,20 @@ public class XmlDomSigner { } 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) { From 119336a1fa33628e7f438083844311020b21a179 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 10:16:21 +0200 Subject: [PATCH 435/457] [Project] Moved everything to a sub directory --- LICENSE => ch.eitchnet.utils/LICENSE | 0 README.md => ch.eitchnet.utils/README.md | 0 pom.xml => ch.eitchnet.utils/pom.xml | 0 .../java/ch/eitchnet/communication/CommandKey.java | 0 .../communication/CommunicationConnection.java | 0 .../communication/CommunicationEndpoint.java | 0 .../eitchnet/communication/ConnectionException.java | 0 .../ch/eitchnet/communication/ConnectionInfo.java | 0 .../eitchnet/communication/ConnectionMessages.java | 0 .../ch/eitchnet/communication/ConnectionMode.java | 0 .../eitchnet/communication/ConnectionObserver.java | 0 .../ch/eitchnet/communication/ConnectionState.java | 0 .../communication/ConnectionStateObserver.java | 0 .../java/ch/eitchnet/communication/IoMessage.java | 0 .../ch/eitchnet/communication/IoMessageArchive.java | 0 .../communication/IoMessageStateObserver.java | 0 .../ch/eitchnet/communication/IoMessageVisitor.java | 0 .../communication/SimpleMessageArchive.java | 0 .../communication/StreamMessageVisitor.java | 0 .../java/ch/eitchnet/communication/chat/Chat.java | 0 .../ch/eitchnet/communication/chat/ChatClient.java | 0 .../eitchnet/communication/chat/ChatIoMessage.java | 0 .../communication/chat/ChatMessageVisitor.java | 0 .../ch/eitchnet/communication/chat/ChatServer.java | 0 .../communication/console/ConsoleEndpoint.java | 0 .../console/ConsoleMessageVisitor.java | 0 .../eitchnet/communication/file/FileEndpoint.java | 0 .../communication/file/FileEndpointMode.java | 0 .../communication/tcpip/ClientSocketEndpoint.java | 0 .../communication/tcpip/ServerSocketEndpoint.java | 0 .../tcpip/SocketEndpointConstants.java | 0 .../communication/tcpip/SocketMessageVisitor.java | 0 .../main/java/ch/eitchnet/db/DbConnectionCheck.java | 0 .../src}/main/java/ch/eitchnet/db/DbConstants.java | 0 .../java/ch/eitchnet/db/DbDataSourceBuilder.java | 0 .../src}/main/java/ch/eitchnet/db/DbException.java | 0 .../main/java/ch/eitchnet/db/DbMigrationState.java | 0 .../java/ch/eitchnet/db/DbSchemaVersionCheck.java | 0 .../java/ch/eitchnet/fileserver/FileClient.java | 0 .../java/ch/eitchnet/fileserver/FileClientUtil.java | 0 .../java/ch/eitchnet/fileserver/FileDeletion.java | 0 .../java/ch/eitchnet/fileserver/FileHandler.java | 0 .../main/java/ch/eitchnet/fileserver/FilePart.java | 0 .../java/ch/eitchnet/utils/StringMatchMode.java | 0 .../src}/main/java/ch/eitchnet/utils/Version.java | 0 .../ch/eitchnet/utils/collections/DateRange.java | 0 .../utils/collections/DefaultedHashMap.java | 0 .../ch/eitchnet/utils/collections/MapOfLists.java | 0 .../ch/eitchnet/utils/collections/MapOfMaps.java | 0 .../java/ch/eitchnet/utils/collections/Paging.java | 0 .../java/ch/eitchnet/utils/collections/Tuple.java | 0 .../src}/main/java/ch/eitchnet/utils/dbc/DBC.java | 0 .../ch/eitchnet/utils/exceptions/XmlException.java | 0 .../ch/eitchnet/utils/helper/AesCryptoHelper.java | 0 .../java/ch/eitchnet/utils/helper/ArraysHelper.java | 0 .../java/ch/eitchnet/utils/helper/AsciiHelper.java | 0 .../java/ch/eitchnet/utils/helper/BaseEncoding.java | 0 .../java/ch/eitchnet/utils/helper/ByteHelper.java | 0 .../java/ch/eitchnet/utils/helper/ClassHelper.java | 0 .../main/java/ch/eitchnet/utils/helper/DomUtil.java | 0 .../ch/eitchnet/utils/helper/ExceptionHelper.java | 0 .../java/ch/eitchnet/utils/helper/FileHelper.java | 0 .../java/ch/eitchnet/utils/helper/MathHelper.java | 0 .../ch/eitchnet/utils/helper/ProcessHelper.java | 0 .../ch/eitchnet/utils/helper/PropertiesHelper.java | 0 .../java/ch/eitchnet/utils/helper/StringHelper.java | 0 .../java/ch/eitchnet/utils/helper/SystemHelper.java | 0 .../java/ch/eitchnet/utils/helper/XmlDomSigner.java | 0 .../java/ch/eitchnet/utils/helper/XmlHelper.java | 0 .../ch/eitchnet/utils/io/FileProgressListener.java | 0 .../utils/io/FileStreamProgressWatcher.java | 0 .../utils/io/LoggingFileProgressListener.java | 0 .../utils/io/ProgressableFileInputStream.java | 0 .../java/ch/eitchnet/utils/iso8601/DateFormat.java | 0 .../ch/eitchnet/utils/iso8601/DurationFormat.java | 0 .../ch/eitchnet/utils/iso8601/FormatFactory.java | 0 .../java/ch/eitchnet/utils/iso8601/ISO8601.java | 0 .../ch/eitchnet/utils/iso8601/ISO8601Duration.java | 0 .../utils/iso8601/ISO8601FormatFactory.java | 0 .../ch/eitchnet/utils/iso8601/ISO8601Worktime.java | 0 .../ch/eitchnet/utils/iso8601/WorktimeFormat.java | 0 .../ch/eitchnet/utils/objectfilter/ObjectCache.java | 0 .../eitchnet/utils/objectfilter/ObjectFilter.java | 0 .../ch/eitchnet/utils/objectfilter/Operation.java | 0 .../java/ch/eitchnet/utils/xml/XmlKeyValue.java | 0 .../main/java/javanet/staxutils/Indentation.java | 0 .../javanet/staxutils/IndentingXMLStreamWriter.java | 0 .../staxutils/helpers/StreamWriterDelegate.java | 0 {src => ch.eitchnet.utils/src}/main/java/log4j.xml | 0 .../communication/AbstractEndpointTest.java | 0 .../eitchnet/communication/ConsoleEndpointTest.java | 0 .../ch/eitchnet/communication/FileEndpointTest.java | 0 .../communication/SimpleMessageArchiveTest.java | 0 .../eitchnet/communication/SocketEndpointTest.java | 0 .../communication/TestConnectionObserver.java | 0 .../ch/eitchnet/communication/TestIoMessage.java | 0 .../java/ch/eitchnet/utils/StringMatchModeTest.java | 0 .../test/java/ch/eitchnet/utils/VersionTest.java | 0 .../eitchnet/utils/collections/DateRangeTest.java | 0 .../utils/collections/DefaultedHashMapTest.java | 0 .../ch/eitchnet/utils/collections/PagingTest.java | 0 .../test/java/ch/eitchnet/utils/dbc/DBCTest.java | 0 .../eitchnet/utils/helper/AesCryptoHelperTest.java | 0 .../ch/eitchnet/utils/helper/BaseDecodingTest.java | 0 .../ch/eitchnet/utils/helper/BaseEncodingTest.java | 0 .../eitchnet/utils/helper/ExceptionHelperTest.java | 0 .../GenerateReverseBaseEncodingAlphabets.java | 0 .../utils/helper/ReplacePropertiesInTest.java | 0 .../ch/eitchnet/utils/helper/XmlSignHelperTest.java | 0 .../utils/objectfilter/ObjectFilterTest.java | 0 .../src}/test/resources/SignedXmlFile.xml | 0 .../test/resources/SignedXmlFileWithNamespaces.xml | 0 .../src}/test/resources/crypto_test_image.ico | Bin .../src}/test/resources/crypto_test_long.txt | 0 .../src}/test/resources/crypto_test_middle.txt | 0 .../src}/test/resources/crypto_test_short.txt | 0 .../src}/test/resources/log4j.xml | 0 .../src}/test/resources/test.jks | Bin .../src}/test/resources/test_data.csv | 0 119 files changed, 0 insertions(+), 0 deletions(-) rename LICENSE => ch.eitchnet.utils/LICENSE (100%) rename README.md => ch.eitchnet.utils/README.md (100%) rename pom.xml => ch.eitchnet.utils/pom.xml (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/CommandKey.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/CommunicationConnection.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/CommunicationEndpoint.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/ConnectionException.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/ConnectionInfo.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/ConnectionMessages.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/ConnectionMode.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/ConnectionObserver.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/ConnectionState.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/ConnectionStateObserver.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/IoMessage.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/IoMessageArchive.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/IoMessageStateObserver.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/IoMessageVisitor.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/SimpleMessageArchive.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/StreamMessageVisitor.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/chat/Chat.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/chat/ChatClient.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/chat/ChatServer.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/file/FileEndpoint.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/file/FileEndpointMode.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/db/DbConnectionCheck.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/db/DbConstants.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/db/DbDataSourceBuilder.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/db/DbException.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/db/DbMigrationState.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/fileserver/FileClient.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/fileserver/FileClientUtil.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/fileserver/FileDeletion.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/fileserver/FileHandler.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/fileserver/FilePart.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/StringMatchMode.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/Version.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/collections/DateRange.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/collections/MapOfLists.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/collections/MapOfMaps.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/collections/Paging.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/collections/Tuple.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/dbc/DBC.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/exceptions/XmlException.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/ArraysHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/AsciiHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/BaseEncoding.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/ByteHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/ClassHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/DomUtil.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/FileHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/MathHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/ProcessHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/StringHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/SystemHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/helper/XmlHelper.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/io/FileProgressListener.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/iso8601/DateFormat.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/iso8601/ISO8601.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/objectfilter/Operation.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/javanet/staxutils/Indentation.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/javanet/staxutils/IndentingXMLStreamWriter.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java (100%) rename {src => ch.eitchnet.utils/src}/main/java/log4j.xml (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/communication/AbstractEndpointTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/communication/FileEndpointTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/communication/SocketEndpointTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/communication/TestConnectionObserver.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/communication/TestIoMessage.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/StringMatchModeTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/VersionTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/collections/DateRangeTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/collections/PagingTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/dbc/DBCTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java (100%) rename {src => ch.eitchnet.utils/src}/test/resources/SignedXmlFile.xml (100%) rename {src => ch.eitchnet.utils/src}/test/resources/SignedXmlFileWithNamespaces.xml (100%) rename {src => ch.eitchnet.utils/src}/test/resources/crypto_test_image.ico (100%) rename {src => ch.eitchnet.utils/src}/test/resources/crypto_test_long.txt (100%) rename {src => ch.eitchnet.utils/src}/test/resources/crypto_test_middle.txt (100%) rename {src => ch.eitchnet.utils/src}/test/resources/crypto_test_short.txt (100%) rename {src => ch.eitchnet.utils/src}/test/resources/log4j.xml (100%) rename {src => ch.eitchnet.utils/src}/test/resources/test.jks (100%) rename {src => ch.eitchnet.utils/src}/test/resources/test_data.csv (100%) diff --git a/LICENSE b/ch.eitchnet.utils/LICENSE similarity index 100% rename from LICENSE rename to ch.eitchnet.utils/LICENSE diff --git a/README.md b/ch.eitchnet.utils/README.md similarity index 100% rename from README.md rename to ch.eitchnet.utils/README.md diff --git a/pom.xml b/ch.eitchnet.utils/pom.xml similarity index 100% rename from pom.xml rename to ch.eitchnet.utils/pom.xml diff --git a/src/main/java/ch/eitchnet/communication/CommandKey.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommandKey.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/CommandKey.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommandKey.java diff --git a/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/CommunicationConnection.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java diff --git a/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java diff --git a/src/main/java/ch/eitchnet/communication/ConnectionException.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/ConnectionException.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java diff --git a/src/main/java/ch/eitchnet/communication/ConnectionInfo.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/ConnectionInfo.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/ConnectionMessages.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java diff --git a/src/main/java/ch/eitchnet/communication/ConnectionMode.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/ConnectionMode.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java diff --git a/src/main/java/ch/eitchnet/communication/ConnectionObserver.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/ConnectionObserver.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java diff --git a/src/main/java/ch/eitchnet/communication/ConnectionState.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/ConnectionState.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java diff --git a/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java diff --git a/src/main/java/ch/eitchnet/communication/IoMessage.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessage.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/IoMessage.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessage.java diff --git a/src/main/java/ch/eitchnet/communication/IoMessageArchive.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/IoMessageArchive.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java diff --git a/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java diff --git a/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/IoMessageVisitor.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java diff --git a/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java diff --git a/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java diff --git a/src/main/java/ch/eitchnet/communication/chat/Chat.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/chat/Chat.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatClient.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/chat/ChatClient.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java diff --git a/src/main/java/ch/eitchnet/communication/chat/ChatServer.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/chat/ChatServer.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java diff --git a/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java diff --git a/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java diff --git a/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/file/FileEndpoint.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java diff --git a/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java diff --git a/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java diff --git a/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java similarity index 100% rename from src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java diff --git a/src/main/java/ch/eitchnet/db/DbConnectionCheck.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java similarity index 100% rename from src/main/java/ch/eitchnet/db/DbConnectionCheck.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java diff --git a/src/main/java/ch/eitchnet/db/DbConstants.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConstants.java similarity index 100% rename from src/main/java/ch/eitchnet/db/DbConstants.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConstants.java diff --git a/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java similarity index 100% rename from src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java diff --git a/src/main/java/ch/eitchnet/db/DbException.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbException.java similarity index 100% rename from src/main/java/ch/eitchnet/db/DbException.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbException.java diff --git a/src/main/java/ch/eitchnet/db/DbMigrationState.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java similarity index 100% rename from src/main/java/ch/eitchnet/db/DbMigrationState.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java diff --git a/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java similarity index 100% rename from src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java diff --git a/src/main/java/ch/eitchnet/fileserver/FileClient.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java similarity index 100% rename from src/main/java/ch/eitchnet/fileserver/FileClient.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java diff --git a/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java similarity index 100% rename from src/main/java/ch/eitchnet/fileserver/FileClientUtil.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java diff --git a/src/main/java/ch/eitchnet/fileserver/FileDeletion.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java similarity index 100% rename from src/main/java/ch/eitchnet/fileserver/FileDeletion.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java diff --git a/src/main/java/ch/eitchnet/fileserver/FileHandler.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/fileserver/FileHandler.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java diff --git a/src/main/java/ch/eitchnet/fileserver/FilePart.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java similarity index 100% rename from src/main/java/ch/eitchnet/fileserver/FilePart.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java diff --git a/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/StringMatchMode.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java diff --git a/src/main/java/ch/eitchnet/utils/Version.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/Version.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/Version.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/Version.java diff --git a/src/main/java/ch/eitchnet/utils/collections/DateRange.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/collections/DateRange.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java diff --git a/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/collections/MapOfLists.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java diff --git a/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java diff --git a/src/main/java/ch/eitchnet/utils/collections/Paging.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/collections/Paging.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java diff --git a/src/main/java/ch/eitchnet/utils/collections/Tuple.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/collections/Tuple.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java diff --git a/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/dbc/DBC.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java diff --git a/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/exceptions/XmlException.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java diff --git a/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/ByteHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/ClassHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/DomUtil.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/DomUtil.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/FileHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/MathHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/MathHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/StringHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/SystemHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java diff --git a/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/helper/XmlHelper.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java diff --git a/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/io/FileProgressListener.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java diff --git a/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java diff --git a/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java diff --git a/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java diff --git a/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java diff --git a/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/objectfilter/Operation.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java diff --git a/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java b/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java similarity index 100% rename from src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java rename to ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java diff --git a/src/main/java/javanet/staxutils/Indentation.java b/ch.eitchnet.utils/src/main/java/javanet/staxutils/Indentation.java similarity index 100% rename from src/main/java/javanet/staxutils/Indentation.java rename to ch.eitchnet.utils/src/main/java/javanet/staxutils/Indentation.java diff --git a/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/ch.eitchnet.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java similarity index 100% rename from src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java rename to ch.eitchnet.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java diff --git a/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java b/ch.eitchnet.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java similarity index 100% rename from src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java rename to ch.eitchnet.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java diff --git a/src/main/java/log4j.xml b/ch.eitchnet.utils/src/main/java/log4j.xml similarity index 100% rename from src/main/java/log4j.xml rename to ch.eitchnet.utils/src/main/java/log4j.xml diff --git a/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java similarity index 100% rename from src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java diff --git a/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java similarity index 100% rename from src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java diff --git a/src/test/java/ch/eitchnet/communication/FileEndpointTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java similarity index 100% rename from src/test/java/ch/eitchnet/communication/FileEndpointTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java diff --git a/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java similarity index 100% rename from src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java diff --git a/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java similarity index 100% rename from src/test/java/ch/eitchnet/communication/SocketEndpointTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java diff --git a/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java similarity index 100% rename from src/test/java/ch/eitchnet/communication/TestConnectionObserver.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java diff --git a/src/test/java/ch/eitchnet/communication/TestIoMessage.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java similarity index 100% rename from src/test/java/ch/eitchnet/communication/TestIoMessage.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java diff --git a/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/StringMatchModeTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java diff --git a/src/test/java/ch/eitchnet/utils/VersionTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/VersionTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/VersionTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/VersionTest.java diff --git a/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java diff --git a/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java diff --git a/src/test/java/ch/eitchnet/utils/collections/PagingTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/collections/PagingTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java diff --git a/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/dbc/DBCTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java diff --git a/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java diff --git a/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java diff --git a/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java diff --git a/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java diff --git a/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java diff --git a/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java diff --git a/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java similarity index 100% rename from src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java rename to ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java diff --git a/src/test/resources/SignedXmlFile.xml b/ch.eitchnet.utils/src/test/resources/SignedXmlFile.xml similarity index 100% rename from src/test/resources/SignedXmlFile.xml rename to ch.eitchnet.utils/src/test/resources/SignedXmlFile.xml diff --git a/src/test/resources/SignedXmlFileWithNamespaces.xml b/ch.eitchnet.utils/src/test/resources/SignedXmlFileWithNamespaces.xml similarity index 100% rename from src/test/resources/SignedXmlFileWithNamespaces.xml rename to ch.eitchnet.utils/src/test/resources/SignedXmlFileWithNamespaces.xml diff --git a/src/test/resources/crypto_test_image.ico b/ch.eitchnet.utils/src/test/resources/crypto_test_image.ico similarity index 100% rename from src/test/resources/crypto_test_image.ico rename to ch.eitchnet.utils/src/test/resources/crypto_test_image.ico diff --git a/src/test/resources/crypto_test_long.txt b/ch.eitchnet.utils/src/test/resources/crypto_test_long.txt similarity index 100% rename from src/test/resources/crypto_test_long.txt rename to ch.eitchnet.utils/src/test/resources/crypto_test_long.txt diff --git a/src/test/resources/crypto_test_middle.txt b/ch.eitchnet.utils/src/test/resources/crypto_test_middle.txt similarity index 100% rename from src/test/resources/crypto_test_middle.txt rename to ch.eitchnet.utils/src/test/resources/crypto_test_middle.txt diff --git a/src/test/resources/crypto_test_short.txt b/ch.eitchnet.utils/src/test/resources/crypto_test_short.txt similarity index 100% rename from src/test/resources/crypto_test_short.txt rename to ch.eitchnet.utils/src/test/resources/crypto_test_short.txt diff --git a/src/test/resources/log4j.xml b/ch.eitchnet.utils/src/test/resources/log4j.xml similarity index 100% rename from src/test/resources/log4j.xml rename to ch.eitchnet.utils/src/test/resources/log4j.xml diff --git a/src/test/resources/test.jks b/ch.eitchnet.utils/src/test/resources/test.jks similarity index 100% rename from src/test/resources/test.jks rename to ch.eitchnet.utils/src/test/resources/test.jks diff --git a/src/test/resources/test_data.csv b/ch.eitchnet.utils/src/test/resources/test_data.csv similarity index 100% rename from src/test/resources/test_data.csv rename to ch.eitchnet.utils/src/test/resources/test_data.csv From b3b274f143d6a658759467813cf5beb7f3c1068a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 28 Feb 2016 19:53:53 +0100 Subject: [PATCH 436/457] [Project] Moved everything to a sub directory --- LICENSE => ch.eitchnet.privilege/LICENSE | 0 README.md => ch.eitchnet.privilege/README.md | 0 .../config}/PrivilegeConfig.xml | 3 +- .../config}/PrivilegeConfigMerge.xml | 3 +- .../config/PrivilegeRoles.xml | 90 ++++++++ .../config/PrivilegeRolesMerge.xml | 28 +++ .../config/PrivilegeUsers.xml | 39 ++++ .../config/PrivilegeUsersMerge.xml | 26 +++ .../docs}/PrivilegeAuthentication.dia | Bin .../docs}/PrivilegeHandlers.dia | Bin .../docs}/PrivilegeModelPrivilege.dia | Bin .../docs}/PrivilegeModelUser.dia | Bin {docs => ch.eitchnet.privilege/docs}/TODO | 0 pom.xml => ch.eitchnet.privilege/pom.xml | 0 .../privilege.jardesc | 0 .../privilege/base/AccessDeniedException.java | 0 .../base/InvalidCredentialsException.java | 0 .../base/PrivilegeConflictResolution.java | 0 .../privilege/base/PrivilegeException.java | 0 .../handler/DefaultEncryptionHandler.java | 0 .../handler/DefaultPrivilegeHandler.java | 3 +- .../privilege/handler/EncryptionHandler.java | 0 .../privilege/handler/PersistenceHandler.java | 0 .../privilege/handler/PrivilegeHandler.java | 0 .../privilege/handler/SystemUserAction.java | 0 .../handler/XmlPersistenceHandler.java | 125 ++++++++--- .../helper/BootstrapConfigurationHelper.java | 0 .../privilege/helper/PasswordCreaterUI.java | 0 .../privilege/helper/PasswordCreator.java | 0 .../helper/PrivilegeInitializationHelper.java | 0 .../privilege/helper/XmlConstants.java | 9 +- .../privilege/i18n/PrivilegeMessages.java | 0 .../eitchnet/privilege/model/Certificate.java | 0 .../eitchnet/privilege/model/IPrivilege.java | 0 .../privilege/model/PrivilegeContext.java | 0 .../privilege/model/PrivilegeRep.java | 0 .../privilege/model/Restrictable.java | 0 .../ch/eitchnet/privilege/model/RoleRep.java | 0 .../privilege/model/SimpleRestrictable.java | 0 .../ch/eitchnet/privilege/model/UserRep.java | 0 .../eitchnet/privilege/model/UserState.java | 0 .../internal/PrivilegeContainerModel.java | 0 .../model/internal/PrivilegeImpl.java | 0 .../privilege/model/internal/Role.java | 0 .../privilege/model/internal/User.java | 0 .../privilege/policy/DefaultPrivilege.java | 0 .../privilege/policy/PrivilegePolicy.java | 0 .../policy/PrivilegePolicyHelper.java | 0 .../privilege/policy/RoleAccessPrivilege.java | 0 .../privilege/policy/UserAccessPrivilege.java | 0 ...erAccessWithSameOrganisationPrivilege.java | 0 .../UsernameFromCertificatePrivilege.java | 0 ...tificateWithSameOrganisationPrivilege.java | 0 .../xml/CertificateStubsDomWriter.java | 0 .../xml/CertificateStubsSaxReader.java | 0 .../eitchnet/privilege/xml/ElementParser.java | 0 .../privilege/xml/ElementParserAdapter.java | 0 .../xml/PrivilegeConfigDomWriter.java | 0 .../xml/PrivilegeConfigSaxReader.java | 0 .../xml/PrivilegeRolesDomWriter.java | 98 ++++++++ .../xml/PrivilegeRolesSaxReader.java | 129 +---------- .../xml/PrivilegeUsersDomWriter.java | 67 +----- .../xml/PrivilegeUsersSaxReader.java | 209 ++++++++++++++++++ .../src}/main/resources/Privilege.xsd | 0 .../resources/PrivilegeMessages.properties | 0 .../src}/main/resources/PrivilegeModel.xsd | 0 .../privilege/test/AbstractPrivilegeTest.java | 12 +- .../privilege/test/PersistSessionsTest.java | 3 +- .../test/PrivilegeConflictMergeTest.java | 2 +- .../privilege/test/PrivilegeTest.java | 3 +- .../ch/eitchnet/privilege/test/XmlTest.java | 143 ++++++++++-- .../test/model/TestRestrictable.java | 0 .../test/model/TestSystemRestrictable.java | 0 .../test/model/TestSystemUserAction.java | 0 .../test/model/TestSystemUserActionDeny.java | 0 .../src}/test/resources/log4j.xml | 0 config/PrivilegeModel.xml | 132 ----------- config/PrivilegeModelMerge.xml | 54 ----- 78 files changed, 742 insertions(+), 436 deletions(-) rename LICENSE => ch.eitchnet.privilege/LICENSE (100%) rename README.md => ch.eitchnet.privilege/README.md (100%) rename {config => ch.eitchnet.privilege/config}/PrivilegeConfig.xml (90%) rename {config => ch.eitchnet.privilege/config}/PrivilegeConfigMerge.xml (87%) create mode 100644 ch.eitchnet.privilege/config/PrivilegeRoles.xml create mode 100644 ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml create mode 100644 ch.eitchnet.privilege/config/PrivilegeUsers.xml create mode 100644 ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml rename {docs => ch.eitchnet.privilege/docs}/PrivilegeAuthentication.dia (100%) rename {docs => ch.eitchnet.privilege/docs}/PrivilegeHandlers.dia (100%) rename {docs => ch.eitchnet.privilege/docs}/PrivilegeModelPrivilege.dia (100%) rename {docs => ch.eitchnet.privilege/docs}/PrivilegeModelUser.dia (100%) rename {docs => ch.eitchnet.privilege/docs}/TODO (100%) rename pom.xml => ch.eitchnet.privilege/pom.xml (100%) rename privilege.jardesc => ch.eitchnet.privilege/privilege.jardesc (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/base/PrivilegeException.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java (99%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java (62%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/XmlConstants.java (95%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/Certificate.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/IPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/Restrictable.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/RoleRep.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/UserRep.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/UserState.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/internal/Role.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/internal/User.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/ElementParser.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java (100%) create mode 100644 ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java rename src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java => ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java (63%) rename src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java => ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java (61%) create mode 100644 ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java rename {src => ch.eitchnet.privilege/src}/main/resources/Privilege.xsd (100%) rename {src => ch.eitchnet.privilege/src}/main/resources/PrivilegeMessages.properties (100%) rename {src => ch.eitchnet.privilege/src}/main/resources/PrivilegeModel.xsd (100%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java (89%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java (95%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java (97%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java (99%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/XmlTest.java (71%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java (100%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java (100%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java (100%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java (100%) rename {src => ch.eitchnet.privilege/src}/test/resources/log4j.xml (100%) delete mode 100644 config/PrivilegeModel.xml delete mode 100644 config/PrivilegeModelMerge.xml diff --git a/LICENSE b/ch.eitchnet.privilege/LICENSE similarity index 100% rename from LICENSE rename to ch.eitchnet.privilege/LICENSE diff --git a/README.md b/ch.eitchnet.privilege/README.md similarity index 100% rename from README.md rename to ch.eitchnet.privilege/README.md diff --git a/config/PrivilegeConfig.xml b/ch.eitchnet.privilege/config/PrivilegeConfig.xml similarity index 90% rename from config/PrivilegeConfig.xml rename to ch.eitchnet.privilege/config/PrivilegeConfig.xml index 4d293aca2..744933dcf 100644 --- a/config/PrivilegeConfig.xml +++ b/ch.eitchnet.privilege/config/PrivilegeConfig.xml @@ -22,7 +22,8 @@ - + + diff --git a/config/PrivilegeConfigMerge.xml b/ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml similarity index 87% rename from config/PrivilegeConfigMerge.xml rename to ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml index 8838bc468..403652054 100644 --- a/config/PrivilegeConfigMerge.xml +++ b/ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml @@ -18,7 +18,8 @@ - + + diff --git a/ch.eitchnet.privilege/config/PrivilegeRoles.xml b/ch.eitchnet.privilege/config/PrivilegeRoles.xml new file mode 100644 index 000000000..95ca00403 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeRoles.xml @@ -0,0 +1,90 @@ + + + + + + Persist + Reload + GetPolicies + + + + true + + + true + + + true + + + true + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + ENABLED + DISABLED + SYSTEM + + + true + + + + + + true + + + + + + true + + + + + + true + + + + + + ch.eitchnet.privilege.test.model.TestSystemUserAction + ch.eitchnet.privilege.test.model.TestSystemUserActionDeny + + + true + + + + + + hello + goodbye + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml b/ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml new file mode 100644 index 000000000..02e9ea037 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml @@ -0,0 +1,28 @@ + + + + + + allow1 + + + + + true + + + + + + allow1 + deny1 + + + + + allow2 + deny2 + + + + diff --git a/ch.eitchnet.privilege/config/PrivilegeUsers.xml b/ch.eitchnet.privilege/config/PrivilegeUsers.xml new file mode 100644 index 000000000..46b0cdf42 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeUsers.xml @@ -0,0 +1,39 @@ + + + + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml b/ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml new file mode 100644 index 000000000..978464173 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml @@ -0,0 +1,26 @@ + + + + + System User + Administrator + ENABLED + en_GB + + RoleA1 + RoleA2 + + + + + System User + Administrator + ENABLED + en_GB + + RoleB1 + RoleB2 + + + + \ No newline at end of file diff --git a/docs/PrivilegeAuthentication.dia b/ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia similarity index 100% rename from docs/PrivilegeAuthentication.dia rename to ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia diff --git a/docs/PrivilegeHandlers.dia b/ch.eitchnet.privilege/docs/PrivilegeHandlers.dia similarity index 100% rename from docs/PrivilegeHandlers.dia rename to ch.eitchnet.privilege/docs/PrivilegeHandlers.dia diff --git a/docs/PrivilegeModelPrivilege.dia b/ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia similarity index 100% rename from docs/PrivilegeModelPrivilege.dia rename to ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia diff --git a/docs/PrivilegeModelUser.dia b/ch.eitchnet.privilege/docs/PrivilegeModelUser.dia similarity index 100% rename from docs/PrivilegeModelUser.dia rename to ch.eitchnet.privilege/docs/PrivilegeModelUser.dia diff --git a/docs/TODO b/ch.eitchnet.privilege/docs/TODO similarity index 100% rename from docs/TODO rename to ch.eitchnet.privilege/docs/TODO diff --git a/pom.xml b/ch.eitchnet.privilege/pom.xml similarity index 100% rename from pom.xml rename to ch.eitchnet.privilege/pom.xml diff --git a/privilege.jardesc b/ch.eitchnet.privilege/privilege.jardesc similarity index 100% rename from privilege.jardesc rename to ch.eitchnet.privilege/privilege.jardesc diff --git a/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java diff --git a/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java diff --git a/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java diff --git a/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java similarity index 99% rename from src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 9a83aaa90..91e800a2e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1476,7 +1476,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } this.persistSessionsPath = persistSessionsPath; - logger.info("Enabling persistence of sessions."); //$NON-NLS-1$ + logger.info(MessageFormat.format("Enabling persistence of sessions to {0}", //$NON-NLS-1$ + this.persistSessionsPath.getAbsolutePath())); } else { String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$ msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS, persistSessionsS, Boolean.FALSE); diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java similarity index 62% rename from src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index c161b5beb..2ad3462b4 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -30,8 +30,11 @@ import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; +import ch.eitchnet.privilege.xml.PrivilegeRolesDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeRolesSaxReader; +import ch.eitchnet.privilege.xml.PrivilegeUsersDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeUsersSaxReader; +import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.helper.XmlHelper; /** @@ -47,13 +50,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { private Map userMap; private Map roleMap; - private long modelsFileDate; private boolean userMapDirty; private boolean roleMapDirty; private Map parameterMap; - private File modelPath; + private long usersFileDate; + private long rolesFileDate; + private File usersPath; + private File rolesPath; @Override public List getAllUsers() { @@ -150,26 +155,45 @@ public class XmlPersistenceHandler implements PersistenceHandler { throw new PrivilegeException(msg); } - // get model file name - String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); - if (modelFileName == null || modelFileName.isEmpty()) { + // get users file name + String usersFileName = this.parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (StringHelper.isEmpty(usersFileName)) { String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE); + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_USERS_FILE); throw new PrivilegeException(msg); } - // validate file exists - String modelPathS = basePath + "/" + modelFileName; //$NON-NLS-1$ - File modelPath = new File(modelPathS); - if (!modelPath.exists()) { - String msg = "[{0}] Defined parameter {1} is invalid as model file does not exist at path {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE, - modelPath.getAbsolutePath()); + // get roles file name + String rolesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (StringHelper.isEmpty(rolesFileName)) { + String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_ROLES_FILE); + throw new PrivilegeException(msg); + } + + // validate users file exists + String usersPathS = basePath + "/" + usersFileName; //$NON-NLS-1$ + File usersPath = new File(usersPathS); + if (!usersPath.exists()) { + String msg = "[{0}] Defined parameter {1} is invalid as users file does not exist at path {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_USERS_FILE, + usersPath.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + // validate roles file exists + String rolesPathS = basePath + "/" + rolesFileName; //$NON-NLS-1$ + File rolesPath = new File(rolesPathS); + if (!rolesPath.exists()) { + String msg = "[{0}] Defined parameter {1} is invalid as roles file does not exist at path {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_ROLES_FILE, + rolesPath.getAbsolutePath()); throw new PrivilegeException(msg); } // save path to model - this.modelPath = modelPath; + this.usersPath = usersPath; + this.rolesPath = rolesPath; if (reload()) logger.info("Privilege Data loaded."); //$NON-NLS-1$ @@ -188,19 +212,23 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMap = Collections.synchronizedMap(new HashMap()); // parse models xml file to XML document - PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); - XmlHelper.parseDocument(this.modelPath, xmlHandler); + PrivilegeUsersSaxReader usersXmlHandler = new PrivilegeUsersSaxReader(); + XmlHelper.parseDocument(this.usersPath, usersXmlHandler); - this.modelsFileDate = this.modelPath.lastModified(); + PrivilegeRolesSaxReader rolesXmlHandler = new PrivilegeRolesSaxReader(); + XmlHelper.parseDocument(this.rolesPath, rolesXmlHandler); + + this.usersFileDate = this.usersPath.lastModified(); + this.rolesFileDate = this.rolesPath.lastModified(); // ROLES - List roles = xmlHandler.getRoles(); + List roles = rolesXmlHandler.getRoles(); for (Role role : roles) { this.roleMap.put(role.getName(), role); } // USERS - List users = xmlHandler.getUsers(); + List users = usersXmlHandler.getUsers(); for (User user : users) { this.userMap.put(user.getUsername(), user); } @@ -233,29 +261,54 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public boolean persist() { - // get models file name - String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); - if (modelFileName == null || modelFileName.isEmpty()) { + // get users file name + String usersFileName = this.parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { String msg = "[{0}] Defined parameter {1} is invalid"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE); + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_USERS_FILE); throw new PrivilegeException(msg); } - // get model file - boolean modelFileUnchanged = this.modelPath.exists() && this.modelPath.lastModified() == this.modelsFileDate; - if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { - logger.warn("Not persisting as current file is unchanged and model data is not dirty"); //$NON-NLS-1$ - return false; + // get roles file name + String rolesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + String msg = "[{0}] Defined parameter {1} is invalid"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_ROLES_FILE); + throw new PrivilegeException(msg); } - // delegate writing - PrivilegeModelDomWriter modelWriter = new PrivilegeModelDomWriter(getAllUsers(), getAllRoles(), this.modelPath); - modelWriter.write(); + boolean saved = false; + + // get users file + boolean usersFileUnchanged = this.usersPath.exists() && this.usersPath.lastModified() == this.usersFileDate; + if (usersFileUnchanged && !this.userMapDirty) { + logger.warn("Not persisting of users as current file is unchanged and users data is not dirty"); //$NON-NLS-1$ + } else { + + // delegate writing + PrivilegeUsersDomWriter modelWriter = new PrivilegeUsersDomWriter(getAllUsers(), this.usersPath); + modelWriter.write(); + + this.userMapDirty = false; + saved = true; + } + + // get roles file + boolean rolesFileUnchanged = this.rolesPath.exists() && this.rolesPath.lastModified() == this.rolesFileDate; + if (rolesFileUnchanged && !this.roleMapDirty) { + logger.warn("Not persisting of roles as current file is unchanged and roles data is not dirty"); //$NON-NLS-1$ + } else { + + // delegate writing + PrivilegeRolesDomWriter modelWriter = new PrivilegeRolesDomWriter(getAllRoles(), this.rolesPath); + modelWriter.write(); + + this.roleMapDirty = false; + saved = true; + } // reset dirty states - this.userMapDirty = false; - this.roleMapDirty = false; - return true; + return saved; } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java similarity index 95% rename from src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index 83ff72744..041ce2f5f 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -229,9 +229,14 @@ public class XmlConstants { public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; /** - * XML_PARAM_MODEL_FILE = "modelXmlFile" : + * XML_PARAM_USERS_FILE = "usersXmlFile" : */ - public static final String XML_PARAM_MODEL_FILE = "modelXmlFile"; + public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; + + /** + * XML_PARAM_ROLES_FILE = "rolesXmlFile" : + */ + public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; /** * XML_PARAM_BASE_PATH = "basePath" : diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/Certificate.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java diff --git a/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/IPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java diff --git a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/Restrictable.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/RoleRep.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java diff --git a/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/UserRep.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/UserState.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/internal/Role.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/internal/User.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/ElementParser.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java new file mode 100644 index 000000000..a42963ecf --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.xml; + +import java.io.File; +import java.util.List; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.helper.XmlHelper; + +/** + * @author Robert von Burg + */ +public class PrivilegeRolesDomWriter { + + private List roles; + private File modelFile; + + /** + * @param users + * @param roles + * @param modelFile + */ + public PrivilegeRolesDomWriter(List roles, File modelFile) { + this.roles = roles; + this.modelFile = modelFile; + } + + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_ROLES); + doc.appendChild(rootElement); + + this.roles.stream().sorted((r1, r2) -> r1.getName().compareTo(r2.getName())).forEach(role -> { + + // create the role element + Element roleElement = doc.createElement(XmlConstants.XML_ROLE); + rootElement.appendChild(roleElement); + + roleElement.setAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + + for (String privilegeName : role.getPrivilegeNames()) { + IPrivilege privilege = role.getPrivilege(privilegeName); + + // create the privilege element + Element privilegeElement = doc.createElement(XmlConstants.XML_PRIVILEGE); + roleElement.appendChild(privilegeElement); + + privilegeElement.setAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.setAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + + // add the all allowed element + if (privilege.isAllAllowed()) { + Element allAllowedElement = doc.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setTextContent(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.appendChild(allAllowedElement); + } + + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = doc.createElement(XmlConstants.XML_DENY); + denyValueElement.setTextContent(denyValue); + privilegeElement.appendChild(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = doc.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setTextContent(allowValue); + privilegeElement.appendChild(allowValueElement); + } + } + }); + + // write the container file to disk + XmlHelper.writeDocument(doc, this.modelFile); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java similarity index 63% rename from src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java index 438b91d7a..5efa039b4 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java @@ -22,7 +22,6 @@ import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; @@ -34,41 +33,25 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.utils.helper.StringHelper; /** * @author Robert von Burg */ -public class PrivilegeModelSaxReader extends DefaultHandler { +public class PrivilegeRolesSaxReader extends DefaultHandler { - protected static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); + protected static final Logger logger = LoggerFactory.getLogger(PrivilegeRolesSaxReader.class); private Deque buildersStack = new ArrayDeque<>(); - private List users; private List roles; - private boolean insideUser; - - public PrivilegeModelSaxReader() { - this.users = new ArrayList<>(); + public PrivilegeRolesSaxReader() { this.roles = new ArrayList<>(); } - /** - * @return the users - */ - public List getUsers() { - return this.users; - } - - /** - * @return the roles - */ public List getRoles() { return this.roles; } @@ -76,13 +59,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals(XmlConstants.XML_USER)) { - this.buildersStack.push(new UserParser()); - this.insideUser = true; + if (qName.equals(XmlConstants.XML_ROLE)) { + this.buildersStack.push(new RoleParser()); } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { this.buildersStack.push(new PropertyParser()); - } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { - this.buildersStack.push(new RoleParser()); } if (!this.buildersStack.isEmpty()) @@ -102,13 +82,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals(XmlConstants.XML_USER)) { + if (qName.equals(XmlConstants.XML_ROLE)) { elementParser = this.buildersStack.pop(); - this.insideUser = false; } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { - elementParser = this.buildersStack.pop(); } if (!this.buildersStack.isEmpty() && elementParser != null) @@ -160,7 +137,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { this.text = new StringBuilder(); @@ -215,94 +193,6 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } } -// -// Application -// Administrator -// ENABLED -// en_GB -// -// PrivilegeAdmin -// AppUser -// -// -// -// -// -// - - public class UserParser extends ElementParserAdapter { - - StringBuilder text; - - String userId; - String username; - String password; - String firstName; - String lastname; - UserState userState; - Locale locale; - Set userRoles; - Map parameters; - - public UserParser() { - this.userRoles = new HashSet<>(); - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - - this.text = new StringBuilder(); - - if (qName.equals(XmlConstants.XML_USER)) { - this.userId = attributes.getValue(XmlConstants.XML_ATTR_USER_ID); - this.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME); - this.password = attributes.getValue(XmlConstants.XML_ATTR_PASSWORD); - } - } - - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - this.text.append(ch, start, length); - } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - - if (qName.equals(XmlConstants.XML_FIRSTNAME)) { - this.firstName = this.text.toString().trim(); - } else if (qName.equals(XmlConstants.XML_LASTNAME)) { - this.lastname = this.text.toString().trim(); - } else if (qName.equals(XmlConstants.XML_STATE)) { - this.userState = UserState.valueOf(this.text.toString().trim()); - } else if (qName.equals(XmlConstants.XML_LOCALE)) { - this.locale = new Locale(this.text.toString().trim()); - } else if (qName.equals(XmlConstants.XML_ROLE)) { - this.userRoles.add(this.text.toString().trim()); - } else if (qName.equals(XmlConstants.XML_ROLES)) { - // NO-OP - } else if (qName.equals(XmlConstants.XML_PARAMETER)) { - // NO-OP - } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { - // NO-OP - } else if (qName.equals(XmlConstants.XML_USER)) { - - User user = new User(this.userId, this.username, this.password, this.firstName, this.lastname, - this.userState, this.userRoles, this.locale, this.parameters); - logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$ - getUsers().add(user); - } else { - throw new IllegalArgumentException("Unhandled tag " + qName); - } - } - - @Override - public void notifyChild(ElementParser child) { - if (child instanceof PropertyParser) { - this.parameters = ((PropertyParser) child).parameterMap; - } - } - } - class PropertyParser extends ElementParserAdapter { // @@ -310,7 +200,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { public Map parameterMap = new HashMap<>(); @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { if (qName.equals(XmlConstants.XML_PROPERTY)) { String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java similarity index 61% rename from src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java index a79efe2d8..77b7879f0 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java @@ -23,28 +23,24 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg - * */ -public class PrivilegeModelDomWriter { +public class PrivilegeUsersDomWriter { private List users; - private List roles; private File modelFile; /** - * + * @param users + * @param modelFile */ - public PrivilegeModelDomWriter(List users, List roles, File modelFile) { + public PrivilegeUsersDomWriter(List users, File modelFile) { this.users = users; - this.roles = roles; this.modelFile = modelFile; } @@ -52,17 +48,14 @@ public class PrivilegeModelDomWriter { // create document root Document doc = XmlHelper.createDocument(); - Element rootElement = doc.createElement(XmlConstants.XML_ROOT_PRIVILEGE_USERS_AND_ROLES); + Element rootElement = doc.createElement(XmlConstants.XML_USERS); doc.appendChild(rootElement); - Element usersElement = doc.createElement(XmlConstants.XML_USERS); - rootElement.appendChild(usersElement); - this.users.stream().sorted((u1, u2) -> u1.getUserId().compareTo(u2.getUserId())).forEach(user -> { // create the user element Element userElement = doc.createElement(XmlConstants.XML_USER); - usersElement.appendChild(userElement); + rootElement.appendChild(userElement); userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); @@ -104,10 +97,10 @@ public class PrivilegeModelDomWriter { // add the parameters if (!user.getProperties().isEmpty()) { - Element parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + Element parametersElement = doc.createElement(XmlConstants.XML_PROPERTIES); userElement.appendChild(parametersElement); for (Entry entry : user.getProperties().entrySet()) { - Element paramElement = doc.createElement(XmlConstants.XML_PARAMETER); + Element paramElement = doc.createElement(XmlConstants.XML_PROPERTY); paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); parametersElement.appendChild(paramElement); @@ -115,50 +108,6 @@ public class PrivilegeModelDomWriter { } }); - Element rolesElement = doc.createElement(XmlConstants.XML_ROLES); - rootElement.appendChild(rolesElement); - - this.roles.stream().sorted((r1, r2) -> r1.getName().compareTo(r2.getName())).forEach(role -> { - - // create the role element - Element roleElement = doc.createElement(XmlConstants.XML_ROLE); - rolesElement.appendChild(roleElement); - - roleElement.setAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - - for (String privilegeName : role.getPrivilegeNames()) { - IPrivilege privilege = role.getPrivilege(privilegeName); - - // create the privilege element - Element privilegeElement = doc.createElement(XmlConstants.XML_PRIVILEGE); - roleElement.appendChild(privilegeElement); - - privilegeElement.setAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); - privilegeElement.setAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); - - // add the all allowed element - if (privilege.isAllAllowed()) { - Element allAllowedElement = doc.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setTextContent(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.appendChild(allAllowedElement); - } - - // add all the deny values - for (String denyValue : privilege.getDenyList()) { - Element denyValueElement = doc.createElement(XmlConstants.XML_DENY); - denyValueElement.setTextContent(denyValue); - privilegeElement.appendChild(denyValueElement); - } - - // add all the allow values - for (String allowValue : privilege.getAllowList()) { - Element allowValueElement = doc.createElement(XmlConstants.XML_ALLOW); - allowValueElement.setTextContent(allowValue); - privilegeElement.appendChild(allowValueElement); - } - } - }); - // write the container file to disk XmlHelper.writeDocument(doc, this.modelFile); } diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java new file mode 100644 index 000000000..701c617c2 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java @@ -0,0 +1,209 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ch.eitchnet.privilege.xml; + +import java.text.MessageFormat; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author Robert von Burg + */ +public class PrivilegeUsersSaxReader extends DefaultHandler { + + protected static final Logger logger = LoggerFactory.getLogger(PrivilegeUsersSaxReader.class); + + private Deque buildersStack = new ArrayDeque<>(); + + private List users; + + public PrivilegeUsersSaxReader() { + this.users = new ArrayList<>(); + } + + /** + * @return the users + */ + public List getUsers() { + return this.users; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals(XmlConstants.XML_USER)) { + this.buildersStack.push(new UserParser()); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + this.buildersStack.push(new PropertyParser()); + } + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().startElement(uri, localName, qName, attributes); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().characters(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().endElement(uri, localName, qName); + + ElementParser elementParser = null; + if (qName.equals(XmlConstants.XML_USER)) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + elementParser = this.buildersStack.pop(); + } + + if (!this.buildersStack.isEmpty() && elementParser != null) + this.buildersStack.peek().notifyChild(elementParser); + } + +// +// Application +// Administrator +// ENABLED +// en_GB +// +// PrivilegeAdmin +// AppUser +// +// +// +// +// +// + + public class UserParser extends ElementParserAdapter { + + StringBuilder text; + + String userId; + String username; + String password; + String firstName; + String lastname; + UserState userState; + Locale locale; + Set userRoles; + Map parameters; + + public UserParser() { + this.userRoles = new HashSet<>(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + this.text = new StringBuilder(); + + if (qName.equals(XmlConstants.XML_USER)) { + this.userId = attributes.getValue(XmlConstants.XML_ATTR_USER_ID); + this.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME); + this.password = attributes.getValue(XmlConstants.XML_ATTR_PASSWORD); + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + this.text.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (qName.equals(XmlConstants.XML_FIRSTNAME)) { + this.firstName = this.text.toString().trim(); + } else if (qName.equals(XmlConstants.XML_LASTNAME)) { + this.lastname = this.text.toString().trim(); + } else if (qName.equals(XmlConstants.XML_STATE)) { + this.userState = UserState.valueOf(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_LOCALE)) { + this.locale = new Locale(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_ROLE)) { + this.userRoles.add(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_ROLES)) { + // NO-OP + } else if (qName.equals(XmlConstants.XML_PARAMETER)) { + // NO-OP + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { + // NO-OP + } else if (qName.equals(XmlConstants.XML_USER)) { + + User user = new User(this.userId, this.username, this.password, this.firstName, this.lastname, + this.userState, this.userRoles, this.locale, this.parameters); + logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$ + getUsers().add(user); + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); + } + } + + @Override + public void notifyChild(ElementParser child) { + if (child instanceof PropertyParser) { + this.parameters = ((PropertyParser) child).parameterMap; + } + } + } + + class PropertyParser extends ElementParserAdapter { + +// + + public Map parameterMap = new HashMap<>(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (qName.equals(XmlConstants.XML_PROPERTY)) { + String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); + this.parameterMap.put(key, value); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + // NO-OP + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); + } + } + + public Map getParameterMap() { + return this.parameterMap; + } + } +} diff --git a/src/main/resources/Privilege.xsd b/ch.eitchnet.privilege/src/main/resources/Privilege.xsd similarity index 100% rename from src/main/resources/Privilege.xsd rename to ch.eitchnet.privilege/src/main/resources/Privilege.xsd diff --git a/src/main/resources/PrivilegeMessages.properties b/ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties similarity index 100% rename from src/main/resources/PrivilegeMessages.properties rename to ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties diff --git a/src/main/resources/PrivilegeModel.xsd b/ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd similarity index 100% rename from src/main/resources/PrivilegeModel.xsd rename to ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd diff --git a/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java similarity index 89% rename from src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java index b7b2e1c57..9f1c1a3ce 100644 --- a/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java @@ -43,21 +43,24 @@ public class AbstractPrivilegeTest { } } - protected static void prepareConfigs(String dst, String configFilename, String modelFilename) { + protected static void prepareConfigs(String dst, String configFilename, String usersFilename, + String rolesFilename) { try { String pwd = System.getProperty("user.dir"); File configPath = new File(pwd, "config"); File privilegeConfigFile = new File(configPath, configFilename); - File privilegeModelFile = new File(configPath, modelFilename); + File privilegeUsersFile = new File(configPath, usersFilename); + File privilegeRolesFile = new File(configPath, rolesFilename); File targetPath = new File(pwd, "target/" + dst); if (!targetPath.mkdirs()) throw new RuntimeException("Could not create parent " + targetPath); File dstConfig = new File(targetPath, configFilename); - File dstModel = new File(targetPath, modelFilename); + File dstUsers = new File(targetPath, usersFilename); + File dstRoles = new File(targetPath, rolesFilename); // write config String config = new String(Files.readAllBytes(privilegeConfigFile.toPath()), "UTF-8"); @@ -65,7 +68,8 @@ public class AbstractPrivilegeTest { Files.write(dstConfig.toPath(), config.getBytes("UTF-8")); // copy model - Files.copy(privilegeModelFile.toPath(), dstModel.toPath()); + Files.copy(privilegeUsersFile.toPath(), dstUsers.toPath()); + Files.copy(privilegeRolesFile.toPath(), dstRoles.toPath()); } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java similarity index 95% rename from src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java index a032c980a..57f6b9d03 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java @@ -15,7 +15,8 @@ public class PersistSessionsTest extends AbstractPrivilegeTest { @BeforeClass public static void init() throws Exception { removeConfigs(PersistSessionsTest.class.getSimpleName()); - prepareConfigs(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeModel.xml"); + prepareConfigs(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", + "PrivilegeRoles.xml"); } @AfterClass diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java similarity index 97% rename from src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java index f78edbb23..2fd50fad1 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java @@ -35,7 +35,7 @@ public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { public static void init() throws Exception { removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); prepareConfigs(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml", - "PrivilegeModelMerge.xml"); + "PrivilegeUsersMerge.xml", "PrivilegeRolesMerge.xml"); } @AfterClass diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java similarity index 99% rename from src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 674ccaebb..242c265bb 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -89,7 +89,8 @@ public class PrivilegeTest extends AbstractPrivilegeTest { @BeforeClass public static void init() throws Exception { removeConfigs(PrivilegeTest.class.getSimpleName()); - prepareConfigs(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeModel.xml"); + prepareConfigs(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", + "PrivilegeRoles.xml"); } @AfterClass diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java similarity index 71% rename from src/test/java/ch/eitchnet/privilege/test/XmlTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index e1ddd7e72..54a2e1b4b 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -50,8 +50,10 @@ import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; -import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; +import ch.eitchnet.privilege.xml.PrivilegeRolesDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeRolesSaxReader; +import ch.eitchnet.privilege.xml.PrivilegeUsersDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeUsersSaxReader; import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.helper.XmlHelper; @@ -92,7 +94,12 @@ public class XmlTest { throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); } - tmpFile = new File("target/test/PrivilegeModelTest.xml"); + tmpFile = new File("target/test/PrivilegeUsersTest.xml"); + if (tmpFile.exists() && !tmpFile.delete()) { + throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); + } + + tmpFile = new File("target/test/PrivilegeRolesTest.xml"); if (tmpFile.exists() && !tmpFile.delete()) { throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); } @@ -123,7 +130,7 @@ public class XmlTest { assertEquals(6, containerModel.getParameterMap().size()); assertEquals(3, containerModel.getPolicies().size()); assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); - assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); + assertEquals(3, containerModel.getPersistenceHandlerParameterMap().size()); // TODO extend assertions to actual model } @@ -158,21 +165,16 @@ public class XmlTest { } @Test - public void canReadModel() { + public void canReadUsers() { - PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); - File xmlFile = new File("config/PrivilegeModel.xml"); + PrivilegeUsersSaxReader xmlHandler = new PrivilegeUsersSaxReader(); + File xmlFile = new File("config/PrivilegeUsers.xml"); XmlHelper.parseDocument(xmlFile, xmlHandler); List users = xmlHandler.getUsers(); assertNotNull(users); - List roles = xmlHandler.getRoles(); - assertNotNull(roles); assertEquals(3, users.size()); - assertEquals(6, roles.size()); - - // assert model // // users @@ -204,6 +206,21 @@ public class XmlTest { assertEquals("en_gb", systemAdmin.getLocale().toString()); assertThat(systemAdmin.getRoles(), containsInAnyOrder("system_admin_privileges")); assertTrue(systemAdmin.getProperties().isEmpty()); + } + + @Test + public void canReadRoles() { + + PrivilegeRolesSaxReader xmlHandler = new PrivilegeRolesSaxReader(); + File xmlFile = new File("config/PrivilegeRoles.xml"); + XmlHelper.parseDocument(xmlFile, xmlHandler); + + List roles = xmlHandler.getRoles(); + assertNotNull(roles); + + assertEquals(6, roles.size()); + + // assert model // // roles @@ -314,32 +331,71 @@ public class XmlTest { } @Test - public void canWriteModel() { + public void canWriteUsers() { Map propertyMap; Set userRoles; - Map privilegeMap; List users = new ArrayList<>(); propertyMap = new HashMap<>(); propertyMap.put("prop1", "value1"); userRoles = new HashSet<>(); userRoles.add("role1"); - users.add(new User("1", "user1", "blabla", "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, - propertyMap)); + User user1 = new User("1", "user1", "blabla", "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, + propertyMap); + users.add(user1); propertyMap = new HashMap<>(); propertyMap.put("prop2", "value2"); userRoles = new HashSet<>(); userRoles.add("role2"); - users.add(new User("2", "user2", "haha", "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, - propertyMap)); + User user2 = new User("2", "user2", "haha", "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, + propertyMap); + users.add(user2); + File modelFile = new File("./target/test/PrivilegeUsersTest.xml"); + PrivilegeUsersDomWriter configSaxWriter = new PrivilegeUsersDomWriter(users, modelFile); + configSaxWriter.write(); + + PrivilegeUsersSaxReader xmlHandler = new PrivilegeUsersSaxReader(); + XmlHelper.parseDocument(modelFile, xmlHandler); + + List parsedUsers = xmlHandler.getUsers(); + assertNotNull(parsedUsers); + assertEquals(2, parsedUsers.size()); + + User parsedUser1 = parsedUsers.stream().filter(u -> u.getUsername().equals("user1")).findAny().get(); + User parsedUser2 = parsedUsers.stream().filter(u -> u.getUsername().equals("user2")).findAny().get(); + + assertEquals(user1.getFirstname(), parsedUser1.getFirstname()); + assertEquals(user1.getLastname(), parsedUser1.getLastname()); + assertEquals(user1.getLocale(), parsedUser1.getLocale()); + assertEquals(user1.getPassword(), parsedUser1.getPassword()); + assertEquals(user1.getProperties(), parsedUser1.getProperties()); + assertEquals(user1.getUserId(), parsedUser1.getUserId()); + assertEquals(user1.getUserState(), parsedUser1.getUserState()); + assertEquals(user1.getRoles(), parsedUser1.getRoles()); + + assertEquals(user2.getFirstname(), parsedUser2.getFirstname()); + assertEquals(user2.getLastname(), parsedUser2.getLastname()); + assertEquals(user2.getLocale(), parsedUser2.getLocale()); + assertEquals(user2.getPassword(), parsedUser2.getPassword()); + assertEquals(user2.getProperties(), parsedUser2.getProperties()); + assertEquals(user2.getUserId(), parsedUser2.getUserId()); + assertEquals(user2.getUserState(), parsedUser2.getUserState()); + assertEquals(user2.getRoles(), parsedUser2.getRoles()); + } + + @Test + public void canWriteRoles() { + + Map privilegeMap; List roles = new ArrayList<>(); Set list = Collections.emptySet(); privilegeMap = new HashMap<>(); privilegeMap.put("priv1", new PrivilegeImpl("priv1", "DefaultPrivilege", true, list, list)); - roles.add(new Role("role1", privilegeMap)); + Role role1 = new Role("role1", privilegeMap); + roles.add(role1); privilegeMap = new HashMap<>(); Set denyList = new HashSet<>(); @@ -347,13 +403,52 @@ public class XmlTest { Set allowList = new HashSet<>(); allowList.add("other"); privilegeMap.put("priv2", new PrivilegeImpl("priv2", "DefaultPrivilege", false, denyList, allowList)); - roles.add(new Role("role2", privilegeMap)); + Role role2 = new Role("role2", privilegeMap); + roles.add(role2); - File modelFile = new File("./target/test/PrivilegeModelTest.xml"); - PrivilegeModelDomWriter configSaxWriter = new PrivilegeModelDomWriter(users, roles, modelFile); + File modelFile = new File("./target/test/PrivilegeRolesTest.xml"); + PrivilegeRolesDomWriter configSaxWriter = new PrivilegeRolesDomWriter(roles, modelFile); configSaxWriter.write(); - String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("9fac0049862b2b54b41b08b0e12a9cb105894d57a1500d6603c473661f4c7313", fileHash); + PrivilegeRolesSaxReader xmlHandler = new PrivilegeRolesSaxReader(); + XmlHelper.parseDocument(modelFile, xmlHandler); + + List parsedRoles = xmlHandler.getRoles(); + assertNotNull(parsedRoles); + assertEquals(2, parsedRoles.size()); + + assertEquals(2, parsedRoles.size()); + Role parsedRole1 = parsedRoles.stream().filter(r -> r.getName().equals("role1")).findAny().get(); + Role parsedRole2 = parsedRoles.stream().filter(r -> r.getName().equals("role2")).findAny().get(); + + Set privilegeNames = role1.getPrivilegeNames(); + assertEquals(privilegeNames, parsedRole1.getPrivilegeNames()); + for (String privilegeName : privilegeNames) { + IPrivilege privilege = role1.getPrivilege(privilegeName); + IPrivilege privilege2 = parsedRole1.getPrivilege(privilegeName); + assertNotNull(privilege); + assertNotNull(privilege2); + + assertEquals(privilege.isAllAllowed(), privilege2.isAllAllowed()); + assertEquals(privilege.getAllowList(), privilege2.getAllowList()); + assertEquals(privilege.getDenyList(), privilege2.getDenyList()); + assertEquals(privilege.getName(), privilege2.getName()); + assertEquals(privilege.getPolicy(), privilege2.getPolicy()); + } + + assertEquals(role2.getPrivilegeNames(), parsedRole2.getPrivilegeNames()); + privilegeNames = role2.getPrivilegeNames(); + for (String privilegeName : privilegeNames) { + IPrivilege privilege = role2.getPrivilege(privilegeName); + IPrivilege privilege2 = parsedRole2.getPrivilege(privilegeName); + assertNotNull(privilege); + assertNotNull(privilege2); + + assertEquals(privilege.isAllAllowed(), privilege2.isAllAllowed()); + assertEquals(privilege.getAllowList(), privilege2.getAllowList()); + assertEquals(privilege.getDenyList(), privilege2.getDenyList()); + assertEquals(privilege.getName(), privilege2.getName()); + assertEquals(privilege.getPolicy(), privilege2.getPolicy()); + } } } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java similarity index 100% rename from src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java similarity index 100% rename from src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java similarity index 100% rename from src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java similarity index 100% rename from src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java diff --git a/src/test/resources/log4j.xml b/ch.eitchnet.privilege/src/test/resources/log4j.xml similarity index 100% rename from src/test/resources/log4j.xml rename to ch.eitchnet.privilege/src/test/resources/log4j.xml diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml deleted file mode 100644 index 83131e8bc..000000000 --- a/config/PrivilegeModel.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - Application - Administrator - ENABLED - en_GB - - PrivilegeAdmin - AppUser - - - - - - - - - System User - Administrator - SYSTEM - en_GB - - system_admin_privileges - - - - - System User - Administrator - SYSTEM - en_GB - - system_admin_privileges - - - - - - - - - - Persist - Reload - GetPolicies - - - - true - - - true - - - true - - - true - - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - ENABLED - DISABLED - SYSTEM - - - true - - - - - - true - - - - - - true - - - - - - true - - - - - - ch.eitchnet.privilege.test.model.TestSystemUserAction - ch.eitchnet.privilege.test.model.TestSystemUserActionDeny - - - true - - - - - - hello - goodbye - - - - - \ No newline at end of file diff --git a/config/PrivilegeModelMerge.xml b/config/PrivilegeModelMerge.xml deleted file mode 100644 index 97fb7065d..000000000 --- a/config/PrivilegeModelMerge.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - System User - Administrator - ENABLED - en_GB - - RoleA1 - RoleA2 - - - - System User - Administrator - ENABLED - en_GB - - RoleB1 - RoleB2 - - - - - - - - - allow1 - - - - - true - - - - - - allow1 - deny1 - - - - - allow2 - deny2 - - - - - \ No newline at end of file From cc10f6f402c2bb43be768fa02b3a41eb16f9307a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 10 Feb 2016 20:33:37 +0100 Subject: [PATCH 437/457] [Project] Moved everything to a sub directory --- LICENSE => ch.eitchnet.xmlpers/LICENSE | 0 README.md => ch.eitchnet.xmlpers/README.md | 0 pom.xml => ch.eitchnet.xmlpers/pom.xml | 0 .../src}/main/java/ch/eitchnet/xmlpers/api/DomParser.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/api/FileDao.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/api/FileIo.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/api/IoMode.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/api/IoOperation.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java | 4 ++-- .../main/java/ch/eitchnet/xmlpers/api/ModificationResult.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java | 2 +- .../src}/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java | 0 .../java/ch/eitchnet/xmlpers/api/PersistenceConstants.java | 0 .../main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java | 0 .../ch/eitchnet/xmlpers/api/PersistenceContextFactory.java | 0 .../xmlpers/api/PersistenceContextFactoryDelegator.java | 0 .../main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java | 0 .../ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java | 0 .../main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java | 0 .../java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/api/SaxParser.java | 0 .../ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java | 0 .../main/java/ch/eitchnet/xmlpers/api/TransactionResult.java | 0 .../main/java/ch/eitchnet/xmlpers/api/TransactionState.java | 0 .../java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java | 0 .../ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java | 0 .../ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java | 0 .../eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java | 0 .../main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java | 0 .../main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java | 0 .../main/java/ch/eitchnet/xmlpers/objref/LockableObject.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java | 0 .../java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java | 0 .../main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/objref/RootRef.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java | 0 .../main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java | 0 .../src}/main/java/ch/eitchnet/xmlpers/util/DomUtil.java | 0 .../main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java | 0 .../ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java | 0 .../src}/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java | 0 .../src}/test/java/ch/eitchnet/xmlpers/test/LockingTest.java | 0 .../test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java | 0 .../java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java | 0 .../src}/test/java/ch/eitchnet/xmlpers/test/RealmTest.java | 0 .../java/ch/eitchnet/xmlpers/test/TransactionResultTest.java | 0 .../src}/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java | 2 +- .../ch/eitchnet/xmlpers/test/impl/BookContextFactory.java | 0 .../java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java | 0 .../java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java | 0 .../java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java | 0 .../ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java | 0 .../java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java | 0 .../ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java | 0 .../java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java | 0 .../java/ch/eitchnet/xmlpers/test/impl/TestConstants.java | 0 .../src}/test/java/ch/eitchnet/xmlpers/test/model/Book.java | 0 .../java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java | 0 .../test/java/ch/eitchnet/xmlpers/test/model/MyModel.java | 2 +- .../test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java | 0 {src => ch.eitchnet.xmlpers/src}/test/resources/log4j.xml | 0 63 files changed, 5 insertions(+), 5 deletions(-) rename LICENSE => ch.eitchnet.xmlpers/LICENSE (100%) rename README.md => ch.eitchnet.xmlpers/README.md (100%) rename pom.xml => ch.eitchnet.xmlpers/pom.xml (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/DomParser.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/FileDao.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/FileIo.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/IoMode.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/IoOperation.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java (98%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java (99%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/SaxParser.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/TransactionState.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/RootRef.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/util/DomUtil.java (100%) rename {src => ch.eitchnet.xmlpers/src}/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/LockingTest.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/RealmTest.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java (99%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/model/Book.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java (96%) rename {src => ch.eitchnet.xmlpers/src}/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java (100%) rename {src => ch.eitchnet.xmlpers/src}/test/resources/log4j.xml (100%) diff --git a/LICENSE b/ch.eitchnet.xmlpers/LICENSE similarity index 100% rename from LICENSE rename to ch.eitchnet.xmlpers/LICENSE diff --git a/README.md b/ch.eitchnet.xmlpers/README.md similarity index 100% rename from README.md rename to ch.eitchnet.xmlpers/README.md diff --git a/pom.xml b/ch.eitchnet.xmlpers/pom.xml similarity index 100% rename from pom.xml rename to ch.eitchnet.xmlpers/pom.xml diff --git a/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/DomParser.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/FileDao.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/FileIo.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/IoMode.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java similarity index 98% rename from src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java index fd6f117b4..7a114bb7a 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java @@ -149,7 +149,7 @@ public class MetadataDao { throw new IllegalArgumentException(msg); } - Set keySet = new HashSet(); + Set keySet = new HashSet<>(); File[] subTypeFiles = queryPath.listFiles(); for (File subTypeFile : subTypeFiles) { if (subTypeFile.isDirectory()) { @@ -179,7 +179,7 @@ public class MetadataDao { throw new IllegalArgumentException(msg); } - Set keySet = new HashSet(); + Set keySet = new HashSet<>(); File[] subTypeFiles = queryPath.listFiles(); for (File subTypeFile : subTypeFiles) { diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java similarity index 99% rename from src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java index f5064db6e..493a749c8 100644 --- a/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java @@ -114,7 +114,7 @@ public class ObjectDao { long removed = 0; - Set refs = new HashSet(); + Set refs = new HashSet<>(); typeRef.lock(); refs.add(typeRef); try { diff --git a/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java diff --git a/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java diff --git a/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java diff --git a/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java diff --git a/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java diff --git a/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java diff --git a/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java similarity index 100% rename from src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java rename to ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java similarity index 99% rename from src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java index 690cf4e6f..32af884e2 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java @@ -89,7 +89,7 @@ public class XmlTestMain { Document document = docBuilder.parse(file); Element rootElement = document.getDocumentElement(); - List resources = new ArrayList(); + List resources = new ArrayList<>(); NodeList resElements = rootElement.getElementsByTagName("Resource"); for (int i = 0; i < resElements.getLength(); i++) { diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/model/Book.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java similarity index 96% rename from src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java index 2bac6fdf5..f8f90058a 100644 --- a/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java @@ -25,7 +25,7 @@ public class MyModel { private String id; private String name; private String type; - private Map parameters = new HashMap(); + private Map parameters = new HashMap<>(); /** * diff --git a/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java similarity index 100% rename from src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java rename to ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java diff --git a/src/test/resources/log4j.xml b/ch.eitchnet.xmlpers/src/test/resources/log4j.xml similarity index 100% rename from src/test/resources/log4j.xml rename to ch.eitchnet.xmlpers/src/test/resources/log4j.xml From 1a4f2ee0b89e2d9492933d45ac05f28d05cbcb4c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 10:21:55 +0200 Subject: [PATCH 438/457] [Project] Removed .gitignore in prep for merge --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 18d2ca6cd..000000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target/ -.classpath -.project -.settings/ From 9d022dcd6fb9e91df981ddcae20b53f4785fdfb3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 10:22:39 +0200 Subject: [PATCH 439/457] [Project] Removed .gitignore in prep for merge --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 18d2ca6cd..000000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target/ -.classpath -.project -.settings/ From cf736bcca3643a6fd00b66fd52215644cda28dc7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 17:38:30 +0200 Subject: [PATCH 440/457] [Major] Removed submodules --- .gitmodules | 12 ------------ ch.eitchnet.parent | 1 - ch.eitchnet.privilege | 1 - ch.eitchnet.utils | 1 - ch.eitchnet.xmlpers | 1 - 5 files changed, 16 deletions(-) delete mode 100644 .gitmodules delete mode 160000 ch.eitchnet.parent delete mode 160000 ch.eitchnet.privilege delete mode 160000 ch.eitchnet.utils delete mode 160000 ch.eitchnet.xmlpers diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 2e895355b..000000000 --- a/.gitmodules +++ /dev/null @@ -1,12 +0,0 @@ -[submodule "ch.eitchnet.parent"] - path = ch.eitchnet.parent - url = https://github.com/eitchnet/ch.eitchnet.parent.git -[submodule "ch.eitchnet.utils"] - path = ch.eitchnet.utils - url = https://github.com/eitchnet/ch.eitchnet.utils.git -[submodule "ch.eitchnet.privilege"] - path = ch.eitchnet.privilege - url = https://github.com/eitchnet/ch.eitchnet.privilege.git -[submodule "ch.eitchnet.xmlpers"] - path = ch.eitchnet.xmlpers - url = https://github.com/eitchnet/ch.eitchnet.xmlpers.git diff --git a/ch.eitchnet.parent b/ch.eitchnet.parent deleted file mode 160000 index 538fc3c44..000000000 --- a/ch.eitchnet.parent +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 538fc3c44956b57535c2c15fcf99b3602a22dd28 diff --git a/ch.eitchnet.privilege b/ch.eitchnet.privilege deleted file mode 160000 index 9b05d8cbb..000000000 --- a/ch.eitchnet.privilege +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9b05d8cbbd6bb6e176c0ae5bf3210c517a48fc93 diff --git a/ch.eitchnet.utils b/ch.eitchnet.utils deleted file mode 160000 index 49b325eca..000000000 --- a/ch.eitchnet.utils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 49b325eca13ffe7fe9cfc51ab964b8f7decc8522 diff --git a/ch.eitchnet.xmlpers b/ch.eitchnet.xmlpers deleted file mode 160000 index 4ba00c83d..000000000 --- a/ch.eitchnet.xmlpers +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4ba00c83d661accb6a642d77ef0eda2ffc4ef4c3 From 86fa9573f742d9d066a88bb8487bcb54b265089d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 17:39:21 +0200 Subject: [PATCH 441/457] [Project] removed .gitignore --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 18d2ca6cd..000000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target/ -.classpath -.project -.settings/ From 12d8a71e80895bdc822f8dc9af7a1141d5bae8f7 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 17:42:16 +0200 Subject: [PATCH 442/457] [Major] Move ch.eitchnet projects to li.strolch --- .../LICENSE | 0 .../README.md | 0 .../config/PrivilegeConfig.xml | 0 .../config/PrivilegeConfigMerge.xml | 0 .../config/PrivilegeRoles.xml | 0 .../config/PrivilegeRolesMerge.xml | 0 .../config/PrivilegeUsers.xml | 0 .../config/PrivilegeUsersMerge.xml | 0 .../docs/PrivilegeAuthentication.dia | Bin .../docs/PrivilegeHandlers.dia | Bin .../docs/PrivilegeModelPrivilege.dia | Bin .../docs/PrivilegeModelUser.dia | Bin .../docs/TODO | 0 .../pom.xml | 0 .../privilege.jardesc | 0 .../privilege/base/AccessDeniedException.java | 0 .../privilege/base/InvalidCredentialsException.java | 0 .../privilege/base/PrivilegeConflictResolution.java | 0 .../eitchnet/privilege/base/PrivilegeException.java | 0 .../privilege/handler/DefaultEncryptionHandler.java | 0 .../privilege/handler/DefaultPrivilegeHandler.java | 0 .../privilege/handler/EncryptionHandler.java | 0 .../privilege/handler/PersistenceHandler.java | 0 .../privilege/handler/PrivilegeHandler.java | 0 .../privilege/handler/SystemUserAction.java | 0 .../privilege/handler/XmlPersistenceHandler.java | 0 .../helper/BootstrapConfigurationHelper.java | 0 .../privilege/helper/PasswordCreaterUI.java | 0 .../eitchnet/privilege/helper/PasswordCreator.java | 0 .../helper/PrivilegeInitializationHelper.java | 0 .../ch/eitchnet/privilege/helper/XmlConstants.java | 0 .../eitchnet/privilege/i18n/PrivilegeMessages.java | 0 .../ch/eitchnet/privilege/model/Certificate.java | 0 .../ch/eitchnet/privilege/model/IPrivilege.java | 0 .../eitchnet/privilege/model/PrivilegeContext.java | 0 .../ch/eitchnet/privilege/model/PrivilegeRep.java | 0 .../ch/eitchnet/privilege/model/Restrictable.java | 0 .../java/ch/eitchnet/privilege/model/RoleRep.java | 0 .../privilege/model/SimpleRestrictable.java | 0 .../java/ch/eitchnet/privilege/model/UserRep.java | 0 .../java/ch/eitchnet/privilege/model/UserState.java | 0 .../model/internal/PrivilegeContainerModel.java | 0 .../privilege/model/internal/PrivilegeImpl.java | 0 .../ch/eitchnet/privilege/model/internal/Role.java | 0 .../ch/eitchnet/privilege/model/internal/User.java | 0 .../eitchnet/privilege/policy/DefaultPrivilege.java | 0 .../eitchnet/privilege/policy/PrivilegePolicy.java | 0 .../privilege/policy/PrivilegePolicyHelper.java | 0 .../privilege/policy/RoleAccessPrivilege.java | 0 .../privilege/policy/UserAccessPrivilege.java | 0 .../UserAccessWithSameOrganisationPrivilege.java | 0 .../policy/UsernameFromCertificatePrivilege.java | 0 ...romCertificateWithSameOrganisationPrivilege.java | 0 .../privilege/xml/CertificateStubsDomWriter.java | 0 .../privilege/xml/CertificateStubsSaxReader.java | 0 .../ch/eitchnet/privilege/xml/ElementParser.java | 0 .../privilege/xml/ElementParserAdapter.java | 0 .../privilege/xml/PrivilegeConfigDomWriter.java | 0 .../privilege/xml/PrivilegeConfigSaxReader.java | 0 .../privilege/xml/PrivilegeRolesDomWriter.java | 0 .../privilege/xml/PrivilegeRolesSaxReader.java | 0 .../privilege/xml/PrivilegeUsersDomWriter.java | 0 .../privilege/xml/PrivilegeUsersSaxReader.java | 0 .../src/main/resources/Privilege.xsd | 0 .../src/main/resources/PrivilegeMessages.properties | 0 .../src/main/resources/PrivilegeModel.xsd | 0 .../privilege/test/AbstractPrivilegeTest.java | 0 .../privilege/test/PersistSessionsTest.java | 0 .../privilege/test/PrivilegeConflictMergeTest.java | 0 .../ch/eitchnet/privilege/test/PrivilegeTest.java | 0 .../java/ch/eitchnet/privilege/test/XmlTest.java | 0 .../privilege/test/model/TestRestrictable.java | 0 .../test/model/TestSystemRestrictable.java | 0 .../privilege/test/model/TestSystemUserAction.java | 0 .../test/model/TestSystemUserActionDeny.java | 0 .../src/test/resources/log4j.xml | 0 {ch.eitchnet.utils => li.strolch.utils}/LICENSE | 0 {ch.eitchnet.utils => li.strolch.utils}/README.md | 0 {ch.eitchnet.utils => li.strolch.utils}/pom.xml | 0 .../java/ch/eitchnet/communication/CommandKey.java | 0 .../communication/CommunicationConnection.java | 0 .../communication/CommunicationEndpoint.java | 0 .../eitchnet/communication/ConnectionException.java | 0 .../ch/eitchnet/communication/ConnectionInfo.java | 0 .../eitchnet/communication/ConnectionMessages.java | 0 .../ch/eitchnet/communication/ConnectionMode.java | 0 .../eitchnet/communication/ConnectionObserver.java | 0 .../ch/eitchnet/communication/ConnectionState.java | 0 .../communication/ConnectionStateObserver.java | 0 .../java/ch/eitchnet/communication/IoMessage.java | 0 .../ch/eitchnet/communication/IoMessageArchive.java | 0 .../communication/IoMessageStateObserver.java | 0 .../ch/eitchnet/communication/IoMessageVisitor.java | 0 .../communication/SimpleMessageArchive.java | 0 .../communication/StreamMessageVisitor.java | 0 .../java/ch/eitchnet/communication/chat/Chat.java | 0 .../ch/eitchnet/communication/chat/ChatClient.java | 0 .../eitchnet/communication/chat/ChatIoMessage.java | 0 .../communication/chat/ChatMessageVisitor.java | 0 .../ch/eitchnet/communication/chat/ChatServer.java | 0 .../communication/console/ConsoleEndpoint.java | 0 .../console/ConsoleMessageVisitor.java | 0 .../eitchnet/communication/file/FileEndpoint.java | 0 .../communication/file/FileEndpointMode.java | 0 .../communication/tcpip/ClientSocketEndpoint.java | 0 .../communication/tcpip/ServerSocketEndpoint.java | 0 .../tcpip/SocketEndpointConstants.java | 0 .../communication/tcpip/SocketMessageVisitor.java | 0 .../main/java/ch/eitchnet/db/DbConnectionCheck.java | 0 .../src/main/java/ch/eitchnet/db/DbConstants.java | 0 .../java/ch/eitchnet/db/DbDataSourceBuilder.java | 0 .../src/main/java/ch/eitchnet/db/DbException.java | 0 .../main/java/ch/eitchnet/db/DbMigrationState.java | 0 .../java/ch/eitchnet/db/DbSchemaVersionCheck.java | 0 .../java/ch/eitchnet/fileserver/FileClient.java | 0 .../java/ch/eitchnet/fileserver/FileClientUtil.java | 0 .../java/ch/eitchnet/fileserver/FileDeletion.java | 0 .../java/ch/eitchnet/fileserver/FileHandler.java | 0 .../main/java/ch/eitchnet/fileserver/FilePart.java | 0 .../java/ch/eitchnet/utils/StringMatchMode.java | 0 .../src/main/java/ch/eitchnet/utils/Version.java | 0 .../ch/eitchnet/utils/collections/DateRange.java | 0 .../utils/collections/DefaultedHashMap.java | 0 .../ch/eitchnet/utils/collections/MapOfLists.java | 0 .../ch/eitchnet/utils/collections/MapOfMaps.java | 0 .../java/ch/eitchnet/utils/collections/Paging.java | 0 .../java/ch/eitchnet/utils/collections/Tuple.java | 0 .../src/main/java/ch/eitchnet/utils/dbc/DBC.java | 0 .../ch/eitchnet/utils/exceptions/XmlException.java | 0 .../ch/eitchnet/utils/helper/AesCryptoHelper.java | 0 .../java/ch/eitchnet/utils/helper/ArraysHelper.java | 0 .../java/ch/eitchnet/utils/helper/AsciiHelper.java | 0 .../java/ch/eitchnet/utils/helper/BaseEncoding.java | 0 .../java/ch/eitchnet/utils/helper/ByteHelper.java | 0 .../java/ch/eitchnet/utils/helper/ClassHelper.java | 0 .../main/java/ch/eitchnet/utils/helper/DomUtil.java | 0 .../ch/eitchnet/utils/helper/ExceptionHelper.java | 0 .../java/ch/eitchnet/utils/helper/FileHelper.java | 0 .../java/ch/eitchnet/utils/helper/MathHelper.java | 0 .../ch/eitchnet/utils/helper/ProcessHelper.java | 0 .../ch/eitchnet/utils/helper/PropertiesHelper.java | 0 .../java/ch/eitchnet/utils/helper/StringHelper.java | 0 .../java/ch/eitchnet/utils/helper/SystemHelper.java | 0 .../java/ch/eitchnet/utils/helper/XmlDomSigner.java | 0 .../java/ch/eitchnet/utils/helper/XmlHelper.java | 0 .../ch/eitchnet/utils/io/FileProgressListener.java | 0 .../utils/io/FileStreamProgressWatcher.java | 0 .../utils/io/LoggingFileProgressListener.java | 0 .../utils/io/ProgressableFileInputStream.java | 0 .../java/ch/eitchnet/utils/iso8601/DateFormat.java | 0 .../ch/eitchnet/utils/iso8601/DurationFormat.java | 0 .../ch/eitchnet/utils/iso8601/FormatFactory.java | 0 .../java/ch/eitchnet/utils/iso8601/ISO8601.java | 0 .../ch/eitchnet/utils/iso8601/ISO8601Duration.java | 0 .../utils/iso8601/ISO8601FormatFactory.java | 0 .../ch/eitchnet/utils/iso8601/ISO8601Worktime.java | 0 .../ch/eitchnet/utils/iso8601/WorktimeFormat.java | 0 .../ch/eitchnet/utils/objectfilter/ObjectCache.java | 0 .../eitchnet/utils/objectfilter/ObjectFilter.java | 0 .../ch/eitchnet/utils/objectfilter/Operation.java | 0 .../java/ch/eitchnet/utils/xml/XmlKeyValue.java | 0 .../main/java/javanet/staxutils/Indentation.java | 0 .../javanet/staxutils/IndentingXMLStreamWriter.java | 0 .../staxutils/helpers/StreamWriterDelegate.java | 0 .../src/main/java/log4j.xml | 0 .../communication/AbstractEndpointTest.java | 0 .../eitchnet/communication/ConsoleEndpointTest.java | 0 .../ch/eitchnet/communication/FileEndpointTest.java | 0 .../communication/SimpleMessageArchiveTest.java | 0 .../eitchnet/communication/SocketEndpointTest.java | 0 .../communication/TestConnectionObserver.java | 0 .../ch/eitchnet/communication/TestIoMessage.java | 0 .../java/ch/eitchnet/utils/StringMatchModeTest.java | 0 .../test/java/ch/eitchnet/utils/VersionTest.java | 0 .../eitchnet/utils/collections/DateRangeTest.java | 0 .../utils/collections/DefaultedHashMapTest.java | 0 .../ch/eitchnet/utils/collections/PagingTest.java | 0 .../test/java/ch/eitchnet/utils/dbc/DBCTest.java | 0 .../eitchnet/utils/helper/AesCryptoHelperTest.java | 0 .../ch/eitchnet/utils/helper/BaseDecodingTest.java | 0 .../ch/eitchnet/utils/helper/BaseEncodingTest.java | 0 .../eitchnet/utils/helper/ExceptionHelperTest.java | 0 .../GenerateReverseBaseEncodingAlphabets.java | 0 .../utils/helper/ReplacePropertiesInTest.java | 0 .../ch/eitchnet/utils/helper/XmlSignHelperTest.java | 0 .../utils/objectfilter/ObjectFilterTest.java | 0 .../src/test/resources/SignedXmlFile.xml | 0 .../test/resources/SignedXmlFileWithNamespaces.xml | 0 .../src/test/resources/crypto_test_image.ico | Bin .../src/test/resources/crypto_test_long.txt | 0 .../src/test/resources/crypto_test_middle.txt | 0 .../src/test/resources/crypto_test_short.txt | 0 .../src/test/resources/log4j.xml | 0 .../src/test/resources/test.jks | Bin .../src/test/resources/test_data.csv | 0 {ch.eitchnet.xmlpers => li.strolch.xmlpers}/LICENSE | 0 .../README.md | 0 {ch.eitchnet.xmlpers => li.strolch.xmlpers}/pom.xml | 0 .../java/ch/eitchnet/xmlpers/api/DomParser.java | 0 .../main/java/ch/eitchnet/xmlpers/api/FileDao.java | 0 .../main/java/ch/eitchnet/xmlpers/api/FileIo.java | 0 .../main/java/ch/eitchnet/xmlpers/api/IoMode.java | 0 .../java/ch/eitchnet/xmlpers/api/IoOperation.java | 0 .../java/ch/eitchnet/xmlpers/api/MetadataDao.java | 0 .../ch/eitchnet/xmlpers/api/ModificationResult.java | 0 .../java/ch/eitchnet/xmlpers/api/ObjectDao.java | 0 .../java/ch/eitchnet/xmlpers/api/ParserFactory.java | 0 .../eitchnet/xmlpers/api/PersistenceConstants.java | 0 .../ch/eitchnet/xmlpers/api/PersistenceContext.java | 0 .../xmlpers/api/PersistenceContextFactory.java | 0 .../api/PersistenceContextFactoryDelegator.java | 0 .../ch/eitchnet/xmlpers/api/PersistenceManager.java | 0 .../xmlpers/api/PersistenceManagerLoader.java | 0 .../ch/eitchnet/xmlpers/api/PersistenceRealm.java | 0 .../xmlpers/api/PersistenceTransaction.java | 0 .../java/ch/eitchnet/xmlpers/api/SaxParser.java | 0 .../xmlpers/api/TransactionCloseStrategy.java | 0 .../ch/eitchnet/xmlpers/api/TransactionResult.java | 0 .../ch/eitchnet/xmlpers/api/TransactionState.java | 0 .../xmlpers/api/XmlPersistenceException.java | 0 .../xmlpers/impl/DefaultPersistenceManager.java | 0 .../xmlpers/impl/DefaultPersistenceRealm.java | 0 .../xmlpers/impl/DefaultPersistenceTransaction.java | 0 .../java/ch/eitchnet/xmlpers/impl/PathBuilder.java | 0 .../ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java | 0 .../ch/eitchnet/xmlpers/objref/IdOfTypeRef.java | 0 .../ch/eitchnet/xmlpers/objref/LockableObject.java | 0 .../java/ch/eitchnet/xmlpers/objref/ObjectRef.java | 0 .../xmlpers/objref/ObjectReferenceCache.java | 0 .../ch/eitchnet/xmlpers/objref/RefNameCreator.java | 0 .../java/ch/eitchnet/xmlpers/objref/RootRef.java | 0 .../java/ch/eitchnet/xmlpers/objref/SubTypeRef.java | 0 .../java/ch/eitchnet/xmlpers/objref/TypeRef.java | 0 .../ch/eitchnet/xmlpers/util/AssertionUtil.java | 0 .../main/java/ch/eitchnet/xmlpers/util/DomUtil.java | 0 .../ch/eitchnet/xmlpers/util/FilenameUtility.java | 0 .../xmlpers/test/AbstractPersistenceTest.java | 0 .../java/ch/eitchnet/xmlpers/test/FileDaoTest.java | 0 .../java/ch/eitchnet/xmlpers/test/LockingTest.java | 0 .../ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java | 0 .../xmlpers/test/ObjectDaoResourceTest.java | 0 .../java/ch/eitchnet/xmlpers/test/RealmTest.java | 0 .../xmlpers/test/TransactionResultTest.java | 0 .../java/ch/eitchnet/xmlpers/test/XmlTestMain.java | 0 .../xmlpers/test/impl/BookContextFactory.java | 0 .../eitchnet/xmlpers/test/impl/BookDomParser.java | 0 .../xmlpers/test/impl/BookParserFactory.java | 0 .../eitchnet/xmlpers/test/impl/BookSaxParser.java | 0 .../xmlpers/test/impl/MyModelContextFactory.java | 0 .../xmlpers/test/impl/MyModelDomParser.java | 0 .../xmlpers/test/impl/MyModelParserFactory.java | 0 .../xmlpers/test/impl/MyModelSaxParser.java | 0 .../eitchnet/xmlpers/test/impl/TestConstants.java | 0 .../java/ch/eitchnet/xmlpers/test/model/Book.java | 0 .../eitchnet/xmlpers/test/model/ModelBuilder.java | 0 .../ch/eitchnet/xmlpers/test/model/MyModel.java | 0 .../ch/eitchnet/xmlpers/test/model/MyParameter.java | 0 .../src/test/resources/log4j.xml | 0 pom.xml | 9 +++++---- 259 files changed, 5 insertions(+), 4 deletions(-) rename {ch.eitchnet.privilege => li.strolch.privilege}/LICENSE (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/README.md (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/config/PrivilegeConfig.xml (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/config/PrivilegeConfigMerge.xml (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/config/PrivilegeRoles.xml (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/config/PrivilegeRolesMerge.xml (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/config/PrivilegeUsers.xml (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/config/PrivilegeUsersMerge.xml (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/docs/PrivilegeAuthentication.dia (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/docs/PrivilegeHandlers.dia (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/docs/PrivilegeModelPrivilege.dia (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/docs/PrivilegeModelUser.dia (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/docs/TODO (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/pom.xml (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/privilege.jardesc (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/Certificate.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/Restrictable.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/RoleRep.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/UserRep.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/UserState.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/internal/Role.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/model/internal/User.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/resources/Privilege.xsd (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/resources/PrivilegeMessages.properties (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/main/resources/PrivilegeModel.xsd (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/XmlTest.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java (100%) rename {ch.eitchnet.privilege => li.strolch.privilege}/src/test/resources/log4j.xml (100%) rename {ch.eitchnet.utils => li.strolch.utils}/LICENSE (100%) rename {ch.eitchnet.utils => li.strolch.utils}/README.md (100%) rename {ch.eitchnet.utils => li.strolch.utils}/pom.xml (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/CommandKey.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/CommunicationConnection.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/ConnectionException.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/ConnectionInfo.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/ConnectionMessages.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/ConnectionMode.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/ConnectionObserver.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/ConnectionState.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/IoMessage.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/IoMessageArchive.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/chat/Chat.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/chat/ChatClient.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/chat/ChatServer.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/db/DbConnectionCheck.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/db/DbConstants.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/db/DbException.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/db/DbMigrationState.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/fileserver/FileClient.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/fileserver/FileDeletion.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/fileserver/FileHandler.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/fileserver/FilePart.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/StringMatchMode.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/Version.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/collections/DateRange.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/collections/Paging.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/collections/Tuple.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/dbc/DBC.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/DomUtil.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/FileHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/MathHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/StringHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/javanet/staxutils/Indentation.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/main/java/log4j.xml (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/communication/FileEndpointTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/communication/TestIoMessage.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/VersionTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/collections/PagingTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/SignedXmlFile.xml (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/SignedXmlFileWithNamespaces.xml (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/crypto_test_image.ico (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/crypto_test_long.txt (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/crypto_test_middle.txt (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/crypto_test_short.txt (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/log4j.xml (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/test.jks (100%) rename {ch.eitchnet.utils => li.strolch.utils}/src/test/resources/test_data.csv (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/LICENSE (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/README.md (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/pom.xml (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java (100%) rename {ch.eitchnet.xmlpers => li.strolch.xmlpers}/src/test/resources/log4j.xml (100%) diff --git a/ch.eitchnet.privilege/LICENSE b/li.strolch.privilege/LICENSE similarity index 100% rename from ch.eitchnet.privilege/LICENSE rename to li.strolch.privilege/LICENSE diff --git a/ch.eitchnet.privilege/README.md b/li.strolch.privilege/README.md similarity index 100% rename from ch.eitchnet.privilege/README.md rename to li.strolch.privilege/README.md diff --git a/ch.eitchnet.privilege/config/PrivilegeConfig.xml b/li.strolch.privilege/config/PrivilegeConfig.xml similarity index 100% rename from ch.eitchnet.privilege/config/PrivilegeConfig.xml rename to li.strolch.privilege/config/PrivilegeConfig.xml diff --git a/ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml b/li.strolch.privilege/config/PrivilegeConfigMerge.xml similarity index 100% rename from ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml rename to li.strolch.privilege/config/PrivilegeConfigMerge.xml diff --git a/ch.eitchnet.privilege/config/PrivilegeRoles.xml b/li.strolch.privilege/config/PrivilegeRoles.xml similarity index 100% rename from ch.eitchnet.privilege/config/PrivilegeRoles.xml rename to li.strolch.privilege/config/PrivilegeRoles.xml diff --git a/ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml b/li.strolch.privilege/config/PrivilegeRolesMerge.xml similarity index 100% rename from ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml rename to li.strolch.privilege/config/PrivilegeRolesMerge.xml diff --git a/ch.eitchnet.privilege/config/PrivilegeUsers.xml b/li.strolch.privilege/config/PrivilegeUsers.xml similarity index 100% rename from ch.eitchnet.privilege/config/PrivilegeUsers.xml rename to li.strolch.privilege/config/PrivilegeUsers.xml diff --git a/ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml b/li.strolch.privilege/config/PrivilegeUsersMerge.xml similarity index 100% rename from ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml rename to li.strolch.privilege/config/PrivilegeUsersMerge.xml diff --git a/ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia b/li.strolch.privilege/docs/PrivilegeAuthentication.dia similarity index 100% rename from ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia rename to li.strolch.privilege/docs/PrivilegeAuthentication.dia diff --git a/ch.eitchnet.privilege/docs/PrivilegeHandlers.dia b/li.strolch.privilege/docs/PrivilegeHandlers.dia similarity index 100% rename from ch.eitchnet.privilege/docs/PrivilegeHandlers.dia rename to li.strolch.privilege/docs/PrivilegeHandlers.dia diff --git a/ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia b/li.strolch.privilege/docs/PrivilegeModelPrivilege.dia similarity index 100% rename from ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia rename to li.strolch.privilege/docs/PrivilegeModelPrivilege.dia diff --git a/ch.eitchnet.privilege/docs/PrivilegeModelUser.dia b/li.strolch.privilege/docs/PrivilegeModelUser.dia similarity index 100% rename from ch.eitchnet.privilege/docs/PrivilegeModelUser.dia rename to li.strolch.privilege/docs/PrivilegeModelUser.dia diff --git a/ch.eitchnet.privilege/docs/TODO b/li.strolch.privilege/docs/TODO similarity index 100% rename from ch.eitchnet.privilege/docs/TODO rename to li.strolch.privilege/docs/TODO diff --git a/ch.eitchnet.privilege/pom.xml b/li.strolch.privilege/pom.xml similarity index 100% rename from ch.eitchnet.privilege/pom.xml rename to li.strolch.privilege/pom.xml diff --git a/ch.eitchnet.privilege/privilege.jardesc b/li.strolch.privilege/privilege.jardesc similarity index 100% rename from ch.eitchnet.privilege/privilege.jardesc rename to li.strolch.privilege/privilege.jardesc diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java b/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java similarity index 100% rename from ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java rename to li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java diff --git a/ch.eitchnet.privilege/src/main/resources/Privilege.xsd b/li.strolch.privilege/src/main/resources/Privilege.xsd similarity index 100% rename from ch.eitchnet.privilege/src/main/resources/Privilege.xsd rename to li.strolch.privilege/src/main/resources/Privilege.xsd diff --git a/ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties b/li.strolch.privilege/src/main/resources/PrivilegeMessages.properties similarity index 100% rename from ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties rename to li.strolch.privilege/src/main/resources/PrivilegeMessages.properties diff --git a/ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd b/li.strolch.privilege/src/main/resources/PrivilegeModel.xsd similarity index 100% rename from ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd rename to li.strolch.privilege/src/main/resources/PrivilegeModel.xsd diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java similarity index 100% rename from ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java rename to li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java diff --git a/ch.eitchnet.privilege/src/test/resources/log4j.xml b/li.strolch.privilege/src/test/resources/log4j.xml similarity index 100% rename from ch.eitchnet.privilege/src/test/resources/log4j.xml rename to li.strolch.privilege/src/test/resources/log4j.xml diff --git a/ch.eitchnet.utils/LICENSE b/li.strolch.utils/LICENSE similarity index 100% rename from ch.eitchnet.utils/LICENSE rename to li.strolch.utils/LICENSE diff --git a/ch.eitchnet.utils/README.md b/li.strolch.utils/README.md similarity index 100% rename from ch.eitchnet.utils/README.md rename to li.strolch.utils/README.md diff --git a/ch.eitchnet.utils/pom.xml b/li.strolch.utils/pom.xml similarity index 100% rename from ch.eitchnet.utils/pom.xml rename to li.strolch.utils/pom.xml diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommandKey.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommandKey.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommandKey.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/CommandKey.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessage.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessage.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessage.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessage.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java b/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java rename to li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java b/li.strolch.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java rename to li.strolch.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConstants.java b/li.strolch.utils/src/main/java/ch/eitchnet/db/DbConstants.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbConstants.java rename to li.strolch.utils/src/main/java/ch/eitchnet/db/DbConstants.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java b/li.strolch.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java rename to li.strolch.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbException.java b/li.strolch.utils/src/main/java/ch/eitchnet/db/DbException.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbException.java rename to li.strolch.utils/src/main/java/ch/eitchnet/db/DbException.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java b/li.strolch.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java rename to li.strolch.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/li.strolch.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java rename to li.strolch.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java b/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java rename to li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java b/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java rename to li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java b/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java rename to li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java b/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java rename to li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java b/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java rename to li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/Version.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/Version.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/Version.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/Version.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java diff --git a/ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java b/li.strolch.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java rename to li.strolch.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java diff --git a/ch.eitchnet.utils/src/main/java/javanet/staxutils/Indentation.java b/li.strolch.utils/src/main/java/javanet/staxutils/Indentation.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/javanet/staxutils/Indentation.java rename to li.strolch.utils/src/main/java/javanet/staxutils/Indentation.java diff --git a/ch.eitchnet.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java b/li.strolch.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java rename to li.strolch.utils/src/main/java/javanet/staxutils/IndentingXMLStreamWriter.java diff --git a/ch.eitchnet.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java b/li.strolch.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java similarity index 100% rename from ch.eitchnet.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java rename to li.strolch.utils/src/main/java/javanet/staxutils/helpers/StreamWriterDelegate.java diff --git a/ch.eitchnet.utils/src/main/java/log4j.xml b/li.strolch.utils/src/main/java/log4j.xml similarity index 100% rename from ch.eitchnet.utils/src/main/java/log4j.xml rename to li.strolch.utils/src/main/java/log4j.xml diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java b/li.strolch.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java rename to li.strolch.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java b/li.strolch.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java rename to li.strolch.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/VersionTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/VersionTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/VersionTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/VersionTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java diff --git a/ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/li.strolch.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java similarity index 100% rename from ch.eitchnet.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java rename to li.strolch.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java diff --git a/ch.eitchnet.utils/src/test/resources/SignedXmlFile.xml b/li.strolch.utils/src/test/resources/SignedXmlFile.xml similarity index 100% rename from ch.eitchnet.utils/src/test/resources/SignedXmlFile.xml rename to li.strolch.utils/src/test/resources/SignedXmlFile.xml diff --git a/ch.eitchnet.utils/src/test/resources/SignedXmlFileWithNamespaces.xml b/li.strolch.utils/src/test/resources/SignedXmlFileWithNamespaces.xml similarity index 100% rename from ch.eitchnet.utils/src/test/resources/SignedXmlFileWithNamespaces.xml rename to li.strolch.utils/src/test/resources/SignedXmlFileWithNamespaces.xml diff --git a/ch.eitchnet.utils/src/test/resources/crypto_test_image.ico b/li.strolch.utils/src/test/resources/crypto_test_image.ico similarity index 100% rename from ch.eitchnet.utils/src/test/resources/crypto_test_image.ico rename to li.strolch.utils/src/test/resources/crypto_test_image.ico diff --git a/ch.eitchnet.utils/src/test/resources/crypto_test_long.txt b/li.strolch.utils/src/test/resources/crypto_test_long.txt similarity index 100% rename from ch.eitchnet.utils/src/test/resources/crypto_test_long.txt rename to li.strolch.utils/src/test/resources/crypto_test_long.txt diff --git a/ch.eitchnet.utils/src/test/resources/crypto_test_middle.txt b/li.strolch.utils/src/test/resources/crypto_test_middle.txt similarity index 100% rename from ch.eitchnet.utils/src/test/resources/crypto_test_middle.txt rename to li.strolch.utils/src/test/resources/crypto_test_middle.txt diff --git a/ch.eitchnet.utils/src/test/resources/crypto_test_short.txt b/li.strolch.utils/src/test/resources/crypto_test_short.txt similarity index 100% rename from ch.eitchnet.utils/src/test/resources/crypto_test_short.txt rename to li.strolch.utils/src/test/resources/crypto_test_short.txt diff --git a/ch.eitchnet.utils/src/test/resources/log4j.xml b/li.strolch.utils/src/test/resources/log4j.xml similarity index 100% rename from ch.eitchnet.utils/src/test/resources/log4j.xml rename to li.strolch.utils/src/test/resources/log4j.xml diff --git a/ch.eitchnet.utils/src/test/resources/test.jks b/li.strolch.utils/src/test/resources/test.jks similarity index 100% rename from ch.eitchnet.utils/src/test/resources/test.jks rename to li.strolch.utils/src/test/resources/test.jks diff --git a/ch.eitchnet.utils/src/test/resources/test_data.csv b/li.strolch.utils/src/test/resources/test_data.csv similarity index 100% rename from ch.eitchnet.utils/src/test/resources/test_data.csv rename to li.strolch.utils/src/test/resources/test_data.csv diff --git a/ch.eitchnet.xmlpers/LICENSE b/li.strolch.xmlpers/LICENSE similarity index 100% rename from ch.eitchnet.xmlpers/LICENSE rename to li.strolch.xmlpers/LICENSE diff --git a/ch.eitchnet.xmlpers/README.md b/li.strolch.xmlpers/README.md similarity index 100% rename from ch.eitchnet.xmlpers/README.md rename to li.strolch.xmlpers/README.md diff --git a/ch.eitchnet.xmlpers/pom.xml b/li.strolch.xmlpers/pom.xml similarity index 100% rename from ch.eitchnet.xmlpers/pom.xml rename to li.strolch.xmlpers/pom.xml diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java b/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java similarity index 100% rename from ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java rename to li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java b/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java similarity index 100% rename from ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java rename to li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java diff --git a/ch.eitchnet.xmlpers/src/test/resources/log4j.xml b/li.strolch.xmlpers/src/test/resources/log4j.xml similarity index 100% rename from ch.eitchnet.xmlpers/src/test/resources/log4j.xml rename to li.strolch.xmlpers/src/test/resources/log4j.xml diff --git a/pom.xml b/pom.xml index 5bf824b69..12ce58934 100644 --- a/pom.xml +++ b/pom.xml @@ -109,10 +109,9 @@ - ch.eitchnet.parent - ch.eitchnet.privilege - ch.eitchnet.utils - ch.eitchnet.xmlpers + li.strolch.privilege + li.strolch.utils + li.strolch.xmlpers li.strolch.parent li.strolch.dev li.strolch.bom @@ -127,5 +126,7 @@ li.strolch.tutorialapp li.strolch.tutorialwebapp li.strolch.planningwebapp + strolch_minimal + strolch_minimal_rest From 5d38fc4526c1755788466febfa822e2aa71e15c0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 17:46:30 +0200 Subject: [PATCH 443/457] [Project] Removed parent --- li.strolch.parent/.gitignore | 3 - li.strolch.parent/LICENSE | 202 -------------- li.strolch.parent/README.md | 6 - li.strolch.parent/pom.xml | 428 ------------------------------ pom.xml | 496 ++++++++++++++++++++++++++++------- 5 files changed, 407 insertions(+), 728 deletions(-) delete mode 100644 li.strolch.parent/.gitignore delete mode 100644 li.strolch.parent/LICENSE delete mode 100644 li.strolch.parent/README.md delete mode 100644 li.strolch.parent/pom.xml diff --git a/li.strolch.parent/.gitignore b/li.strolch.parent/.gitignore deleted file mode 100644 index 6efaec8b2..000000000 --- a/li.strolch.parent/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -target/ -.project -.settings diff --git a/li.strolch.parent/LICENSE b/li.strolch.parent/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/li.strolch.parent/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/li.strolch.parent/README.md b/li.strolch.parent/README.md deleted file mode 100644 index f5ff4305e..000000000 --- a/li.strolch.parent/README.md +++ /dev/null @@ -1,6 +0,0 @@ -li.strolch.parent -================= - -[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=li.strolch.parent)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.parent/) - -Parent for all li.strolch projects for easier project maintenance diff --git a/li.strolch.parent/pom.xml b/li.strolch.parent/pom.xml deleted file mode 100644 index 3d70b08fa..000000000 --- a/li.strolch.parent/pom.xml +++ /dev/null @@ -1,428 +0,0 @@ - - - 4.0.0 - - - li.strolch - li.strolch - 1.1.0-SNAPSHOT - ../pom.xml - - - li.strolch.parent - pom - li.strolch.parent - Parent for all li.strolch projects for easier project maintenance - 2013 - - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - - - - Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0 - - - - - eitchnet.ch - http://www.strolch.li - - - - - eitch - Robert von Burg - eitch@eitchnet.ch - http://www.eitchnet.ch - eitchnet.ch - http://www.eitchnet.ch - - architect - developer - - +1 - - - - - - - li.strolch - li.strolch.model - ${project.version} - - - li.strolch - li.strolch.agent - ${project.version} - - - li.strolch - li.strolch.service - ${project.version} - - - li.strolch - li.strolch.persistence.xml - ${project.version} - - - li.strolch - li.strolch.persistence.postgresql - ${project.version} - - - li.strolch - li.strolch.rest - ${project.version} - - - - - li.strolch - li.strolch.testbase - ${project.version} - test - - - - - ch.eitchnet - ch.eitchnet.utils - ${eitchnet.utils.version} - - - ch.eitchnet - ch.eitchnet.xmlpers - ${eitchnet.xmlpers.version} - - - ch.eitchnet - ch.eitchnet.privilege - ${eitchnet.privilege.version} - - - - - org.slf4j - slf4j-api - 1.7.2 - - - commons-cli - commons-cli - 1.2 - - - com.github.petitparser.java-petitparser - petitparser-core - 2.0.2 - - - - - com.google.code.gson - gson - 2.3.1 - - - - - javax.mail - mail - 1.5.0-b01 - - - - - javax.servlet - javax.servlet-api - 3.0.1 - provided - - - - - junit - junit - 4.11 - test - - - org.hamcrest - hamcrest-core - 1.3 - test - - - org.hamcrest - hamcrest-library - 1.3 - test - - - org.slf4j - slf4j-log4j12 - 1.7.2 - test - - - - - - - - - ch.eitchnet - ch.eitchnet.utils - - - org.slf4j - slf4j-api - - - - - junit - junit - test - - - org.hamcrest - hamcrest-core - test - - - org.hamcrest - hamcrest-library - test - - - org.slf4j - slf4j-log4j12 - test - - - - - - - src/main/resources - true - - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.2 - - - org.apache.maven.plugins - maven-scm-plugin - 1.9.4 - - ${project.artifactId}-${project.version} - - - - - org.codehaus.mojo - buildnumber-maven-plugin - 1.3 - - - validate - - create - - - - - false - false - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.3 - - 1.8 - 1.8 - true - true - -Xlint:all - - - - - org.apache.maven.plugins - maven-site-plugin - 3.4 - - UTF-8 - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.10 - - true - true - - - - - org.apache.maven.plugins - maven-source-plugin - 2.4 - - - attach-sources - package - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - - - true - true - - - - - - - - - - - - org.apache.maven.plugins - maven-war-plugin - 2.6 - - false - ${warFinalName} - - - - - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 - - ${tomcat7Url} - ${tomcat7ServerId} - /${warFinalName} - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.3 - - - attach-javadocs - deploy - - jar - - - - - -Xdoclint:none - - - - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - - deploy - deploy - - deploy - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.10 - - - copy-dependencies - package - - copy-dependencies - - - ${project.build.directory}/lib - false - false - true - false - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.5.5 - - - - true - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pom.xml b/pom.xml index 12ce58934..f5fb08300 100644 --- a/pom.xml +++ b/pom.xml @@ -1,16 +1,52 @@ - + 4.0.0 li.strolch li.strolch 1.1.0-SNAPSHOT li.strolch + Module build for strolch pom - + http://www.strolch.li 2014 + + scm:git:https://github.com/eitchnet/strolch.git + scm:git:git@github.com:eitch/strolch.git + https://github.com/eitchnet/strolch + + + + + Apache License 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + eitchnet.ch + http://www.strolch.li + + + + + eitch + Robert von Burg + eitch@eitchnet.ch + http://www.eitchnet.ch + eitchnet.ch + http://www.eitchnet.ch + + architect + developer + + +1 + + + 1.1.0-SNAPSHOT 1.1.0-SNAPSHOT @@ -21,93 +57,6 @@ ${maven.build.timestamp} - - - - - - jitpack.io - snapshots - https://jitpack.io - - true - - - true - - - - - - - - - - archive.eitchnet.ch - Internal Releases - https://archive.eitchnet.ch/repository/internal/ - - - archive.eitchnet.ch - Internal Snapshots - https://archive.eitchnet.ch/repository/snapshots/ - - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - li.strolch.privilege li.strolch.utils @@ -129,4 +78,373 @@ strolch_minimal strolch_minimal_rest + + + + + li.strolch + li.strolch.model + ${project.version} + + + li.strolch + li.strolch.agent + ${project.version} + + + li.strolch + li.strolch.service + ${project.version} + + + li.strolch + li.strolch.persistence.xml + ${project.version} + + + li.strolch + li.strolch.persistence.postgresql + ${project.version} + + + li.strolch + li.strolch.rest + ${project.version} + + + + + li.strolch + li.strolch.testbase + ${project.version} + test + + + + + ch.eitchnet + ch.eitchnet.utils + ${eitchnet.utils.version} + + + ch.eitchnet + ch.eitchnet.xmlpers + ${eitchnet.xmlpers.version} + + + ch.eitchnet + ch.eitchnet.privilege + ${eitchnet.privilege.version} + + + + + org.slf4j + slf4j-api + 1.7.2 + + + commons-cli + commons-cli + 1.2 + + + com.github.petitparser.java-petitparser + petitparser-core + 2.0.2 + + + + + com.google.code.gson + gson + 2.3.1 + + + + + javax.mail + mail + 1.5.0-b01 + + + + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + + + junit + junit + 4.11 + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + org.hamcrest + hamcrest-library + 1.3 + test + + + org.slf4j + slf4j-log4j12 + 1.7.2 + test + + + + + + + + src/main/resources + true + + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.2 + + + org.apache.maven.plugins + maven-scm-plugin + 1.9.4 + + ${project.artifactId}-${project.version} + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.3 + + + validate + + create + + + + + false + false + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + true + true + -Xlint:all + + + + + org.apache.maven.plugins + maven-site-plugin + 3.4 + + UTF-8 + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.10 + + true + true + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + true + true + + + + + + + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + false + ${warFinalName} + + + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + ${tomcat7Url} + ${tomcat7ServerId} + /${warFinalName} + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + + attach-javadocs + deploy + + jar + + + + + -Xdoclint:none + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + deploy + deploy + + deploy + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + false + false + true + false + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.5.5 + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + jitpack.io + snapshots + https://jitpack.io + + true + + + true + + + + + + + + archive.eitchnet.ch + Internal Releases + https://archive.eitchnet.ch/repository/internal/ + + + archive.eitchnet.ch + Internal Snapshots + https://archive.eitchnet.ch/repository/snapshots/ + + + From 14197ed644528ebef473d92237aa3a129405dc55 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 18:00:44 +0200 Subject: [PATCH 444/457] [Major] Cleaning up parents --- li.strolch.agent/pom.xml | 24 +-- li.strolch.bom/pom.xml | 19 +-- li.strolch.dev/pom.xml | 15 +- li.strolch.model/pom.xml | 22 +-- li.strolch.performancetest/pom.xml | 22 +-- li.strolch.persistence.postgresql/pom.xml | 27 +--- li.strolch.persistence.xml/pom.xml | 27 +--- li.strolch.planningwebapp/pom.xml | 52 ++---- li.strolch.privilege/pom.xml | 30 +--- li.strolch.rest/pom.xml | 56 ++----- li.strolch.service/pom.xml | 183 ++++++++++------------ li.strolch.testbase/pom.xml | 24 +-- li.strolch.tutorialapp/pom.xml | 29 +--- li.strolch.tutorialwebapp/pom.xml | 51 ++---- li.strolch.utils/pom.xml | 32 +--- li.strolch.website/pom.xml | 11 +- li.strolch.xmlpers/pom.xml | 33 +--- pom.xml | 34 +++- strolch_minimal/pom.xml | 8 +- strolch_minimal_rest/pom.xml | 12 +- 20 files changed, 231 insertions(+), 480 deletions(-) diff --git a/li.strolch.agent/pom.xml b/li.strolch.agent/pom.xml index 576c1e92b..3fe2f932b 100644 --- a/li.strolch.agent/pom.xml +++ b/li.strolch.agent/pom.xml @@ -1,34 +1,20 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml li.strolch.agent - li.strolch.agent Strolch Agent which is the runtime for Strolch - - https://github.com/eitchnet/li.strolch.agent - 2011 - - Github Issues - https://github.com/eitchnet/li.strolch.agent/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - diff --git a/li.strolch.bom/pom.xml b/li.strolch.bom/pom.xml index 183a07a2b..d3686ec1b 100644 --- a/li.strolch.bom/pom.xml +++ b/li.strolch.bom/pom.xml @@ -1,24 +1,19 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml li.strolch.bom pom li.strolch.bom The default set of dependencies to start working with strolch projects - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - @@ -52,7 +47,7 @@ li.strolch.rest ${project.version} - + li.strolch @@ -60,7 +55,7 @@ ${project.version} test - + diff --git a/li.strolch.dev/pom.xml b/li.strolch.dev/pom.xml index f1b9b0027..8a21f5d51 100644 --- a/li.strolch.dev/pom.xml +++ b/li.strolch.dev/pom.xml @@ -1,12 +1,13 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml li.strolch.dev @@ -14,10 +15,4 @@ li.strolch.dev Project used to develop strolch modules. Contains scripts for bootstrapping development, etc. - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - diff --git a/li.strolch.model/pom.xml b/li.strolch.model/pom.xml index e842bd7a0..a387e0441 100644 --- a/li.strolch.model/pom.xml +++ b/li.strolch.model/pom.xml @@ -1,32 +1,20 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml li.strolch.model jar li.strolch.model - https://github.com/eitchnet/li.strolch.model - 2012 - - Github Issues - https://github.com/eitchnet/li.strolch.model/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - diff --git a/li.strolch.performancetest/pom.xml b/li.strolch.performancetest/pom.xml index 1161604ec..27249a2fa 100644 --- a/li.strolch.performancetest/pom.xml +++ b/li.strolch.performancetest/pom.xml @@ -1,32 +1,20 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml li.strolch.performancetest - li.strolch.performancetest Strolch project for running performance tests - 2015 - - - - li.strolch - li.strolch.bom - pom - ${project.version} - - - - li.strolch diff --git a/li.strolch.persistence.postgresql/pom.xml b/li.strolch.persistence.postgresql/pom.xml index d00eed9b9..2707aeda0 100644 --- a/li.strolch.persistence.postgresql/pom.xml +++ b/li.strolch.persistence.postgresql/pom.xml @@ -1,34 +1,20 @@ - + li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml 4.0.0 li.strolch.persistence.postgresql - li.strolch.persistence.postgresql PostgreSQL Persistence Implementation for Strolch - - https://github.com/eitchnet/li.strolch.persistence.postgresql - 2011 - - Github Issues - https://github.com/eitchnet/li.strolch.persistence.postgresql/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - @@ -43,13 +29,10 @@ com.zaxxer HikariCP - 2.3.6 - compile org.postgresql postgresql - 9.4.1208.jre7 diff --git a/li.strolch.persistence.xml/pom.xml b/li.strolch.persistence.xml/pom.xml index 9f2b3e728..c9cc58d90 100644 --- a/li.strolch.persistence.xml/pom.xml +++ b/li.strolch.persistence.xml/pom.xml @@ -1,35 +1,20 @@ - + + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml - 4.0.0 - li.strolch.persistence.xml - li.strolch.persistence.xml Reference Persistence Implementation for Strolch - - https://github.com/eitchnet/li.strolch.persistence.xml - 2011 - - Github Issues - https://github.com/eitchnet/li.strolch.persistence.xml/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - diff --git a/li.strolch.planningwebapp/pom.xml b/li.strolch.planningwebapp/pom.xml index 721a3b53f..7ce396520 100644 --- a/li.strolch.planningwebapp/pom.xml +++ b/li.strolch.planningwebapp/pom.xml @@ -1,60 +1,30 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml + li.strolch.planningwebapp + li.strolch.planningwebapp + Tutorial webapp to show case using Strolch in a servlet container + war + 2011 + - UTF-8 planningwebapp tomcat7.eitchnet.ch http://tomcat.eitchnet.ch:8080/manager/text ${warFinalName} - li.strolch.planningwebapp - li.strolch.planningwebapp - Tutorial webapp to show case using Strolch in a servlet container - war - - https://github.com/eitchnet/li.strolch - - 2011 - - - Github Issues - https://github.com/eitchnet/li.strolch/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - - - - - li.strolch - li.strolch.bom - pom - ${project.version} - - - - - - li.strolch - li.strolch.bom - pom - li.strolch li.strolch.rest diff --git a/li.strolch.privilege/pom.xml b/li.strolch.privilege/pom.xml index 51d2ba92f..0bdb4e379 100644 --- a/li.strolch.privilege/pom.xml +++ b/li.strolch.privilege/pom.xml @@ -1,41 +1,25 @@ - + 4.0.0 - ch.eitchnet - ch.eitchnet.parent - 1.1.0-SNAPSHOT - ../ch.eitchnet.parent/pom.xml + li.strolch + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml + ch.eitchnet ch.eitchnet.privilege jar ch.eitchnet.privilege - https://github.com/eitchnet/ch.eitchnet.privilege 2011 - - 1.1.0-SNAPSHOT - - - - Github Issues - https://github.com/eitchnet/ch.eitchnet.privilege/issues - - - - scm:git:https://github.com/eitchnet/ch.eitchnet.privilege.git - scm:git:git@github.com:eitchnet/ch.eitchnet.privilege.git - https://github.com/eitchnet/ch.eitchnet.privilege - HEAD - - ch.eitchnet ch.eitchnet.utils - ${eitchnet.utils.version} diff --git a/li.strolch.rest/pom.xml b/li.strolch.rest/pom.xml index 9f8c26293..7a424d044 100644 --- a/li.strolch.rest/pom.xml +++ b/li.strolch.rest/pom.xml @@ -1,50 +1,20 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml li.strolch.rest - li.strolch.rest Restful Web Service API for Strolch - - https://github.com/eitchnet/li.strolch.rest - 2011 - - Github Issues - https://github.com/eitchnet/li.strolch.rest/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - - - 2.11 - - - - - - org.glassfish.jersey - jersey-bom - ${jersey.version} - pom - import - - - - @@ -60,18 +30,20 @@ li.strolch.service - - - javax.servlet - javax.servlet-api - - javax.mail mail - + + + org.glassfish.jersey + jersey-bom + + + javax.servlet + javax.servlet-api + org.glassfish.jersey.containers jersey-container-servlet diff --git a/li.strolch.service/pom.xml b/li.strolch.service/pom.xml index 21436d05f..64a96fa17 100644 --- a/li.strolch.service/pom.xml +++ b/li.strolch.service/pom.xml @@ -1,115 +1,100 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml - + + li.strolch + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml + - li.strolch.service + li.strolch.service + li.strolch.service + Service API for Strolch + 2011 - li.strolch.service - Service API for Strolch + + + + li.strolch + li.strolch.model + + + li.strolch + li.strolch.agent + - https://github.com/eitchnet/li.strolch.service + + ch.eitchnet + ch.eitchnet.xmlpers + + + ch.eitchnet + ch.eitchnet.privilege + - 2011 + + + org.mockito + mockito-core + 2.0.8-beta + - - Github Issues - https://github.com/eitchnet/li.strolch.service/issues - + + + li.strolch + li.strolch.testbase + + + li.strolch + li.strolch.persistence.postgresql + test + - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - + - - - - li.strolch - li.strolch.model - - - li.strolch - li.strolch.agent - + + + + src/main/resources + true + + **/componentVersion.properties + + + - - ch.eitchnet - ch.eitchnet.xmlpers - - - ch.eitchnet - ch.eitchnet.privilege - + + + org.codehaus.mojo + buildnumber-maven-plugin + + + org.apache.maven.plugins + maven-eclipse-plugin + - - - org.mockito - mockito-core - 2.0.8-beta - + + org.apache.maven.plugins + maven-compiler-plugin + - - - li.strolch - li.strolch.testbase - - - li.strolch - li.strolch.persistence.postgresql - test - + + org.apache.maven.plugins + maven-source-plugin + - + + org.apache.maven.plugins + maven-jar-plugin + - - - - src/main/resources - true - - **/componentVersion.properties - - - - - - - org.codehaus.mojo - buildnumber-maven-plugin - - - org.apache.maven.plugins - maven-eclipse-plugin - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.apache.maven.plugins - maven-source-plugin - - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.apache.maven.plugins - maven-site-plugin - - - + + org.apache.maven.plugins + maven-site-plugin + + + diff --git a/li.strolch.testbase/pom.xml b/li.strolch.testbase/pom.xml index 4c0883101..fa537095e 100644 --- a/li.strolch.testbase/pom.xml +++ b/li.strolch.testbase/pom.xml @@ -1,34 +1,20 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml li.strolch.testbase - li.strolch.testbase runtime for Strolch - - https://github.com/eitchnet/li.strolch.testbase - 2011 - - Github Issues - https://github.com/eitchnet/li.strolch.testbase/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - diff --git a/li.strolch.tutorialapp/pom.xml b/li.strolch.tutorialapp/pom.xml index 71ec41c6a..7094ddff3 100644 --- a/li.strolch.tutorialapp/pom.xml +++ b/li.strolch.tutorialapp/pom.xml @@ -1,34 +1,20 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml li.strolch.tutorialapp - li.strolch.tutorialapp Tutorial Application for Strolch - - https://github.com/eitchnet/li.strolch.tutorialapp - 2011 - - Github Issues - https://github.com/eitchnet/li.strolch.tutorialapp/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - @@ -41,11 +27,6 @@ - - li.strolch - li.strolch.bom - pom - li.strolch li.strolch.persistence.xml diff --git a/li.strolch.tutorialwebapp/pom.xml b/li.strolch.tutorialwebapp/pom.xml index f33cf9ac5..b13eb1ee6 100644 --- a/li.strolch.tutorialwebapp/pom.xml +++ b/li.strolch.tutorialwebapp/pom.xml @@ -1,14 +1,21 @@ - + 4.0.0 li.strolch - li.strolch.parent - 1.1.0-SNAPSHOT - ../li.strolch.parent/pom.xml + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml + li.strolch.tutorialwebapp + li.strolch.tutorialwebapp + Tutorial webapp to show case using Strolch in a servlet container + war + 2011 + UTF-8 tutorialwebapp @@ -17,44 +24,8 @@ ${warFinalName} - li.strolch.tutorialwebapp - li.strolch.tutorialwebapp - Tutorial webapp to show case using Strolch in a servlet container - war - - https://github.com/eitchnet/li.strolch.tutorialwebapp - - 2011 - - - Github Issues - https://github.com/eitchnet/li.strolch.tutorialwebapp/issues - - - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - - - - - li.strolch - li.strolch.bom - pom - ${project.version} - - - - - - li.strolch - li.strolch.bom - pom - li.strolch li.strolch.rest diff --git a/li.strolch.utils/pom.xml b/li.strolch.utils/pom.xml index 9839d0b9a..d7468502b 100644 --- a/li.strolch.utils/pom.xml +++ b/li.strolch.utils/pom.xml @@ -1,41 +1,21 @@ - + 4.0.0 - ch.eitchnet - ch.eitchnet.parent - 1.1.0-SNAPSHOT - ../ch.eitchnet.parent/pom.xml + li.strolch + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml ch.eitchnet.utils jar ch.eitchnet.utils These utils contain project independent helper classes and utilities for reuse - https://github.com/eitchnet/ch.eitchnet.utils 2011 - - Github Issues - https://github.com/eitchnet/ch.eitchnet.utils/issues - - - - scm:git:https://github.com/eitchnet/ch.eitchnet.utils.git - scm:git:git@github.com:eitchnet/ch.eitchnet.utils.git - https://github.com/eitchnet/ch.eitchnet.utils - HEAD - - - - - org.slf4j - slf4j-log4j12 - runtime - - - diff --git a/li.strolch.website/pom.xml b/li.strolch.website/pom.xml index 7a975ffdd..6a4d4e23d 100644 --- a/li.strolch.website/pom.xml +++ b/li.strolch.website/pom.xml @@ -1,11 +1,12 @@ - + 4.0.0 li.strolch li.strolch - 1.1.0-SNAPSHOT + 1.2.0-SNAPSHOT ../pom.xml @@ -14,10 +15,4 @@ li.strolch.website Strolch's Website - - scm:git:https://github.com/eitchnet/strolch.git - scm:git:git@github.com:eitch/strolch.git - https://github.com/eitchnet/strolch - - diff --git a/li.strolch.xmlpers/pom.xml b/li.strolch.xmlpers/pom.xml index 4374bde4f..652dfee9f 100644 --- a/li.strolch.xmlpers/pom.xml +++ b/li.strolch.xmlpers/pom.xml @@ -1,45 +1,24 @@ - + 4.0.0 - ch.eitchnet - ch.eitchnet.parent - 1.1.0-SNAPSHOT - ../ch.eitchnet.parent/pom.xml + li.strolch + li.strolch + 1.2.0-SNAPSHOT + ../pom.xml ch.eitchnet.xmlpers jar ch.eitchnet.xmlpers - https://github.com/eitchnet/ch.eitchnet.xmlpers - - - UTF-8 - 1.1.0-SNAPSHOT - - - - 2011 - - Github Issues - https://github.com/eitchnet/ch.eitchnet.xmlpers/issues - - - - scm:git:https://github.com/eitchnet/ch.eitchnet.xmlpers.git - scm:git:git@github.com:eitchnet/ch.eitchnet.xmlpers.git - https://github.com/eitchnet/ch.eitchnet.xmlpers - HEAD - - ch.eitchnet ch.eitchnet.utils - ${eitchnet.utils.version} diff --git a/pom.xml b/pom.xml index f5fb08300..cd149340e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,14 +5,19 @@ li.strolch li.strolch - 1.1.0-SNAPSHOT - li.strolch + 1.2.0-SNAPSHOT + li.strolch Module build for strolch pom http://www.strolch.li 2014 + + Github Issues + https://github.com/eitchnet/strolch/issues + + scm:git:https://github.com/eitchnet/strolch.git scm:git:git@github.com:eitch/strolch.git @@ -55,6 +60,9 @@ UTF-8 yyyy-MM-dd HH:mm:ss ${maven.build.timestamp} + + 2.11 + @@ -137,7 +145,7 @@ ${eitchnet.privilege.version} - + org.slf4j slf4j-api @@ -154,6 +162,19 @@ 2.0.2 + + + com.zaxxer + HikariCP + 2.3.6 + compile + + + org.postgresql + postgresql + 9.4.1208.jre7 + + com.google.code.gson @@ -175,6 +196,13 @@ 3.0.1 provided + + org.glassfish.jersey + jersey-bom + ${jersey.version} + pom + import + diff --git a/strolch_minimal/pom.xml b/strolch_minimal/pom.xml index 2b925f3ee..5700b269e 100644 --- a/strolch_minimal/pom.xml +++ b/strolch_minimal/pom.xml @@ -1,13 +1,13 @@ - + 4.0.0 li.strolch strolch_minimal - 1.0.0-SNAPSHOT + 1.2.0-SNAPSHOT strolch_minimal Strolch app with minimal runtime - 2014 @@ -15,7 +15,7 @@ yyyy-MM-dd HH:mm:ss ${maven.build.timestamp} 1.8 - 1.1.0-SNAPSHOT + ${project.version} strolch_minimal diff --git a/strolch_minimal_rest/pom.xml b/strolch_minimal_rest/pom.xml index c5482f403..f01762043 100644 --- a/strolch_minimal_rest/pom.xml +++ b/strolch_minimal_rest/pom.xml @@ -1,13 +1,12 @@ - + 4.0.0 li.strolch strolch_minimal_rest war - 1.0.0-SNAPSHOT - + 1.2.0-SNAPSHOT 2014 - strolch_minimal_rest http://www.strolch.li @@ -21,7 +20,7 @@ 1.8 - 1.1.0-SNAPSHOT + ${project.version} 2.11 2.0 @@ -113,7 +112,8 @@ jersey-container-grizzly2-http test - + com.google.code.gson gson From 3774c987a68a4ef5aff2bf5af54a1c53a2b73470 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 18:02:08 +0200 Subject: [PATCH 445/457] [Major] Moving eitchnet projects into Strolch --- li.strolch.privilege/pom.xml | 10 +++++----- li.strolch.utils/pom.xml | 4 ++-- li.strolch.xmlpers/pom.xml | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/li.strolch.privilege/pom.xml b/li.strolch.privilege/pom.xml index 0bdb4e379..a67dd9320 100644 --- a/li.strolch.privilege/pom.xml +++ b/li.strolch.privilege/pom.xml @@ -10,16 +10,16 @@ ../pom.xml - ch.eitchnet - ch.eitchnet.privilege + li.strolch + li.strolch.privilege jar - ch.eitchnet.privilege + li.strolch.privilege 2011 - ch.eitchnet - ch.eitchnet.utils + li.strolch + li.strolch.utils diff --git a/li.strolch.utils/pom.xml b/li.strolch.utils/pom.xml index d7468502b..b5dd83456 100644 --- a/li.strolch.utils/pom.xml +++ b/li.strolch.utils/pom.xml @@ -10,9 +10,9 @@ ../pom.xml - ch.eitchnet.utils + li.strolch.utils jar - ch.eitchnet.utils + li.strolch.utils These utils contain project independent helper classes and utilities for reuse 2011 diff --git a/li.strolch.xmlpers/pom.xml b/li.strolch.xmlpers/pom.xml index 652dfee9f..8f6e1aae2 100644 --- a/li.strolch.xmlpers/pom.xml +++ b/li.strolch.xmlpers/pom.xml @@ -10,15 +10,15 @@ ../pom.xml - ch.eitchnet.xmlpers + li.strolch.xmlpers jar - ch.eitchnet.xmlpers + li.strolch.xmlpers 2011 - ch.eitchnet - ch.eitchnet.utils + li.strolch + li.strolch.utils From 3b7e5200fabd580d95f3520e5e120fd8ae6e3bd4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 18:02:47 +0200 Subject: [PATCH 446/457] [Project] Removed parent --- li.strolch.agent/pom.xml | 4 ++-- li.strolch.model/pom.xml | 8 +++---- li.strolch.persistence.xml/pom.xml | 4 ++-- li.strolch.service/pom.xml | 8 +++---- pom.xml | 37 ++++++++++++------------------ strolch_minimal_rest/pom.xml | 8 +++---- 6 files changed, 31 insertions(+), 38 deletions(-) diff --git a/li.strolch.agent/pom.xml b/li.strolch.agent/pom.xml index 3fe2f932b..0d19a5381 100644 --- a/li.strolch.agent/pom.xml +++ b/li.strolch.agent/pom.xml @@ -22,8 +22,8 @@ li.strolch.model - ch.eitchnet - ch.eitchnet.privilege + li.strolch + li.strolch.privilege commons-cli diff --git a/li.strolch.model/pom.xml b/li.strolch.model/pom.xml index a387e0441..b457bfa66 100644 --- a/li.strolch.model/pom.xml +++ b/li.strolch.model/pom.xml @@ -23,12 +23,12 @@ - ch.eitchnet - ch.eitchnet.utils + li.strolch + li.strolch.utils - ch.eitchnet - ch.eitchnet.privilege + li.strolch + li.strolch.privilege diff --git a/li.strolch.persistence.xml/pom.xml b/li.strolch.persistence.xml/pom.xml index c9cc58d90..0eed44edb 100644 --- a/li.strolch.persistence.xml/pom.xml +++ b/li.strolch.persistence.xml/pom.xml @@ -26,8 +26,8 @@ li.strolch.agent - ch.eitchnet - ch.eitchnet.xmlpers + li.strolch + li.strolch.xmlpers diff --git a/li.strolch.service/pom.xml b/li.strolch.service/pom.xml index 64a96fa17..8d4caa408 100644 --- a/li.strolch.service/pom.xml +++ b/li.strolch.service/pom.xml @@ -27,12 +27,12 @@ - ch.eitchnet - ch.eitchnet.xmlpers + li.strolch + li.strolch.xmlpers - ch.eitchnet - ch.eitchnet.privilege + li.strolch + li.strolch.privilege diff --git a/pom.xml b/pom.xml index cd149340e..d696dfeb8 100644 --- a/pom.xml +++ b/pom.xml @@ -53,10 +53,6 @@ - 1.1.0-SNAPSHOT - 1.1.0-SNAPSHOT - 1.1.0-SNAPSHOT - 1.1.0-SNAPSHOT UTF-8 yyyy-MM-dd HH:mm:ss ${maven.build.timestamp} @@ -69,7 +65,6 @@ li.strolch.privilege li.strolch.utils li.strolch.xmlpers - li.strolch.parent li.strolch.dev li.strolch.bom li.strolch.model @@ -119,6 +114,21 @@ li.strolch.rest ${project.version} + + li.strolch + li.strolch.utils + ${project.version} + + + li.strolch + li.strolch.xmlpers + ${project.version} + + + li.strolch + li.strolch.privilege + ${project.version} + @@ -128,23 +138,6 @@ test - - - ch.eitchnet - ch.eitchnet.utils - ${eitchnet.utils.version} - - - ch.eitchnet - ch.eitchnet.xmlpers - ${eitchnet.xmlpers.version} - - - ch.eitchnet - ch.eitchnet.privilege - ${eitchnet.privilege.version} - - org.slf4j diff --git a/strolch_minimal_rest/pom.xml b/strolch_minimal_rest/pom.xml index f01762043..5d8f00612 100644 --- a/strolch_minimal_rest/pom.xml +++ b/strolch_minimal_rest/pom.xml @@ -68,12 +68,12 @@ - ch.eitchnet - ch.eitchnet.utils + li.strolch + li.strolch.utils - ch.eitchnet - ch.eitchnet.privilege + li.strolch + li.strolch.privilege From f0bba0f9157da7b76f9a432c0012c36a54a1ed11 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 18:13:02 +0200 Subject: [PATCH 447/457] [Major] Moving eitchnet projects into Strolch --- li.strolch.rest/pom.xml | 16 ++++++++++++---- li.strolch.utils/pom.xml | 7 +++++++ pom.xml | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/li.strolch.rest/pom.xml b/li.strolch.rest/pom.xml index 7a424d044..db0d34aa3 100644 --- a/li.strolch.rest/pom.xml +++ b/li.strolch.rest/pom.xml @@ -15,6 +15,18 @@ Restful Web Service API for Strolch 2011 + + + + org.glassfish.jersey + jersey-bom + ${jersey.version} + pom + import + + + + @@ -36,10 +48,6 @@ - - org.glassfish.jersey - jersey-bom - javax.servlet javax.servlet-api diff --git a/li.strolch.utils/pom.xml b/li.strolch.utils/pom.xml index b5dd83456..380f71a2b 100644 --- a/li.strolch.utils/pom.xml +++ b/li.strolch.utils/pom.xml @@ -16,6 +16,13 @@ These utils contain project independent helper classes and utilities for reuse 2011 + + + org.slf4j + slf4j-api + + + diff --git a/pom.xml b/pom.xml index d696dfeb8..1d18b44eb 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,28 @@ strolch_minimal_rest + + + + org.slf4j + slf4j-api + + + + + junit + junit + + + org.hamcrest + hamcrest-core + + + org.hamcrest + hamcrest-library + + + From 9139d3dd09041c792f96b32e18fde7ea6d6dd93b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 18:14:43 +0200 Subject: [PATCH 448/457] [Major] Moving eitchnet projects into Strolch --- li.strolch.privilege/.gitignore | 1 + li.strolch.utils/.gitignore | 1 + li.strolch.xmlpers/.gitignore | 1 + 3 files changed, 3 insertions(+) create mode 100644 li.strolch.privilege/.gitignore create mode 100644 li.strolch.utils/.gitignore create mode 100644 li.strolch.xmlpers/.gitignore diff --git a/li.strolch.privilege/.gitignore b/li.strolch.privilege/.gitignore new file mode 100644 index 000000000..b83d22266 --- /dev/null +++ b/li.strolch.privilege/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/li.strolch.utils/.gitignore b/li.strolch.utils/.gitignore new file mode 100644 index 000000000..b83d22266 --- /dev/null +++ b/li.strolch.utils/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/li.strolch.xmlpers/.gitignore b/li.strolch.xmlpers/.gitignore new file mode 100644 index 000000000..b83d22266 --- /dev/null +++ b/li.strolch.xmlpers/.gitignore @@ -0,0 +1 @@ +/target/ From 4ea4806f00346fe87626bdd7d7ac95b93e16e46a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 26 Jun 2016 11:13:07 +0200 Subject: [PATCH 449/457] [Minor] Fixing compile errors in minimal* --- .../src/main/java/li/strolch/minimal/main/Main.java | 2 +- strolch_minimal_rest/pom.xml | 2 -- .../li/strolch/minimal/rest/main/StartupListener.java | 10 +++++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/strolch_minimal/src/main/java/li/strolch/minimal/main/Main.java b/strolch_minimal/src/main/java/li/strolch/minimal/main/Main.java index 225a0c3cb..a16a190ff 100644 --- a/strolch_minimal/src/main/java/li/strolch/minimal/main/Main.java +++ b/strolch_minimal/src/main/java/li/strolch/minimal/main/Main.java @@ -26,7 +26,7 @@ public class Main { * @param args */ public static void main(String[] args) { - MainStarter mainStarter = new MainStarter(); + MainStarter mainStarter = new MainStarter(Main.class); mainStarter.start(args); } } diff --git a/strolch_minimal_rest/pom.xml b/strolch_minimal_rest/pom.xml index 5d8f00612..4212a0817 100644 --- a/strolch_minimal_rest/pom.xml +++ b/strolch_minimal_rest/pom.xml @@ -98,7 +98,6 @@ javax.servlet javax.servlet-api - 3.0.1 provided @@ -117,7 +116,6 @@ com.google.code.gson gson - 2.3.1 diff --git a/strolch_minimal_rest/src/main/java/li/strolch/minimal/rest/main/StartupListener.java b/strolch_minimal_rest/src/main/java/li/strolch/minimal/rest/main/StartupListener.java index 2d3954935..51a2185ab 100644 --- a/strolch_minimal_rest/src/main/java/li/strolch/minimal/rest/main/StartupListener.java +++ b/strolch_minimal_rest/src/main/java/li/strolch/minimal/rest/main/StartupListener.java @@ -6,12 +6,13 @@ import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; -import li.strolch.agent.api.StrolchAgent; -import li.strolch.runtime.configuration.StrolchEnvironment; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import li.strolch.agent.api.StrolchAgent; +import li.strolch.agent.api.StrolchBootstrapper; +import li.strolch.runtime.configuration.StrolchEnvironment; + @WebListener public class StartupListener implements ServletContextListener { @@ -28,8 +29,7 @@ public class StartupListener implements ServletContextListener { String environment = StrolchEnvironment.getEnvironmentFromEnvProperties(pathF); logger.info("Starting Strolch Minimal Rest..."); try { - this.agent = new StrolchAgent(); - this.agent.setup(environment, pathF); + this.agent = new StrolchBootstrapper(StartupListener.class).setupByRoot(environment, pathF); this.agent.initialize(); this.agent.start(); } catch (Exception e) { From 364cbf29658017ae72ec0e93336f8a87cbe033d6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 26 Jun 2016 11:20:09 +0200 Subject: [PATCH 450/457] [Minor] Adding ignores --- li.strolch.privilege/.gitignore | 1 + li.strolch.utils/.gitignore | 1 + li.strolch.xmlpers/.gitignore | 1 + 3 files changed, 3 insertions(+) diff --git a/li.strolch.privilege/.gitignore b/li.strolch.privilege/.gitignore index b83d22266..0f44a0f25 100644 --- a/li.strolch.privilege/.gitignore +++ b/li.strolch.privilege/.gitignore @@ -1 +1,2 @@ /target/ +/.classpath diff --git a/li.strolch.utils/.gitignore b/li.strolch.utils/.gitignore index b83d22266..0f44a0f25 100644 --- a/li.strolch.utils/.gitignore +++ b/li.strolch.utils/.gitignore @@ -1 +1,2 @@ /target/ +/.classpath diff --git a/li.strolch.xmlpers/.gitignore b/li.strolch.xmlpers/.gitignore index b83d22266..0f44a0f25 100644 --- a/li.strolch.xmlpers/.gitignore +++ b/li.strolch.xmlpers/.gitignore @@ -1 +1,2 @@ /target/ +/.classpath From 11ba7eb1de7da230a8eac4f14b46609c5cd0ffce Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 26 Jun 2016 11:38:41 +0200 Subject: [PATCH 451/457] [Major] Moved ch.eitchnet to li.strolch --- .../java/li/strolch/agent/api/AuditTrail.java | 2 +- .../strolch/agent/api/ComponentContainer.java | 2 +- .../agent/api/RestrictableElement.java | 2 +- .../li/strolch/agent/api/StrolchAgent.java | 2 +- .../agent/api/StrolchBootstrapper.java | 8 +- .../strolch/agent/api/StrolchComponent.java | 4 +- .../li/strolch/agent/api/StrolchRealm.java | 2 +- .../agent/impl/AuditingActivityMap.java | 2 +- .../agent/impl/AuditingAuditMapFacade.java | 4 +- .../agent/impl/AuditingElementMapFacade.java | 2 +- .../strolch/agent/impl/AuditingOrderMap.java | 2 +- .../agent/impl/AuditingResourceMap.java | 2 +- .../strolch/agent/impl/CachedAuditTrail.java | 3 +- .../li/strolch/agent/impl/CachedRealm.java | 8 +- .../agent/impl/ComponentContainerImpl.java | 6 +- .../impl/ComponentDependencyAnalyzer.java | 5 +- .../agent/impl/DefaultLockHandler.java | 3 +- .../agent/impl/DefaultRealmHandler.java | 2 +- .../li/strolch/agent/impl/EmptyRealm.java | 6 +- .../agent/impl/InternalStrolchRealm.java | 5 +- .../agent/impl/NoStrategyAuditTrail.java | 2 +- .../li/strolch/agent/impl/StartRealms.java | 4 +- .../agent/impl/TransactionalAuditTrail.java | 2 +- .../agent/impl/TransactionalRealm.java | 8 +- .../li/strolch/agent/impl/TransientRealm.java | 8 +- .../persistence/api/AbstractTransaction.java | 12 +- .../li/strolch/persistence/api/AuditDao.java | 2 +- .../persistence/api/PersistenceHandler.java | 2 +- .../persistence/api/StrolchTransaction.java | 2 +- .../persistence/api/TransactionResult.java | 2 +- .../inmemory/InMemoryAuditDao.java | 4 +- .../inmemory/InMemoryPersistence.java | 2 +- .../inmemory/InMemoryPersistenceHandler.java | 2 +- .../inmemory/InMemoryTransaction.java | 2 +- .../strolch/policy/DefaultPolicyHandler.java | 4 +- .../policy/StrolchPolicyFileParser.java | 2 +- .../li/strolch/runtime/StrolchConstants.java | 4 +- .../AbstractionConfiguration.java | 2 +- .../configuration/ConfigurationParser.java | 4 +- .../configuration/ConfigurationSaxParser.java | 4 +- .../configuration/DbConnectionBuilder.java | 8 +- .../configuration/StrolchEnvironment.java | 4 +- .../li/strolch/runtime/main/MainStarter.java | 4 +- .../DefaultStrolchPrivilegeHandler.java | 38 +- .../runtime/privilege/PrivilegeHandler.java | 26 +- .../strolch/runtime/privilege/RunAsAgent.java | 4 +- .../runtime/privilege/RunRunnable.java | 4 +- .../query/enums/DefaultEnumHandler.java | 6 +- .../runtime/query/enums/EnumHandler.java | 2 +- .../runtime/query/enums/StrolchEnum.java | 2 +- .../query/inmemory/AuditTypeNavigator.java | 4 +- .../runtime/query/inmemory/DateSelector.java | 2 +- .../runtime/query/inmemory/IdSelector.java | 2 +- .../InMemoryActivityQueryVisitor.java | 2 +- .../query/inmemory/InMemoryAuditQuery.java | 2 +- .../inmemory/InMemoryAuditQueryVisitor.java | 4 +- .../inmemory/InMemoryOrderQueryVisitor.java | 2 +- .../runtime/query/inmemory/InMemoryQuery.java | 2 +- .../query/inmemory/InMemoryQueryVisitor.java | 2 +- .../InMemoryResourceQueryVisitor.java | 2 +- .../runtime/query/inmemory/NameSelector.java | 2 +- .../runtime/query/inmemory/NotSelector.java | 2 +- .../query/inmemory/ParameterSelector.java | 4 +- .../strolch/service/api/AbstractService.java | 14 +- .../java/li/strolch/service/api/Command.java | 10 +- .../service/api/DefaultServiceHandler.java | 8 +- .../java/li/strolch/service/api/Service.java | 2 +- .../strolch/service/api/ServiceHandler.java | 2 +- .../src/test/java/li/strolch/RuntimeMock.java | 4 +- .../strolch/agent/ComponentContainerTest.java | 2 +- .../strolch/policytest/PolicyHandlerTest.java | 2 +- .../configuration/BootstrapperTest.java | 2 +- .../ConfigurationParserTest.java | 2 +- .../ControllerDependencyTest.java | 2 +- .../runtime/query/enums/EnumHandlerTest.java | 2 +- .../query/inmemory/AuditQueryTest.java | 5 +- .../query/inmemory/FindByLocatorTest.java | 2 +- .../query/inmemory/InMemoryQueryTest.java | 4 +- .../runtime/query/inmemory/QueryTest.java | 4 +- .../cachedtest/config/PrivilegeConfig.xml | 10 +- .../cachedtest/config/PrivilegeRoles.xml | 2 +- .../emptytest/config/PrivilegeConfig.xml | 10 +- .../emptytest/config/PrivilegeRoles.xml | 2 +- li.strolch.agent/src/test/resources/log4j.xml | 2 +- .../minimaltest/config/PrivilegeConfig.xml | 10 +- .../minimaltest/config/PrivilegeRoles.xml | 2 +- .../realmtest/config/PrivilegeConfig.xml | 10 +- .../realmtest/config/PrivilegeRoles.xml | 2 +- .../config/PrivilegeConfig.xml | 10 +- .../config/PrivilegeRoles.xml | 2 +- .../transienttest/config/PrivilegeConfig.xml | 10 +- .../transienttest/config/PrivilegeRoles.xml | 2 +- .../StrolchAccessDeniedException.java | 4 +- .../strolch/model/AbstractStrolchElement.java | 2 +- .../model/GroupedParameterizedElement.java | 2 +- .../main/java/li/strolch/model/Locator.java | 2 +- .../java/li/strolch/model/ModelGenerator.java | 4 +- .../li/strolch/model/ModelStatistics.java | 6 +- .../src/main/java/li/strolch/model/Order.java | 2 +- .../strolch/model/ParameterizedElement.java | 2 +- .../li/strolch/model/activity/Activity.java | 2 +- .../strolch/model/audit/ActionSelection.java | 2 +- .../model/audit/AuditFromDomReader.java | 5 +- .../li/strolch/model/audit/AuditQuery.java | 8 +- .../model/audit/AuditToDomVisitor.java | 5 +- .../strolch/model/audit/ElementSelection.java | 2 +- .../model/audit/IdentitySelection.java | 2 +- .../json/StrolchElementToJsonVisitor.java | 2 +- .../model/parameter/AbstractParameter.java | 2 +- .../model/parameter/BooleanParameter.java | 4 +- .../model/parameter/DateParameter.java | 4 +- .../model/parameter/DurationParameter.java | 4 +- .../model/parameter/FloatListParameter.java | 4 +- .../model/parameter/FloatParameter.java | 2 +- .../model/parameter/IntegerListParameter.java | 4 +- .../model/parameter/IntegerParameter.java | 2 +- .../model/parameter/LongListParameter.java | 4 +- .../model/parameter/LongParameter.java | 2 +- .../model/parameter/StringListParameter.java | 4 +- .../model/parameter/StringParameter.java | 2 +- .../li/strolch/model/policy/PolicyDefs.java | 2 +- .../li/strolch/model/query/ActivityQuery.java | 2 +- .../li/strolch/model/query/DateSelection.java | 2 +- .../li/strolch/model/query/IdSelection.java | 2 +- .../li/strolch/model/query/NameSelection.java | 2 +- .../li/strolch/model/query/OrderQuery.java | 2 +- .../model/query/ParameterSelection.java | 8 +- .../li/strolch/model/query/ResourceQuery.java | 2 +- .../strolch/model/query/StringSelection.java | 2 +- .../model/query/StrolchElementQuery.java | 6 +- .../li/strolch/model/query/StrolchQuery.java | 2 +- .../model/query/StrolchTypeNavigation.java | 2 +- .../timedstate/AbstractStrolchTimedState.java | 2 +- .../strolch/model/timevalue/impl/AString.java | 2 +- .../model/timevalue/impl/StringSetValue.java | 4 +- .../StrolchElementDeepEqualsVisitor.java | 2 +- .../model/xml/ActivityToDomVisitor.java | 2 +- .../strolch/model/xml/Iso8601DateAdapter.java | 2 +- .../strolch/model/xml/OrderToDomVisitor.java | 3 +- .../model/xml/OrderToXmlStringVisitor.java | 2 +- .../model/xml/ResourceToDomVisitor.java | 3 +- .../model/xml/ResourceToXmlStringVisitor.java | 2 +- .../xml/StrolchElementFromDomVisitor.java | 6 +- .../model/xml/StrolchElementToDomVisitor.java | 2 +- .../model/xml/StrolchElementToSaxVisitor.java | 2 +- .../xml/StrolchElementToSaxWriterVisitor.java | 2 +- .../model/xml/XmlModelSaxFileReader.java | 3 +- .../strolch/model/xml/XmlModelSaxReader.java | 4 +- .../model/xml/XmlModelSaxStreamReader.java | 3 +- .../model/XmlModelDefaultHandlerTest.java | 3 +- li.strolch.model/src/test/resources/log4j.xml | 2 +- .../src/main/resources/log4j.xml | 2 +- .../config/PrivilegeConfig.xml | 6 +- .../config/PrivilegeRoles.xml | 2 +- .../runtime_xml/config/PrivilegeConfig.xml | 6 +- .../src/runtime_xml/config/PrivilegeRoles.xml | 2 +- .../PerformancePostgreSqlTest.java | 6 +- .../strolch/performance/PerformanceTest.java | 2 +- .../performance/PerformanceTestService.java | 2 +- .../postgresql/PostgreSqlAuditDao.java | 4 +- .../PostgreSqlAuditQueryVisitor.java | 2 +- .../PostgreSqlDbConnectionBuilder.java | 2 +- .../postgresql/PostgreSqlDbInitializer.java | 18 +- .../postgresql/PostgreSqlHelper.java | 4 +- .../postgresql/PostgreSqlInitializer.java | 7 +- .../PostgreSqlPersistenceHandler.java | 14 +- .../postgresql/PostgreSqlQueryVisitor.java | 8 +- .../PostgreSqlSchemaInitializer.java | 6 +- .../PostgreSqlStrolchTransaction.java | 3 +- .../postgresql/dao/test/AuditQueryTest.java | 6 +- .../postgresql/dao/test/CachedDaoTest.java | 6 +- .../postgresql/dao/test/DbMigrationTest.java | 7 +- .../dao/test/ObserverUpdateTest.java | 3 +- .../postgresql/dao/test/QueryTest.java | 6 +- .../postgresql/dao/test/RealmTest.java | 2 +- .../cachedruntime/config/PrivilegeConfig.xml | 10 +- .../cachedruntime/config/PrivilegeRoles.xml | 2 +- .../src/test/resources/log4j.xml | 2 +- .../realmtest/config/PrivilegeConfig.xml | 10 +- .../realmtest/config/PrivilegeRoles.xml | 2 +- .../config/PrivilegeConfig.xml | 10 +- .../config/PrivilegeRoles.xml | 2 +- .../strolch/persistence/xml/AbstractDao.java | 8 +- .../strolch/persistence/xml/XmlAuditDao.java | 14 +- .../xml/XmlPersistenceHandler.java | 12 +- .../xml/XmlStrolchTransaction.java | 8 +- .../xml/model/ActivityContextFactory.java | 10 +- .../xml/model/ActivityDomParser.java | 3 +- .../xml/model/ActivityParserFactory.java | 6 +- .../xml/model/ActivitySaxParser.java | 3 +- .../xml/model/AuditContextFactory.java | 10 +- .../persistence/xml/model/AuditDomParser.java | 3 +- .../xml/model/AuditParserFactory.java | 6 +- .../xml/model/OrderContextFactory.java | 10 +- .../persistence/xml/model/OrderDomParser.java | 3 +- .../xml/model/OrderParserFactory.java | 6 +- .../persistence/xml/model/OrderSaxParser.java | 3 +- .../xml/model/ResourceContextFactory.java | 10 +- .../xml/model/ResourceDomParser.java | 3 +- .../xml/model/ResourceParserFactory.java | 6 +- .../xml/model/ResourceSaxParser.java | 3 +- .../impl/dao/test/ExistingDbTest.java | 4 +- .../impl/dao/test/ObserverUpdateTest.java | 4 +- .../cachedruntime/config/PrivilegeConfig.xml | 10 +- .../cachedruntime/config/PrivilegeRoles.xml | 2 +- .../config/PrivilegeConfig.xml | 10 +- .../config/PrivilegeRoles.xml | 2 +- .../src/test/resources/log4j.xml | 2 +- .../config/PrivilegeConfig.xml | 10 +- .../config/PrivilegeRoles.xml | 2 +- .../src/main/resources/log4j.xml | 2 +- .../webapp/WEB-INF/config/PrivilegeConfig.xml | 10 +- .../webapp/WEB-INF/config/PrivilegeRoles.xml | 2 +- li.strolch.privilege/README.md | 8 +- .../config/PrivilegeConfig.xml | 10 +- .../config/PrivilegeConfigMerge.xml | 10 +- .../config/PrivilegeRoles.xml | 12 +- .../privilege/base/AccessDeniedException.java | 2 +- .../base/InvalidCredentialsException.java | 2 +- .../base/PrivilegeConflictResolution.java | 6 +- .../privilege/base/PrivilegeException.java | 2 +- .../handler/DefaultEncryptionHandler.java | 8 +- .../handler/DefaultPrivilegeHandler.java | 48 +- .../privilege/handler/EncryptionHandler.java | 2 +- .../privilege/handler/PersistenceHandler.java | 12 +- .../privilege/handler/PrivilegeHandler.java | 28 +- .../privilege/handler/SystemUserAction.java | 8 +- .../handler/XmlPersistenceHandler.java | 22 +- .../helper/BootstrapConfigurationHelper.java | 14 +- .../privilege/helper/PasswordCreaterUI.java | 4 +- .../privilege/helper/PasswordCreator.java | 4 +- .../helper/PrivilegeInitializationHelper.java | 22 +- .../privilege/helper/XmlConstants.java | 2 +- .../privilege/i18n/PrivilegeMessages.java | 2 +- .../strolch}/privilege/model/Certificate.java | 10 +- .../strolch}/privilege/model/IPrivilege.java | 6 +- .../privilege/model/PrivilegeContext.java | 10 +- .../privilege/model/PrivilegeRep.java | 12 +- .../privilege/model/Restrictable.java | 4 +- .../strolch}/privilege/model/RoleRep.java | 8 +- .../privilege/model/SimpleRestrictable.java | 4 +- .../strolch}/privilege/model/UserRep.java | 12 +- .../strolch}/privilege/model/UserState.java | 4 +- .../internal/PrivilegeContainerModel.java | 8 +- .../model/internal/PrivilegeImpl.java | 16 +- .../privilege/model/internal/Role.java | 12 +- .../privilege/model/internal/User.java | 10 +- .../privilege/policy/DefaultPrivilege.java | 16 +- .../privilege/policy/PrivilegePolicy.java | 14 +- .../policy/PrivilegePolicyHelper.java | 16 +- .../privilege/policy/RoleAccessPrivilege.java | 20 +- .../privilege/policy/UserAccessPrivilege.java | 20 +- ...erAccessWithSameOrganisationPrivilege.java | 24 +- .../UsernameFromCertificatePrivilege.java | 14 +- ...tificateWithSameOrganisationPrivilege.java | 18 +- .../xml/CertificateStubsDomWriter.java | 10 +- .../xml/CertificateStubsSaxReader.java | 12 +- .../strolch}/privilege/xml/ElementParser.java | 2 +- .../privilege/xml/ElementParserAdapter.java | 2 +- .../xml/PrivilegeConfigDomWriter.java | 10 +- .../xml/PrivilegeConfigSaxReader.java | 12 +- .../xml/PrivilegeRolesDomWriter.java | 10 +- .../xml/PrivilegeRolesSaxReader.java | 18 +- .../xml/PrivilegeUsersDomWriter.java | 10 +- .../xml/PrivilegeUsersSaxReader.java | 8 +- .../privilege/test/AbstractPrivilegeTest.java | 14 +- .../privilege/test/PersistSessionsTest.java | 2 +- .../test/PrivilegeConflictMergeTest.java | 4 +- .../privilege/test/PrivilegeTest.java | 36 +- .../strolch}/privilege/test/XmlTest.java | 66 +- .../test/model/TestRestrictable.java | 8 +- .../test/model/TestSystemRestrictable.java | 8 +- .../test/model/TestSystemUserAction.java | 6 +- .../test/model/TestSystemUserActionDeny.java | 6 +- .../model/query/parser/QueryParser.java | 2 +- .../rest/DefaultStrolchSessionHandler.java | 12 +- .../strolch/rest/RestfulStrolchComponent.java | 2 +- .../rest/StrolchRestfulExceptionMapper.java | 4 +- .../strolch/rest/StrolchSessionHandler.java | 2 +- .../strolch/rest/endpoint/AuditsService.java | 2 +- .../rest/endpoint/AuthenticationService.java | 14 +- .../li/strolch/rest/endpoint/EnumQuery.java | 3 +- .../li/strolch/rest/endpoint/Inspector.java | 2 +- .../li/strolch/rest/endpoint/ModelQuery.java | 6 +- .../endpoint/PrivilegePoliciesService.java | 6 +- .../rest/endpoint/PrivilegeRolesService.java | 12 +- .../rest/endpoint/PrivilegeUsersService.java | 12 +- .../rest/endpoint/UserSessionsService.java | 3 +- .../strolch/rest/endpoint/VersionQuery.java | 2 +- .../filters/AuthenicationRequestFilter.java | 4 +- .../filters/AuthenicationResponseFilter.java | 2 +- .../li/strolch/rest/helper/RestfulHelper.java | 6 +- .../strolch/rest/model/AuditQueryResult.java | 2 +- .../li/strolch/rest/model/LoginResult.java | 2 +- .../li/strolch/rest/model/OrderOverview.java | 2 +- .../java/li/strolch/rest/model/Result.java | 2 +- .../strolch/rest/model/StringListResult.java | 2 +- .../li/strolch/rest/model/UserSession.java | 2 +- .../model/visitor/ToAuditQueryVisitor.java | 6 +- li.strolch.rest/src/test/resources/log4j.xml | 2 +- .../config/PrivilegeConfig.xml | 10 +- .../config/PrivilegeModel.xml | 2 +- .../config/PrivilegeRoles.xml | 2 +- .../command/AddOrderCollectionCommand.java | 2 +- .../li/strolch/command/AddOrderCommand.java | 2 +- .../command/AddResourceCollectionCommand.java | 2 +- .../strolch/command/AddResourceCommand.java | 2 +- .../command/RemoveOrderCollectionCommand.java | 2 +- .../strolch/command/RemoveOrderCommand.java | 2 +- .../RemoveResourceCollectionCommand.java | 2 +- .../command/RemoveResourceCommand.java | 2 +- .../command/UpdateOrderCollectionCommand.java | 2 +- .../strolch/command/UpdateOrderCommand.java | 2 +- .../UpdateResourceCollectionCommand.java | 2 +- .../command/UpdateResourceCommand.java | 2 +- .../command/XmlExportModelCommand.java | 4 +- .../command/XmlImportModelCommand.java | 2 +- .../parameter/AddParameterCommand.java | 2 +- .../parameter/RemoveParameterCommand.java | 2 +- .../parameter/SetParameterCommand.java | 2 +- .../command/plan/AssignActionCommand.java | 2 +- .../command/plan/PlanActionCommand.java | 2 +- .../command/plan/PlanActivityCommand.java | 2 +- .../command/plan/ShiftActionCommand.java | 2 +- .../li/strolch/migrations/CodeMigration.java | 4 +- .../CurrentMigrationVersionQuery.java | 4 +- .../li/strolch/migrations/DataMigration.java | 4 +- .../java/li/strolch/migrations/Migration.java | 5 +- .../strolch/migrations/MigrationVersion.java | 2 +- .../li/strolch/migrations/Migrations.java | 9 +- .../strolch/migrations/MigrationsHandler.java | 6 +- .../QueryCurrentVersionsAction.java | 4 +- .../migrations/RunMigrationsAction.java | 4 +- .../li/strolch/service/ClearModelService.java | 2 +- .../service/XmlExportModelService.java | 2 +- .../service/XmlImportModelService.java | 2 +- .../executor/ServiceExecutionHandler.java | 4 +- .../executor/ServiceExecutionStatus.java | 2 +- ...geAddOrReplacePrivilegeOnRoleArgument.java | 2 +- ...egeAddOrReplacePrivilegeOnRoleService.java | 4 +- .../roles/PrivilegeAddRoleService.java | 4 +- ...ivilegeRemovePrivilegeFromRoleService.java | 4 +- .../roles/PrivilegeRemoveRoleService.java | 4 +- .../roles/PrivilegeRoleArgument.java | 2 +- .../privilege/roles/PrivilegeRoleResult.java | 2 +- .../roles/PrivilegeUpdateRoleService.java | 4 +- .../users/PrivilegeAddRoleToUserService.java | 4 +- .../users/PrivilegeAddUserService.java | 4 +- .../PrivilegeRemoveRoleFromUserService.java | 4 +- .../users/PrivilegeRemoveUserService.java | 4 +- .../users/PrivilegeSetUserLocaleService.java | 4 +- .../PrivilegeSetUserPasswordService.java | 2 +- .../users/PrivilegeSetUserStateArgument.java | 2 +- .../users/PrivilegeSetUserStateService.java | 4 +- .../users/PrivilegeUpdateUserService.java | 4 +- .../users/PrivilegeUserArgument.java | 2 +- .../privilege/users/PrivilegeUserResult.java | 2 +- .../command/AbstractRealmCommandTest.java | 3 +- .../li/strolch/migrations/MigrationsTest.java | 7 +- .../java/li/strolch/service/FlushTxTest.java | 3 +- .../test/java/li/strolch/service/TxTest.java | 3 +- .../test/AbstractRealmServiceTest.java | 6 +- .../service/test/GreetingServiceTest.java | 4 +- .../li/strolch/service/test/LockingTest.java | 2 +- .../li/strolch/service/test/ServiceTest.java | 8 +- .../service/test/XmlExportServiceTest.java | 3 +- .../service/test/model/GreetingService.java | 2 +- .../src/test/resources/log4j.xml | 2 +- .../migrationstest/config/PrivilegeConfig.xml | 10 +- .../migrationstest/config/PrivilegeRoles.xml | 2 +- .../svctest/config/PrivilegeConfig.xml | 10 +- .../svctest/config/PrivilegeRoles.xml | 2 +- .../transienttest/config/PrivilegeConfig.xml | 10 +- .../transienttest/config/PrivilegeRoles.xml | 2 +- .../config/PrivilegeConfig.xml | 10 +- .../config/PrivilegeRoles.xml | 2 +- .../testbase/runtime/AbstractModelTest.java | 3 +- .../runtime/AuditModelTestRunner.java | 4 +- .../runtime/OrderModelTestRunner.java | 2 +- .../runtime/ResourceModelTestRunner.java | 2 +- .../strolch/testbase/runtime/RuntimeMock.java | 6 +- .../src/main/resources/log4j.xml | 2 +- .../src/runtime/config/PrivilegeConfig.xml | 6 +- .../src/runtime/config/PrivilegeRoles.xml | 2 +- .../src/main/resources/log4j.xml | 2 +- .../webapp/WEB-INF/config/PrivilegeConfig.xml | 6 +- .../webapp/WEB-INF/config/PrivilegeRoles.xml | 2 +- li.strolch.utils/README.md | 4 +- .../strolch}/communication/CommandKey.java | 4 +- .../CommunicationConnection.java | 10 +- .../communication/CommunicationEndpoint.java | 2 +- .../communication/ConnectionException.java | 66 +- .../communication/ConnectionInfo.java | 2 +- .../communication/ConnectionMessages.java | 4 +- .../communication/ConnectionMode.java | 2 +- .../communication/ConnectionObserver.java | 2 +- .../communication/ConnectionState.java | 2 +- .../ConnectionStateObserver.java | 2 +- .../strolch}/communication/IoMessage.java | 6 +- .../communication/IoMessageArchive.java | 2 +- .../communication/IoMessageStateObserver.java | 4 +- .../communication/IoMessageVisitor.java | 4 +- .../communication/SimpleMessageArchive.java | 2 +- .../communication/StreamMessageVisitor.java | 2 +- .../strolch}/communication/chat/Chat.java | 4 +- .../communication/chat/ChatClient.java | 26 +- .../communication/chat/ChatIoMessage.java | 8 +- .../chat/ChatMessageVisitor.java | 10 +- .../communication/chat/ChatServer.java | 26 +- .../console/ConsoleEndpoint.java | 14 +- .../console/ConsoleMessageVisitor.java | 6 +- .../communication/file/FileEndpoint.java | 20 +- .../communication/file/FileEndpointMode.java | 2 +- .../tcpip/ClientSocketEndpoint.java | 20 +- .../tcpip/ServerSocketEndpoint.java | 18 +- .../tcpip/SocketEndpointConstants.java | 2 +- .../tcpip/SocketMessageVisitor.java | 6 +- .../strolch}/db/DbConnectionCheck.java | 2 +- .../strolch}/db/DbConstants.java | 2 +- .../strolch}/db/DbDataSourceBuilder.java | 2 +- .../strolch}/db/DbException.java | 2 +- .../strolch}/db/DbMigrationState.java | 2 +- .../strolch}/db/DbSchemaVersionCheck.java | 12 +- .../strolch}/fileserver/FileClient.java | 2 +- .../strolch}/fileserver/FileClientUtil.java | 6 +- .../strolch}/fileserver/FileDeletion.java | 2 +- .../strolch}/fileserver/FileHandler.java | 6 +- .../strolch}/fileserver/FilePart.java | 2 +- .../strolch}/utils/StringMatchMode.java | 4 +- .../strolch}/utils/Version.java | 1014 ++++++++--------- .../strolch}/utils/collections/DateRange.java | 6 +- .../utils/collections/DefaultedHashMap.java | 2 +- .../utils/collections/MapOfLists.java | 2 +- .../strolch}/utils/collections/MapOfMaps.java | 2 +- .../strolch}/utils/collections/Paging.java | 2 +- .../strolch}/utils/collections/Tuple.java | 2 +- .../strolch}/utils/dbc/DBC.java | 4 +- .../utils/exceptions/XmlException.java | 2 +- .../utils/helper/AesCryptoHelper.java | 4 +- .../strolch}/utils/helper/ArraysHelper.java | 2 +- .../strolch}/utils/helper/AsciiHelper.java | 644 +++++------ .../strolch}/utils/helper/BaseEncoding.java | 2 +- .../strolch}/utils/helper/ByteHelper.java | 2 +- .../strolch}/utils/helper/ClassHelper.java | 2 +- .../strolch}/utils/helper/DomUtil.java | 2 +- .../utils/helper/ExceptionHelper.java | 2 +- .../strolch}/utils/helper/FileHelper.java | 2 +- .../strolch}/utils/helper/MathHelper.java | 250 ++-- .../strolch}/utils/helper/ProcessHelper.java | 2 +- .../utils/helper/PropertiesHelper.java | 2 +- .../strolch}/utils/helper/StringHelper.java | 2 +- .../strolch}/utils/helper/SystemHelper.java | 2 +- .../strolch}/utils/helper/XmlDomSigner.java | 4 +- .../strolch}/utils/helper/XmlHelper.java | 4 +- .../utils/io/FileProgressListener.java | 2 +- .../utils/io/FileStreamProgressWatcher.java | 2 +- .../utils/io/LoggingFileProgressListener.java | 4 +- .../utils/io/ProgressableFileInputStream.java | 2 +- .../strolch}/utils/iso8601/DateFormat.java | 116 +- .../utils/iso8601/DurationFormat.java | 82 +- .../strolch}/utils/iso8601/FormatFactory.java | 2 +- .../strolch}/utils/iso8601/ISO8601.java | 594 +++++----- .../utils/iso8601/ISO8601Duration.java | 2 +- .../utils/iso8601/ISO8601FormatFactory.java | 4 +- .../utils/iso8601/ISO8601Worktime.java | 474 ++++---- .../utils/iso8601/WorktimeFormat.java | 82 +- .../utils/objectfilter/ObjectCache.java | 2 +- .../utils/objectfilter/ObjectFilter.java | 2 +- .../utils/objectfilter/Operation.java | 2 +- .../strolch}/utils/xml/XmlKeyValue.java | 2 +- li.strolch.utils/src/main/java/log4j.xml | 2 +- .../communication/AbstractEndpointTest.java | 4 +- .../communication/ConsoleEndpointTest.java | 11 +- .../communication/FileEndpointTest.java | 14 +- .../SimpleMessageArchiveTest.java | 7 +- .../communication/SocketEndpointTest.java | 15 +- .../communication/TestConnectionObserver.java | 6 +- .../strolch}/communication/TestIoMessage.java | 5 +- .../strolch}/utils/StringMatchModeTest.java | 10 +- .../strolch}/utils/VersionTest.java | 5 +- .../utils/collections/DateRangeTest.java | 13 +- .../collections/DefaultedHashMapTest.java | 4 +- .../utils/collections/PagingTest.java | 4 +- .../strolch}/utils/dbc/DBCTest.java | 21 +- .../utils/helper/AesCryptoHelperTest.java | 7 +- .../utils/helper/BaseDecodingTest.java | 19 +- .../utils/helper/BaseEncodingTest.java | 18 +- .../utils/helper/ExceptionHelperTest.java | 4 +- .../GenerateReverseBaseEncodingAlphabets.java | 4 +- .../utils/helper/ReplacePropertiesInTest.java | 4 +- .../utils/helper/XmlSignHelperTest.java | 4 +- .../utils/objectfilter/ObjectFilterTest.java | 4 +- .../src/test/resources/SignedXmlFile.xml | 2 +- .../resources/SignedXmlFileWithNamespaces.xml | 2 +- li.strolch.utils/src/test/resources/log4j.xml | 2 +- li.strolch.xmlpers/README.md | 8 +- .../strolch}/xmlpers/api/DomParser.java | 2 +- .../strolch}/xmlpers/api/FileDao.java | 6 +- .../strolch}/xmlpers/api/FileIo.java | 11 +- .../strolch}/xmlpers/api/IoMode.java | 2 +- .../strolch}/xmlpers/api/IoOperation.java | 2 +- .../strolch}/xmlpers/api/MetadataDao.java | 8 +- .../xmlpers/api/ModificationResult.java | 2 +- .../strolch}/xmlpers/api/ObjectDao.java | 20 +- .../strolch}/xmlpers/api/ParserFactory.java | 2 +- .../xmlpers/api/PersistenceConstants.java | 4 +- .../xmlpers/api/PersistenceContext.java | 4 +- .../api/PersistenceContextFactory.java | 6 +- .../PersistenceContextFactoryDelegator.java | 2 +- .../xmlpers/api/PersistenceManager.java | 2 +- .../xmlpers/api/PersistenceManagerLoader.java | 4 +- .../xmlpers/api/PersistenceRealm.java | 4 +- .../xmlpers/api/PersistenceTransaction.java | 4 +- .../strolch}/xmlpers/api/SaxParser.java | 2 +- .../xmlpers/api/TransactionCloseStrategy.java | 2 +- .../xmlpers/api/TransactionResult.java | 4 +- .../xmlpers/api/TransactionState.java | 2 +- .../xmlpers/api/XmlPersistenceException.java | 2 +- .../impl/DefaultPersistenceManager.java | 22 +- .../xmlpers/impl/DefaultPersistenceRealm.java | 10 +- .../impl/DefaultPersistenceTransaction.java | 32 +- .../strolch}/xmlpers/impl/PathBuilder.java | 10 +- .../xmlpers/objref/IdOfSubTypeRef.java | 12 +- .../strolch}/xmlpers/objref/IdOfTypeRef.java | 12 +- .../xmlpers/objref/LockableObject.java | 6 +- .../strolch}/xmlpers/objref/ObjectRef.java | 8 +- .../xmlpers/objref/ObjectReferenceCache.java | 4 +- .../xmlpers/objref/RefNameCreator.java | 4 +- .../strolch}/xmlpers/objref/RootRef.java | 8 +- .../strolch}/xmlpers/objref/SubTypeRef.java | 8 +- .../strolch}/xmlpers/objref/TypeRef.java | 8 +- .../strolch}/xmlpers/util/AssertionUtil.java | 6 +- .../strolch}/xmlpers/util/DomUtil.java | 4 +- .../xmlpers/util/FilenameUtility.java | 6 +- .../xmlpers/test/AbstractPersistenceTest.java | 22 +- .../strolch}/xmlpers/test/FileDaoTest.java | 36 +- .../strolch}/xmlpers/test/LockingTest.java | 22 +- .../xmlpers/test/ObjectDaoBookTest.java | 30 +- .../xmlpers/test/ObjectDaoResourceTest.java | 38 +- .../strolch}/xmlpers/test/RealmTest.java | 16 +- .../xmlpers/test/TransactionResultTest.java | 22 +- .../strolch}/xmlpers/test/XmlTestMain.java | 13 +- .../xmlpers/test/impl/BookContextFactory.java | 14 +- .../xmlpers/test/impl/BookDomParser.java | 8 +- .../xmlpers/test/impl/BookParserFactory.java | 10 +- .../xmlpers/test/impl/BookSaxParser.java | 6 +- .../test/impl/MyModelContextFactory.java | 14 +- .../xmlpers/test/impl/MyModelDomParser.java | 10 +- .../test/impl/MyModelParserFactory.java | 10 +- .../xmlpers/test/impl/MyModelSaxParser.java | 8 +- .../xmlpers/test/impl/TestConstants.java | 2 +- .../strolch}/xmlpers/test/model/Book.java | 2 +- .../xmlpers/test/model/ModelBuilder.java | 2 +- .../strolch}/xmlpers/test/model/MyModel.java | 2 +- .../xmlpers/test/model/MyParameter.java | 2 +- .../src/test/resources/log4j.xml | 2 +- strolch_minimal/src/main/resources/log4j.xml | 2 +- .../src/runtime/config/PrivilegeConfig.xml | 6 +- .../src/runtime/config/PrivilegeModel.xml | 2 +- .../li/strolch/minimal/rest/util/Result.java | 2 +- .../webapp/WEB-INF/config/PrivilegeConfig.xml | 6 +- .../webapp/WEB-INF/config/PrivilegeModel.xml | 2 +- 562 files changed, 3273 insertions(+), 3249 deletions(-) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/base/AccessDeniedException.java (96%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/base/InvalidCredentialsException.java (91%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/base/PrivilegeConflictResolution.java (91%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/base/PrivilegeException.java (97%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/handler/DefaultEncryptionHandler.java (95%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/handler/DefaultPrivilegeHandler.java (97%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/handler/EncryptionHandler.java (98%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/handler/PersistenceHandler.java (93%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/handler/PrivilegeHandler.java (97%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/handler/SystemUserAction.java (90%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/handler/XmlPersistenceHandler.java (94%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/helper/BootstrapConfigurationHelper.java (86%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/helper/PasswordCreaterUI.java (97%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/helper/PasswordCreator.java (95%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/helper/PrivilegeInitializationHelper.java (89%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/helper/XmlConstants.java (99%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/i18n/PrivilegeMessages.java (97%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/Certificate.java (97%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/IPrivilege.java (93%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/PrivilegeContext.java (94%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/PrivilegeRep.java (95%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/Restrictable.java (94%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/RoleRep.java (95%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/SimpleRestrictable.java (94%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/UserRep.java (97%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/UserState.java (95%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/internal/PrivilegeContainerModel.java (96%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/internal/PrivilegeImpl.java (93%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/internal/Role.java (94%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/model/internal/User.java (96%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/policy/DefaultPrivilege.java (82%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/policy/PrivilegePolicy.java (81%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/policy/PrivilegePolicyHelper.java (86%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/policy/RoleAccessPrivilege.java (89%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/policy/UserAccessPrivilege.java (93%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/policy/UserAccessWithSameOrganisationPrivilege.java (86%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/policy/UsernameFromCertificatePrivilege.java (86%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java (85%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/CertificateStubsDomWriter.java (91%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/CertificateStubsSaxReader.java (91%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/ElementParser.java (96%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/ElementParserAdapter.java (97%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/PrivilegeConfigDomWriter.java (94%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/PrivilegeConfigSaxReader.java (93%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/PrivilegeRolesDomWriter.java (92%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/PrivilegeRolesSaxReader.java (92%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/PrivilegeUsersDomWriter.java (94%) rename li.strolch.privilege/src/main/java/{ch/eitchnet => li/strolch}/privilege/xml/PrivilegeUsersSaxReader.java (97%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/AbstractPrivilegeTest.java (91%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/PersistSessionsTest.java (97%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/PrivilegeConflictMergeTest.java (96%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/PrivilegeTest.java (94%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/XmlTest.java (87%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/model/TestRestrictable.java (80%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/model/TestSystemRestrictable.java (80%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/model/TestSystemUserAction.java (85%) rename li.strolch.privilege/src/test/java/{ch/eitchnet => li/strolch}/privilege/test/model/TestSystemUserActionDeny.java (86%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/CommandKey.java (95%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/CommunicationConnection.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/CommunicationEndpoint.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/ConnectionException.java (93%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/ConnectionInfo.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/ConnectionMessages.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/ConnectionMode.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/ConnectionObserver.java (95%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/ConnectionState.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/ConnectionStateObserver.java (82%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/IoMessage.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/IoMessageArchive.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/IoMessageStateObserver.java (93%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/IoMessageVisitor.java (93%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/SimpleMessageArchive.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/StreamMessageVisitor.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/chat/Chat.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/chat/ChatClient.java (77%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/chat/ChatIoMessage.java (78%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/chat/ChatMessageVisitor.java (84%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/chat/ChatServer.java (78%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/console/ConsoleEndpoint.java (86%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/console/ConsoleMessageVisitor.java (85%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/file/FileEndpoint.java (93%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/file/FileEndpointMode.java (95%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/tcpip/ClientSocketEndpoint.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/tcpip/ServerSocketEndpoint.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/tcpip/SocketEndpointConstants.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/communication/tcpip/SocketMessageVisitor.java (92%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/db/DbConnectionCheck.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/db/DbConstants.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/db/DbDataSourceBuilder.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/db/DbException.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/db/DbMigrationState.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/db/DbSchemaVersionCheck.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/fileserver/FileClient.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/fileserver/FileClientUtil.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/fileserver/FileDeletion.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/fileserver/FileHandler.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/fileserver/FilePart.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/StringMatchMode.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/Version.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/collections/DateRange.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/collections/DefaultedHashMap.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/collections/MapOfLists.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/collections/MapOfMaps.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/collections/Paging.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/collections/Tuple.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/dbc/DBC.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/exceptions/XmlException.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/AesCryptoHelper.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/ArraysHelper.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/AsciiHelper.java (95%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/BaseEncoding.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/ByteHelper.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/ClassHelper.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/DomUtil.java (97%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/ExceptionHelper.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/FileHelper.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/MathHelper.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/ProcessHelper.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/PropertiesHelper.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/StringHelper.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/SystemHelper.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/XmlDomSigner.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/helper/XmlHelper.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/io/FileProgressListener.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/io/FileStreamProgressWatcher.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/io/LoggingFileProgressListener.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/io/ProgressableFileInputStream.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/iso8601/DateFormat.java (93%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/iso8601/DurationFormat.java (93%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/iso8601/FormatFactory.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/iso8601/ISO8601.java (94%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/iso8601/ISO8601Duration.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/iso8601/ISO8601FormatFactory.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/iso8601/ISO8601Worktime.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/iso8601/WorktimeFormat.java (93%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/objectfilter/ObjectCache.java (98%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/objectfilter/ObjectFilter.java (99%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/objectfilter/Operation.java (96%) rename li.strolch.utils/src/main/java/{ch/eitchnet => li/strolch}/utils/xml/XmlKeyValue.java (97%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/communication/AbstractEndpointTest.java (96%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/communication/ConsoleEndpointTest.java (84%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/communication/FileEndpointTest.java (90%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/communication/SimpleMessageArchiveTest.java (90%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/communication/SocketEndpointTest.java (91%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/communication/TestConnectionObserver.java (87%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/communication/TestIoMessage.java (90%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/StringMatchModeTest.java (89%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/VersionTest.java (97%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/collections/DateRangeTest.java (87%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/collections/DefaultedHashMapTest.java (93%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/collections/PagingTest.java (96%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/dbc/DBCTest.java (93%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/helper/AesCryptoHelperTest.java (95%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/helper/BaseDecodingTest.java (90%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/helper/BaseEncodingTest.java (90%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/helper/ExceptionHelperTest.java (93%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/helper/GenerateReverseBaseEncodingAlphabets.java (96%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/helper/ReplacePropertiesInTest.java (97%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/helper/XmlSignHelperTest.java (98%) rename li.strolch.utils/src/test/java/{ch/eitchnet => li/strolch}/utils/objectfilter/ObjectFilterTest.java (99%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/DomParser.java (95%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/FileDao.java (98%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/FileIo.java (96%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/IoMode.java (97%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/IoOperation.java (95%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/MetadataDao.java (97%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/ModificationResult.java (97%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/ObjectDao.java (92%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/ParserFactory.java (95%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/PersistenceConstants.java (91%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/PersistenceContext.java (96%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/PersistenceContextFactory.java (85%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/PersistenceContextFactoryDelegator.java (98%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/PersistenceManager.java (96%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/PersistenceManagerLoader.java (91%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/PersistenceRealm.java (91%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/PersistenceTransaction.java (95%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/SaxParser.java (96%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/TransactionCloseStrategy.java (96%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/TransactionResult.java (98%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/TransactionState.java (95%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/api/XmlPersistenceException.java (96%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/impl/DefaultPersistenceManager.java (88%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/impl/DefaultPersistenceRealm.java (88%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/impl/DefaultPersistenceTransaction.java (92%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/impl/PathBuilder.java (94%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/IdOfSubTypeRef.java (92%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/IdOfTypeRef.java (91%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/LockableObject.java (93%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/ObjectRef.java (89%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/ObjectReferenceCache.java (97%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/RefNameCreator.java (96%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/RootRef.java (93%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/SubTypeRef.java (94%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/objref/TypeRef.java (93%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/util/AssertionUtil.java (93%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/util/DomUtil.java (93%) rename li.strolch.xmlpers/src/main/java/{ch/eitchnet => li/strolch}/xmlpers/util/FilenameUtility.java (89%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/AbstractPersistenceTest.java (81%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/FileDaoTest.java (76%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/LockingTest.java (90%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/ObjectDaoBookTest.java (88%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/ObjectDaoResourceTest.java (90%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/RealmTest.java (91%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/TransactionResultTest.java (87%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/XmlTestMain.java (97%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/BookContextFactory.java (78%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/BookDomParser.java (93%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/BookParserFactory.java (81%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/BookSaxParser.java (94%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/MyModelContextFactory.java (78%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/MyModelDomParser.java (93%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/MyModelParserFactory.java (80%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/MyModelSaxParser.java (93%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/impl/TestConstants.java (95%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/model/Book.java (98%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/model/ModelBuilder.java (99%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/model/MyModel.java (98%) rename li.strolch.xmlpers/src/test/java/{ch/eitchnet => li/strolch}/xmlpers/test/model/MyParameter.java (98%) diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/AuditTrail.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/AuditTrail.java index eba9cdafe..b4bfdcf71 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/AuditTrail.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/AuditTrail.java @@ -21,7 +21,7 @@ import java.util.Set; import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditQuery; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.collections.DateRange; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/ComponentContainer.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/ComponentContainer.java index ccd113fb2..fba0a9c31 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/ComponentContainer.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/ComponentContainer.java @@ -18,9 +18,9 @@ package li.strolch.agent.api; import java.util.Set; import li.strolch.exception.StrolchException; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/RestrictableElement.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/RestrictableElement.java index 7a00c6067..af28a1ede 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/RestrictableElement.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/RestrictableElement.java @@ -15,7 +15,7 @@ */ package li.strolch.agent.api; -import ch.eitchnet.privilege.model.Restrictable; +import li.strolch.privilege.model.Restrictable; /** * A simple implementation for the {@link Restrictable} interface diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchAgent.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchAgent.java index 0a3abe6d5..1932956de 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchAgent.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchAgent.java @@ -25,11 +25,11 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.agent.impl.ComponentContainerImpl; import li.strolch.runtime.configuration.ConfigurationParser; import li.strolch.runtime.configuration.RuntimeConfiguration; import li.strolch.runtime.configuration.StrolchConfiguration; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchBootstrapper.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchBootstrapper.java index 53668a13d..cfec02c99 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchBootstrapper.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchBootstrapper.java @@ -12,12 +12,12 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.helper.XmlHelper; import li.strolch.runtime.configuration.ConfigurationParser; import li.strolch.runtime.configuration.StrolchConfigurationException; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.FileHelper; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.helper.XmlHelper; public class StrolchBootstrapper extends DefaultHandler { diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchComponent.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchComponent.java index 1733d8717..c47963824 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchComponent.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchComponent.java @@ -23,8 +23,8 @@ import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.SystemUserAction; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.SystemUserAction; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.privilege.RunRunnable; diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchRealm.java index 1bfa6bc56..cd878bf14 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/api/StrolchRealm.java @@ -18,7 +18,7 @@ package li.strolch.agent.api; import li.strolch.agent.impl.DataStoreMode; import li.strolch.model.StrolchRootElement; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.privilege.model.Certificate; +import li.strolch.privilege.model.Certificate; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingActivityMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingActivityMap.java index 67090a76f..081544e67 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingActivityMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingActivityMap.java @@ -17,7 +17,6 @@ package li.strolch.agent.impl; import java.util.List; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.agent.api.ActivityMap; import li.strolch.agent.api.AuditTrail; import li.strolch.agent.api.ElementMap; @@ -25,6 +24,7 @@ import li.strolch.model.ActivityVisitor; import li.strolch.model.activity.Activity; import li.strolch.model.query.ActivityQuery; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.utils.dbc.DBC; /** * This is the {@link AuditTrail} for {@link Activity Activities} diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingAuditMapFacade.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingAuditMapFacade.java index 51dc49be1..8b7d6e72c 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingAuditMapFacade.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingAuditMapFacade.java @@ -26,8 +26,8 @@ import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditQuery; import li.strolch.model.audit.AuditVisitor; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.collections.DateRange; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.collections.DateRange; +import li.strolch.utils.dbc.DBC; /** *

        diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingElementMapFacade.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingElementMapFacade.java index cd42837fd..9df714f60 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingElementMapFacade.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingElementMapFacade.java @@ -28,7 +28,7 @@ import li.strolch.model.StrolchRootElement; import li.strolch.model.parameter.StringListParameter; import li.strolch.model.parameter.StringParameter; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** *

        diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingOrderMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingOrderMap.java index 3b18390ea..618cba9e4 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingOrderMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingOrderMap.java @@ -24,7 +24,7 @@ import li.strolch.model.Order; import li.strolch.model.OrderVisitor; import li.strolch.model.query.OrderQuery; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * This is the {@link AuditTrail} for {@link Order Orders} diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingResourceMap.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingResourceMap.java index 7ee432312..6d1c4c8fd 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingResourceMap.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/AuditingResourceMap.java @@ -24,7 +24,7 @@ import li.strolch.model.Resource; import li.strolch.model.ResourceVisitor; import li.strolch.model.query.ResourceQuery; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * This is the {@link AuditTrail} for {@link Resource Resources} diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedAuditTrail.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedAuditTrail.java index 97156fb6d..dfbc9fc6e 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedAuditTrail.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedAuditTrail.java @@ -25,12 +25,11 @@ import li.strolch.model.audit.AuditQuery; import li.strolch.persistence.api.AuditDao; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.inmemory.InMemoryAuditDao; +import li.strolch.utils.collections.DateRange; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.collections.DateRange; - /** * @author Robert von Burg */ diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedRealm.java index b3384bbf8..b16d3afd8 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/CachedRealm.java @@ -32,11 +32,11 @@ import li.strolch.persistence.api.OrderDao; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.ResourceDao; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.configuration.ComponentConfiguration; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerImpl.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerImpl.java index a834dd44e..102e65197 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerImpl.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerImpl.java @@ -26,9 +26,6 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.helper.SystemHelper; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.ComponentState; import li.strolch.agent.api.RealmHandler; @@ -36,11 +33,14 @@ import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.api.StrolchRealm; import li.strolch.exception.StrolchException; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.StrolchConfiguration; import li.strolch.runtime.configuration.StrolchConfigurationException; import li.strolch.runtime.privilege.PrivilegeHandler; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.helper.SystemHelper; public class ComponentContainerImpl implements ComponentContainer { diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentDependencyAnalyzer.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentDependencyAnalyzer.java index ac174d22c..abbfba0ad 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentDependencyAnalyzer.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentDependencyAnalyzer.java @@ -23,13 +23,12 @@ import java.util.Set; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.StrolchConfiguration; import li.strolch.runtime.configuration.StrolchConfigurationException; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; - public class ComponentDependencyAnalyzer { private static final Logger logger = LoggerFactory.getLogger(ComponentDependencyAnalyzer.class); diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultLockHandler.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultLockHandler.java index 0b25e83c5..3fd5ddd7b 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultLockHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultLockHandler.java @@ -25,12 +25,11 @@ import li.strolch.agent.api.LockHandler; import li.strolch.agent.api.StrolchLockException; import li.strolch.model.Locator; import li.strolch.model.StrolchRootElement; +import li.strolch.utils.dbc.DBC; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.dbc.DBC; - /** * @author Robert von Burg */ diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultRealmHandler.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultRealmHandler.java index 14eb2fc05..a1f85c615 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultRealmHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/DefaultRealmHandler.java @@ -29,7 +29,7 @@ import li.strolch.exception.StrolchException; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/EmptyRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/EmptyRealm.java index 9801b2368..17a70e1b1 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/EmptyRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/EmptyRealm.java @@ -25,11 +25,11 @@ import li.strolch.agent.api.ResourceMap; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.inmemory.InMemoryPersistence; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/InternalStrolchRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/InternalStrolchRealm.java index bba2e338a..cd02a7143 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/InternalStrolchRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/InternalStrolchRealm.java @@ -27,15 +27,14 @@ import li.strolch.agent.api.OrderMap; import li.strolch.agent.api.ResourceMap; import li.strolch.agent.api.StrolchRealm; import li.strolch.model.StrolchRootElement; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; +import li.strolch.utils.dbc.DBC; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.dbc.DBC; - /** * @author Robert von Burg */ diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/NoStrategyAuditTrail.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/NoStrategyAuditTrail.java index 0c0742cad..32f437bae 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/NoStrategyAuditTrail.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/NoStrategyAuditTrail.java @@ -22,7 +22,7 @@ import li.strolch.agent.api.AuditTrail; import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditQuery; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.collections.DateRange; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/StartRealms.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/StartRealms.java index 513fe7d2b..6c40f67fe 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/StartRealms.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/StartRealms.java @@ -15,8 +15,8 @@ */ package li.strolch.agent.impl; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.PrivilegeContext; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalAuditTrail.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalAuditTrail.java index 077714a9a..503246c03 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalAuditTrail.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalAuditTrail.java @@ -23,7 +23,7 @@ import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditQuery; import li.strolch.persistence.api.AuditDao; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.collections.DateRange; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalRealm.java index bd7cfd173..36cce26b0 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransactionalRealm.java @@ -24,11 +24,11 @@ import li.strolch.agent.api.OrderMap; import li.strolch.agent.api.ResourceMap; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.configuration.ComponentConfiguration; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransientRealm.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransientRealm.java index 500aaa0d0..cfd6478a4 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransientRealm.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/TransientRealm.java @@ -18,10 +18,6 @@ package li.strolch.agent.impl; import java.io.File; import java.text.MessageFormat; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.agent.api.ActivityMap; import li.strolch.agent.api.AuditTrail; import li.strolch.agent.api.ComponentContainer; @@ -32,9 +28,13 @@ import li.strolch.model.xml.XmlModelSaxFileReader; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.inmemory.InMemoryPersistence; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.StrolchConfigurationException; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java index 95f950148..0b0447e22 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AbstractTransaction.java @@ -26,12 +26,6 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.ExceptionHelper; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.agent.api.ActivityMap; import li.strolch.agent.api.AuditTrail; import li.strolch.agent.api.ObserverHandler; @@ -69,9 +63,15 @@ import li.strolch.model.query.StrolchQuery; import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.IValue; import li.strolch.model.visitor.ElementTypeVisitor; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.service.api.Command; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.ExceptionHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AuditDao.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AuditDao.java index 78fc82b21..97f4202c7 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/AuditDao.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/AuditDao.java @@ -20,7 +20,7 @@ import java.util.Set; import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditQuery; -import ch.eitchnet.utils.collections.DateRange; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/PersistenceHandler.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/PersistenceHandler.java index eb52f4716..c10240026 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/PersistenceHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/PersistenceHandler.java @@ -24,7 +24,7 @@ import li.strolch.model.Order; import li.strolch.model.Resource; import li.strolch.model.activity.Activity; import li.strolch.model.audit.Audit; -import ch.eitchnet.privilege.model.Certificate; +import li.strolch.privilege.model.Certificate; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java index 9f57bb466..2e61afc80 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/StrolchTransaction.java @@ -17,7 +17,6 @@ package li.strolch.persistence.api; import java.util.List; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.agent.api.ActivityMap; import li.strolch.agent.api.AuditTrail; import li.strolch.agent.api.OrderMap; @@ -48,6 +47,7 @@ import li.strolch.model.parameter.StringParameter; import li.strolch.model.query.ActivityQuery; import li.strolch.model.query.OrderQuery; import li.strolch.model.query.ResourceQuery; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.service.api.Command; diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/api/TransactionResult.java b/li.strolch.agent/src/main/java/li/strolch/persistence/api/TransactionResult.java index 52126b64e..a3898de6d 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/api/TransactionResult.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/api/TransactionResult.java @@ -17,7 +17,7 @@ package li.strolch.persistence.api; import java.util.Date; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; public class TransactionResult { diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryAuditDao.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryAuditDao.java index 048d854e3..9ff2d7c91 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryAuditDao.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryAuditDao.java @@ -26,8 +26,8 @@ import li.strolch.model.audit.AuditQuery; import li.strolch.persistence.api.AuditDao; import li.strolch.runtime.query.inmemory.InMemoryAuditQuery; import li.strolch.runtime.query.inmemory.InMemoryAuditQueryVisitor; -import ch.eitchnet.utils.collections.DateRange; -import ch.eitchnet.utils.collections.MapOfMaps; +import li.strolch.utils.collections.DateRange; +import li.strolch.utils.collections.MapOfMaps; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistence.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistence.java index 251da8939..d5a5fe089 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistence.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistence.java @@ -25,8 +25,8 @@ import li.strolch.persistence.api.OrderDao; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.ResourceDao; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; public class InMemoryPersistence implements PersistenceHandler { diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistenceHandler.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistenceHandler.java index b702dfa43..2d09ce609 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistenceHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryPersistenceHandler.java @@ -24,8 +24,8 @@ import li.strolch.persistence.api.OrderDao; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.ResourceDao; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.configuration.ComponentConfiguration; -import ch.eitchnet.privilege.model.Certificate; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryTransaction.java b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryTransaction.java index 8916f0c61..de9674fd0 100644 --- a/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryTransaction.java +++ b/li.strolch.agent/src/main/java/li/strolch/persistence/inmemory/InMemoryTransaction.java @@ -20,8 +20,8 @@ import li.strolch.persistence.api.AbstractTransaction; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.TransactionResult; import li.strolch.persistence.api.TransactionState; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; public class InMemoryTransaction extends AbstractTransaction { diff --git a/li.strolch.agent/src/main/java/li/strolch/policy/DefaultPolicyHandler.java b/li.strolch.agent/src/main/java/li/strolch/policy/DefaultPolicyHandler.java index 1fee9fa75..187939906 100644 --- a/li.strolch.agent/src/main/java/li/strolch/policy/DefaultPolicyHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/policy/DefaultPolicyHandler.java @@ -21,8 +21,6 @@ import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.Map; -import ch.eitchnet.utils.collections.MapOfMaps; -import ch.eitchnet.utils.helper.XmlHelper; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.exception.StrolchPolicyException; @@ -34,6 +32,8 @@ import li.strolch.persistence.api.StrolchTransaction; import li.strolch.policy.StrolchPolicyFileParser.PolicyModel; import li.strolch.policy.StrolchPolicyFileParser.PolicyType; import li.strolch.runtime.configuration.ComponentConfiguration; +import li.strolch.utils.collections.MapOfMaps; +import li.strolch.utils.helper.XmlHelper; /** *

        diff --git a/li.strolch.agent/src/main/java/li/strolch/policy/StrolchPolicyFileParser.java b/li.strolch.agent/src/main/java/li/strolch/policy/StrolchPolicyFileParser.java index dd5651e2e..bcdc1ca9c 100644 --- a/li.strolch.agent/src/main/java/li/strolch/policy/StrolchPolicyFileParser.java +++ b/li.strolch.agent/src/main/java/li/strolch/policy/StrolchPolicyFileParser.java @@ -22,7 +22,7 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/StrolchConstants.java b/li.strolch.agent/src/main/java/li/strolch/runtime/StrolchConstants.java index 461a5458a..286b5add3 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/StrolchConstants.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/StrolchConstants.java @@ -15,12 +15,12 @@ */ package li.strolch.runtime; -import static ch.eitchnet.utils.helper.StringHelper.DOT; +import static li.strolch.utils.helper.StringHelper.DOT; -import ch.eitchnet.privilege.handler.PrivilegeHandler; import li.strolch.agent.api.ObserverHandler; import li.strolch.model.StrolchModelConstants; import li.strolch.persistence.api.PersistenceHandler; +import li.strolch.privilege.handler.PrivilegeHandler; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/AbstractionConfiguration.java b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/AbstractionConfiguration.java index d82e81369..d618600fd 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/AbstractionConfiguration.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/AbstractionConfiguration.java @@ -24,7 +24,7 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; public abstract class AbstractionConfiguration { diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/ConfigurationParser.java b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/ConfigurationParser.java index bc86c2474..4e8ad34b7 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/ConfigurationParser.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/ConfigurationParser.java @@ -18,9 +18,9 @@ package li.strolch.runtime.configuration; import java.io.File; import java.text.MessageFormat; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.XmlHelper; import li.strolch.runtime.configuration.ConfigurationSaxParser.ConfigurationBuilder; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.XmlHelper; public class ConfigurationParser { diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/ConfigurationSaxParser.java b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/ConfigurationSaxParser.java index 8f057d2d7..929150585 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/ConfigurationSaxParser.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/ConfigurationSaxParser.java @@ -43,10 +43,10 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.model.Locator; import li.strolch.model.Locator.LocatorBuilder; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; public class ConfigurationSaxParser extends DefaultHandler { diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/DbConnectionBuilder.java b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/DbConnectionBuilder.java index ba8fd0180..2bb67e880 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/DbConnectionBuilder.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/DbConnectionBuilder.java @@ -15,10 +15,10 @@ */ package li.strolch.runtime.configuration; -import static ch.eitchnet.db.DbConstants.PROP_DB_IGNORE_REALM; -import static ch.eitchnet.db.DbConstants.PROP_DB_PASSWORD; -import static ch.eitchnet.db.DbConstants.PROP_DB_URL; -import static ch.eitchnet.db.DbConstants.PROP_DB_USERNAME; +import static li.strolch.db.DbConstants.PROP_DB_IGNORE_REALM; +import static li.strolch.db.DbConstants.PROP_DB_PASSWORD; +import static li.strolch.db.DbConstants.PROP_DB_URL; +import static li.strolch.db.DbConstants.PROP_DB_USERNAME; import static li.strolch.runtime.StrolchConstants.makeRealmKey; import java.sql.Connection; diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/StrolchEnvironment.java b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/StrolchEnvironment.java index 75428e28a..1feea9b64 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/StrolchEnvironment.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/configuration/StrolchEnvironment.java @@ -21,8 +21,8 @@ import java.text.MessageFormat; import java.util.Properties; import li.strolch.runtime.StrolchConstants; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/main/MainStarter.java b/li.strolch.agent/src/main/java/li/strolch/runtime/main/MainStarter.java index 7979a735f..bfdb763d6 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/main/MainStarter.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/main/MainStarter.java @@ -31,11 +31,11 @@ import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchBootstrapper; import li.strolch.agent.api.StrolchVersion; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java index e6214ea29..ccbf07306 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/DefaultStrolchPrivilegeHandler.java @@ -15,43 +15,43 @@ */ package li.strolch.runtime.privilege; -import static ch.eitchnet.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS; -import static ch.eitchnet.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS_PATH; +import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS; +import static li.strolch.privilege.handler.PrivilegeHandler.PARAM_PERSIST_SESSIONS_PATH; import java.io.File; import java.io.FileInputStream; import java.text.MessageFormat; import java.util.Map; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; -import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.PersistenceHandler; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.handler.XmlPersistenceHandler; -import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; -import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; -import ch.eitchnet.utils.helper.XmlHelper; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.api.StrolchRealm; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.DefaultPrivilegeHandler; +import li.strolch.privilege.handler.EncryptionHandler; +import li.strolch.privilege.handler.PersistenceHandler; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.handler.XmlPersistenceHandler; +import li.strolch.privilege.helper.PrivilegeInitializationHelper; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.internal.PrivilegeContainerModel; +import li.strolch.privilege.xml.PrivilegeConfigSaxReader; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.RuntimeConfiguration; +import li.strolch.utils.helper.XmlHelper; public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements PrivilegeHandler { public static final String PROP_PRIVILEGE_CONFIG_FILE = "privilegeConfigFile"; //$NON-NLS-1$ public static final String PRIVILEGE_CONFIG_XML = "PrivilegeConfig.xml"; //$NON-NLS-1$ - private ch.eitchnet.privilege.handler.PrivilegeHandler privilegeHandler; + private li.strolch.privilege.handler.PrivilegeHandler privilegeHandler; public DefaultStrolchPrivilegeHandler(ComponentContainer container, String componentName) { super(container, componentName); @@ -65,7 +65,7 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements RuntimeConfiguration runtimeConfiguration = configuration.getRuntimeConfiguration(); File privilegeConfigFile = configuration.getConfigFile(PROP_PRIVILEGE_CONFIG_FILE, PRIVILEGE_CONFIG_XML, runtimeConfiguration); - ch.eitchnet.privilege.handler.PrivilegeHandler privilegeHandler = initializeFromXml(configuration, + li.strolch.privilege.handler.PrivilegeHandler privilegeHandler = initializeFromXml(configuration, privilegeConfigFile); this.privilegeHandler = privilegeHandler; } @@ -79,7 +79,7 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements * @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and * {@link PersistenceHandler} are set and initialized as well */ - private ch.eitchnet.privilege.handler.PrivilegeHandler initializeFromXml(ComponentConfiguration configuration, + private li.strolch.privilege.handler.PrivilegeHandler initializeFromXml(ComponentConfiguration configuration, File privilegeXmlFile) { // make sure file exists @@ -189,7 +189,7 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements } @Override - public ch.eitchnet.privilege.handler.PrivilegeHandler getPrivilegeHandler(Certificate certificate) + public li.strolch.privilege.handler.PrivilegeHandler getPrivilegeHandler(Certificate certificate) throws PrivilegeException { return this.privilegeHandler; } diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/PrivilegeHandler.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/PrivilegeHandler.java index d15cc8a02..9c543a99f 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/PrivilegeHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/PrivilegeHandler.java @@ -15,10 +15,10 @@ */ package li.strolch.runtime.privilege; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; /** * @author Robert von Burg @@ -30,28 +30,28 @@ public interface PrivilegeHandler { * @param password * @return * - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(String, byte[]) + * @see li.strolch.privilege.handler.PrivilegeHandler#authenticate(String, byte[]) */ public abstract Certificate authenticate(String username, byte[] password); /** * @param certificate * @throws PrivilegeException - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(Certificate) + * @see li.strolch.privilege.handler.PrivilegeHandler#isCertificateValid(Certificate) */ public abstract void isCertificateValid(Certificate certificate) throws PrivilegeException; /** * @param certificate * @return - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#invalidateSession(ch.eitchnet.privilege.model.Certificate) + * @see li.strolch.privilege.handler.PrivilegeHandler#invalidateSession(li.strolch.privilege.model.Certificate) */ public abstract boolean invalidateSession(Certificate certificate); /** * @param certificate * @return - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#invalidateSession(ch.eitchnet.privilege.model.Certificate) + * @see li.strolch.privilege.handler.PrivilegeHandler#invalidateSession(li.strolch.privilege.model.Certificate) */ public abstract boolean sessionTimeout(Certificate certificate); @@ -59,7 +59,7 @@ public interface PrivilegeHandler { * @param certificate * @return * @throws PrivilegeException - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilegeContext(ch.eitchnet.privilege.model.Certificate) + * @see li.strolch.privilege.handler.PrivilegeHandler#getPrivilegeContext(li.strolch.privilege.model.Certificate) */ public abstract PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException; @@ -67,8 +67,8 @@ public interface PrivilegeHandler { * @param systemUsername * @param action * @throws PrivilegeException - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#runAsSystem(java.lang.String, - * ch.eitchnet.privilege.handler.SystemUserAction) + * @see li.strolch.privilege.handler.PrivilegeHandler#runAsSystem(java.lang.String, + * li.strolch.privilege.handler.SystemUserAction) */ public abstract T runAsSystem(String systemUsername, T action) throws PrivilegeException; @@ -78,14 +78,14 @@ public interface PrivilegeHandler { * @return * @throws PrivilegeException */ - public abstract ch.eitchnet.privilege.handler.PrivilegeHandler getPrivilegeHandler(Certificate certificate) + public abstract li.strolch.privilege.handler.PrivilegeHandler getPrivilegeHandler(Certificate certificate) throws PrivilegeException; /** * @param certificate * @param password * @throws PrivilegeException - * @see {@link ch.eitchnet.privilege.handler.PrivilegeHandler#checkPassword(Certificate, byte[])} + * @see {@link li.strolch.privilege.handler.PrivilegeHandler#checkPassword(Certificate, byte[])} */ public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException; } \ No newline at end of file diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunAsAgent.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunAsAgent.java index 020846e6b..c46c0080d 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunAsAgent.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunAsAgent.java @@ -1,7 +1,7 @@ package li.strolch.runtime.privilege; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.service.api.AbstractService; import li.strolch.service.api.Service; import li.strolch.service.api.ServiceArgument; diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunRunnable.java b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunRunnable.java index 1022a06c3..5db3cda8c 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunRunnable.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/privilege/RunRunnable.java @@ -1,7 +1,7 @@ package li.strolch.runtime.privilege; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.PrivilegeContext; /** * {@link SystemUserAction} to run {@link Runnable} as a system user diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/DefaultEnumHandler.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/DefaultEnumHandler.java index 1c6411f0c..d86f0e3b2 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/DefaultEnumHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/DefaultEnumHandler.java @@ -15,7 +15,7 @@ */ package li.strolch.runtime.query.enums; -import static ch.eitchnet.utils.helper.StringHelper.UNDERLINE; +import static li.strolch.utils.helper.StringHelper.UNDERLINE; import java.text.MessageFormat; import java.util.HashMap; @@ -31,10 +31,10 @@ import li.strolch.model.ParameterBag; import li.strolch.model.Resource; import li.strolch.model.parameter.StringParameter; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.ComponentConfiguration; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/EnumHandler.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/EnumHandler.java index 932ab2b92..a456dbe18 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/EnumHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/EnumHandler.java @@ -17,7 +17,7 @@ package li.strolch.runtime.query.enums; import java.util.Locale; -import ch.eitchnet.privilege.model.Certificate; +import li.strolch.privilege.model.Certificate; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/StrolchEnum.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/StrolchEnum.java index dab3f9e8e..97a508a56 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/StrolchEnum.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/enums/StrolchEnum.java @@ -30,7 +30,7 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/AuditTypeNavigator.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/AuditTypeNavigator.java index 91471dd55..d94ea7128 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/AuditTypeNavigator.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/AuditTypeNavigator.java @@ -19,8 +19,8 @@ import java.util.List; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.AuditDao; -import ch.eitchnet.utils.collections.DateRange; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.collections.DateRange; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/DateSelector.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/DateSelector.java index 7297eaa4b..e7529ca9a 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/DateSelector.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/DateSelector.java @@ -16,7 +16,7 @@ package li.strolch.runtime.query.inmemory; import li.strolch.model.Order; -import ch.eitchnet.utils.collections.DateRange; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/IdSelector.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/IdSelector.java index 22a644c71..07bc02aa7 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/IdSelector.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/IdSelector.java @@ -17,8 +17,8 @@ package li.strolch.runtime.query.inmemory; import java.util.List; -import ch.eitchnet.utils.StringMatchMode; import li.strolch.model.StrolchElement; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryActivityQueryVisitor.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryActivityQueryVisitor.java index 23940ea2a..244d0b844 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryActivityQueryVisitor.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryActivityQueryVisitor.java @@ -23,7 +23,7 @@ import li.strolch.model.query.ActivityQuery; import li.strolch.model.query.ActivityQueryVisitor; import li.strolch.model.query.StrolchTypeNavigation; import li.strolch.persistence.api.ActivityDao; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryAuditQuery.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryAuditQuery.java index 74f7a67d1..502151590 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryAuditQuery.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryAuditQuery.java @@ -21,7 +21,7 @@ import java.util.List; import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditVisitor; import li.strolch.persistence.inmemory.InMemoryAuditDao; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryAuditQueryVisitor.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryAuditQueryVisitor.java index 6c0b7b5d2..cd246901a 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryAuditQueryVisitor.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryAuditQueryVisitor.java @@ -24,8 +24,8 @@ import li.strolch.model.audit.AuditQueryVisitor; import li.strolch.model.audit.AuditVisitor; import li.strolch.model.audit.ElementSelection; import li.strolch.model.audit.IdentitySelection; -import ch.eitchnet.utils.collections.DateRange; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.collections.DateRange; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryOrderQueryVisitor.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryOrderQueryVisitor.java index 823f33eda..6c9c1f388 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryOrderQueryVisitor.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryOrderQueryVisitor.java @@ -25,7 +25,7 @@ import li.strolch.model.query.OrderQueryVisitor; import li.strolch.model.query.StateSelection; import li.strolch.model.query.StrolchTypeNavigation; import li.strolch.persistence.api.OrderDao; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryQuery.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryQuery.java index 9d430f305..a2788ce2f 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryQuery.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryQuery.java @@ -24,7 +24,7 @@ import java.util.List; import li.strolch.model.StrolchElement; import li.strolch.model.visitor.StrolchElementVisitor; import li.strolch.persistence.api.StrolchDao; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryQueryVisitor.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryQueryVisitor.java index ee952964c..bc8de2ab7 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryQueryVisitor.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryQueryVisitor.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.model.GroupedParameterizedElement; import li.strolch.model.query.AndSelection; import li.strolch.model.query.BooleanSelection; @@ -54,6 +53,7 @@ import li.strolch.model.query.ordering.StrolchQueryOrderingVisitor; import li.strolch.persistence.api.StrolchDao; import li.strolch.runtime.query.inmemory.ParameterBagSelector.NullParameterBagSelector; import li.strolch.runtime.query.inmemory.ParameterSelector.StringParameterSelector; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryResourceQueryVisitor.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryResourceQueryVisitor.java index 88ad97046..fd5989d52 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryResourceQueryVisitor.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/InMemoryResourceQueryVisitor.java @@ -23,7 +23,7 @@ import li.strolch.model.query.ResourceQuery; import li.strolch.model.query.ResourceQueryVisitor; import li.strolch.model.query.StrolchTypeNavigation; import li.strolch.persistence.api.ResourceDao; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/NameSelector.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/NameSelector.java index 13b6c6d1f..862982d71 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/NameSelector.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/NameSelector.java @@ -15,8 +15,8 @@ */ package li.strolch.runtime.query.inmemory; -import ch.eitchnet.utils.StringMatchMode; import li.strolch.model.StrolchElement; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/NotSelector.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/NotSelector.java index 5cb86c337..ed4ac91d5 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/NotSelector.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/NotSelector.java @@ -16,7 +16,7 @@ package li.strolch.runtime.query.inmemory; import li.strolch.model.StrolchElement; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/ParameterSelector.java b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/ParameterSelector.java index bb057e008..ef1459ee6 100644 --- a/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/ParameterSelector.java +++ b/li.strolch.agent/src/main/java/li/strolch/runtime/query/inmemory/ParameterSelector.java @@ -18,8 +18,6 @@ package li.strolch.runtime.query.inmemory; import java.util.Date; import java.util.List; -import ch.eitchnet.utils.StringMatchMode; -import ch.eitchnet.utils.collections.DateRange; import li.strolch.model.GroupedParameterizedElement; import li.strolch.model.ParameterBag; import li.strolch.model.parameter.BooleanParameter; @@ -31,6 +29,8 @@ import li.strolch.model.parameter.ListParameter; import li.strolch.model.parameter.LongParameter; import li.strolch.model.parameter.Parameter; import li.strolch.model.parameter.StringParameter; +import li.strolch.utils.StringMatchMode; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/service/api/AbstractService.java b/li.strolch.agent/src/main/java/li/strolch/service/api/AbstractService.java index c1a162648..b91546e8d 100644 --- a/li.strolch.agent/src/main/java/li/strolch/service/api/AbstractService.java +++ b/li.strolch.agent/src/main/java/li/strolch/service/api/AbstractService.java @@ -20,19 +20,19 @@ import java.text.MessageFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.api.StrolchRealm; import li.strolch.exception.StrolchException; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.RuntimeConfiguration; import li.strolch.runtime.privilege.PrivilegeHandler; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg @@ -314,7 +314,7 @@ public abstract class AbstractService @@ -131,7 +131,7 @@ public abstract class Command implements Restrictable { } /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeName() */ @Override public String getPrivilegeName() { @@ -139,7 +139,7 @@ public abstract class Command implements Restrictable { } /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeValue() */ @Override public Object getPrivilegeValue() { diff --git a/li.strolch.agent/src/main/java/li/strolch/service/api/DefaultServiceHandler.java b/li.strolch.agent/src/main/java/li/strolch/service/api/DefaultServiceHandler.java index 34a6c428e..a97275dcf 100644 --- a/li.strolch.agent/src/main/java/li/strolch/service/api/DefaultServiceHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/service/api/DefaultServiceHandler.java @@ -17,17 +17,17 @@ package li.strolch.service.api; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.exception.StrolchAccessDeniedException; import li.strolch.exception.StrolchException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.RuntimeConfiguration; import li.strolch.runtime.privilege.PrivilegeHandler; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/main/java/li/strolch/service/api/Service.java b/li.strolch.agent/src/main/java/li/strolch/service/api/Service.java index 1bf0db9b9..ce07cf5c4 100644 --- a/li.strolch.agent/src/main/java/li/strolch/service/api/Service.java +++ b/li.strolch.agent/src/main/java/li/strolch/service/api/Service.java @@ -17,7 +17,7 @@ package li.strolch.service.api; import java.io.Serializable; -import ch.eitchnet.privilege.model.Restrictable; +import li.strolch.privilege.model.Restrictable; /** * Interface for Strolch service's. Service's are the main object in which business logic is implemented in a Strolch diff --git a/li.strolch.agent/src/main/java/li/strolch/service/api/ServiceHandler.java b/li.strolch.agent/src/main/java/li/strolch/service/api/ServiceHandler.java index 12899036f..3777bcfe7 100644 --- a/li.strolch.agent/src/main/java/li/strolch/service/api/ServiceHandler.java +++ b/li.strolch.agent/src/main/java/li/strolch/service/api/ServiceHandler.java @@ -15,7 +15,7 @@ */ package li.strolch.service.api; -import ch.eitchnet.privilege.model.Certificate; +import li.strolch.privilege.model.Certificate; public interface ServiceHandler { diff --git a/li.strolch.agent/src/test/java/li/strolch/RuntimeMock.java b/li.strolch.agent/src/test/java/li/strolch/RuntimeMock.java index ef29aaa46..ca122ac65 100644 --- a/li.strolch.agent/src/test/java/li/strolch/RuntimeMock.java +++ b/li.strolch.agent/src/test/java/li/strolch/RuntimeMock.java @@ -26,8 +26,6 @@ import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchBootstrapper; @@ -37,6 +35,8 @@ import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.service.api.ServiceHandler; import li.strolch.service.api.ServiceResult; import li.strolch.service.api.ServiceResultState; +import li.strolch.utils.helper.FileHelper; +import li.strolch.utils.helper.StringHelper; /** * Basically you should use the RuntimeMock class in the testbase project, but to mitigate circular dependencies, in diff --git a/li.strolch.agent/src/test/java/li/strolch/agent/ComponentContainerTest.java b/li.strolch.agent/src/test/java/li/strolch/agent/ComponentContainerTest.java index a29ed8641..810838da8 100644 --- a/li.strolch.agent/src/test/java/li/strolch/agent/ComponentContainerTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/agent/ComponentContainerTest.java @@ -23,7 +23,6 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.RuntimeMock; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.OrderMap; @@ -35,6 +34,7 @@ import li.strolch.model.Resource; import li.strolch.persistence.api.OrderDao; import li.strolch.persistence.api.ResourceDao; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.configuration.model.ResourceGeneratorHandlerTest; import li.strolch.runtime.configuration.model.ServiceHandlerTest; diff --git a/li.strolch.agent/src/test/java/li/strolch/policytest/PolicyHandlerTest.java b/li.strolch.agent/src/test/java/li/strolch/policytest/PolicyHandlerTest.java index a444a9952..a6fb49cd9 100644 --- a/li.strolch.agent/src/test/java/li/strolch/policytest/PolicyHandlerTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/policytest/PolicyHandlerTest.java @@ -19,7 +19,6 @@ import static org.junit.Assert.assertNotNull; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.RuntimeMock; import li.strolch.agent.ComponentContainerTest; import li.strolch.agent.api.ComponentContainer; @@ -27,6 +26,7 @@ import li.strolch.model.Resource; import li.strolch.model.policy.PolicyDef; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.policy.PolicyHandler; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; /** diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/BootstrapperTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/BootstrapperTest.java index 35c2adb63..27a2fb732 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/BootstrapperTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/BootstrapperTest.java @@ -6,10 +6,10 @@ import java.io.File; import org.junit.Test; -import ch.eitchnet.utils.helper.FileHelper; import li.strolch.RuntimeMock; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchBootstrapper; +import li.strolch.utils.helper.FileHelper; public class BootstrapperTest { diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ConfigurationParserTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ConfigurationParserTest.java index fdd60a343..8304f4e15 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ConfigurationParserTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ConfigurationParserTest.java @@ -23,10 +23,10 @@ import java.io.File; import org.junit.Test; -import ch.eitchnet.utils.helper.FileHelper; import li.strolch.RuntimeMock; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchBootstrapper; +import li.strolch.utils.helper.FileHelper; @SuppressWarnings("nls") public class ConfigurationParserTest { diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ControllerDependencyTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ControllerDependencyTest.java index ce62d9f98..b7d933463 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ControllerDependencyTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ControllerDependencyTest.java @@ -30,13 +30,13 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import ch.eitchnet.utils.dbc.DBC.DbcException; import li.strolch.agent.api.ComponentState; import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.impl.ComponentContainerImpl; import li.strolch.agent.impl.ComponentContainerStateHandler; import li.strolch.agent.impl.ComponentController; import li.strolch.agent.impl.ComponentDependencyAnalyzer; +import li.strolch.utils.dbc.DBC.DbcException; @SuppressWarnings("nls") public class ControllerDependencyTest { diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/query/enums/EnumHandlerTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/query/enums/EnumHandlerTest.java index 22aa63d94..fca2a2e31 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/query/enums/EnumHandlerTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/query/enums/EnumHandlerTest.java @@ -23,10 +23,10 @@ import java.util.Locale; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.RuntimeMock; import li.strolch.agent.ComponentContainerTest; import li.strolch.agent.api.ComponentContainer; +import li.strolch.privilege.model.Certificate; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/AuditQueryTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/AuditQueryTest.java index 54d47e590..09366d006 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/AuditQueryTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/AuditQueryTest.java @@ -34,13 +34,12 @@ import li.strolch.model.audit.AuditQuery; import li.strolch.model.audit.AuditVisitor; import li.strolch.model.audit.NoStrategyAuditVisitor; import li.strolch.persistence.inmemory.InMemoryAuditDao; +import li.strolch.utils.StringMatchMode; +import li.strolch.utils.collections.DateRange; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.utils.StringMatchMode; -import ch.eitchnet.utils.collections.DateRange; - /** * @author Robert von Burg */ diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/FindByLocatorTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/FindByLocatorTest.java index 88888ea64..45bc66957 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/FindByLocatorTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/FindByLocatorTest.java @@ -19,7 +19,6 @@ import static org.junit.Assert.assertNotNull; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.RuntimeMock; import li.strolch.agent.ComponentContainerTest; import li.strolch.agent.api.ComponentContainer; @@ -32,6 +31,7 @@ import li.strolch.model.parameter.FloatParameter; import li.strolch.model.parameter.StringParameter; import li.strolch.model.timedstate.IntegerTimedState; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; /** diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryQueryTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryQueryTest.java index 75c899b63..3dbca950a 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryQueryTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/InMemoryQueryTest.java @@ -15,8 +15,6 @@ */ package li.strolch.runtime.query.inmemory; -import static ch.eitchnet.utils.StringMatchMode.ci; -import static ch.eitchnet.utils.StringMatchMode.es; import static li.strolch.model.query.ParameterSelection.booleanSelection; import static li.strolch.model.query.ParameterSelection.floatListSelection; import static li.strolch.model.query.ParameterSelection.floatSelection; @@ -24,6 +22,8 @@ import static li.strolch.model.query.ParameterSelection.integerListSelection; import static li.strolch.model.query.ParameterSelection.longListSelection; import static li.strolch.model.query.ParameterSelection.stringListSelection; import static li.strolch.model.query.ParameterSelection.stringSelection; +import static li.strolch.utils.StringMatchMode.ci; +import static li.strolch.utils.StringMatchMode.es; import static org.junit.Assert.assertEquals; import java.util.ArrayList; diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/QueryTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/QueryTest.java index 8a9c7f4f5..9b2994b1b 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/QueryTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/query/inmemory/QueryTest.java @@ -26,8 +26,6 @@ import java.util.List; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.StringMatchMode; import li.strolch.RuntimeMock; import li.strolch.agent.ComponentContainerTest; import li.strolch.agent.api.ComponentContainer; @@ -40,7 +38,9 @@ import li.strolch.model.query.ParameterSelection; import li.strolch.model.query.ResourceQuery; import li.strolch.model.query.Selection; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.agent/src/test/resources/cachedtest/config/PrivilegeConfig.xml b/li.strolch.agent/src/test/resources/cachedtest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.agent/src/test/resources/cachedtest/config/PrivilegeConfig.xml +++ b/li.strolch.agent/src/test/resources/cachedtest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/cachedtest/config/PrivilegeRoles.xml b/li.strolch.agent/src/test/resources/cachedtest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.agent/src/test/resources/cachedtest/config/PrivilegeRoles.xml +++ b/li.strolch.agent/src/test/resources/cachedtest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.agent/src/test/resources/emptytest/config/PrivilegeConfig.xml b/li.strolch.agent/src/test/resources/emptytest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.agent/src/test/resources/emptytest/config/PrivilegeConfig.xml +++ b/li.strolch.agent/src/test/resources/emptytest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/emptytest/config/PrivilegeRoles.xml b/li.strolch.agent/src/test/resources/emptytest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.agent/src/test/resources/emptytest/config/PrivilegeRoles.xml +++ b/li.strolch.agent/src/test/resources/emptytest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.agent/src/test/resources/log4j.xml b/li.strolch.agent/src/test/resources/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.agent/src/test/resources/log4j.xml +++ b/li.strolch.agent/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml b/li.strolch.agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml +++ b/li.strolch.agent/src/test/resources/minimaltest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/minimaltest/config/PrivilegeRoles.xml b/li.strolch.agent/src/test/resources/minimaltest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.agent/src/test/resources/minimaltest/config/PrivilegeRoles.xml +++ b/li.strolch.agent/src/test/resources/minimaltest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.agent/src/test/resources/realmtest/config/PrivilegeConfig.xml b/li.strolch.agent/src/test/resources/realmtest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.agent/src/test/resources/realmtest/config/PrivilegeConfig.xml +++ b/li.strolch.agent/src/test/resources/realmtest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/realmtest/config/PrivilegeRoles.xml b/li.strolch.agent/src/test/resources/realmtest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.agent/src/test/resources/realmtest/config/PrivilegeRoles.xml +++ b/li.strolch.agent/src/test/resources/realmtest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.agent/src/test/resources/transactionaltest/config/PrivilegeConfig.xml b/li.strolch.agent/src/test/resources/transactionaltest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.agent/src/test/resources/transactionaltest/config/PrivilegeConfig.xml +++ b/li.strolch.agent/src/test/resources/transactionaltest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/transactionaltest/config/PrivilegeRoles.xml b/li.strolch.agent/src/test/resources/transactionaltest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.agent/src/test/resources/transactionaltest/config/PrivilegeRoles.xml +++ b/li.strolch.agent/src/test/resources/transactionaltest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.agent/src/test/resources/transienttest/config/PrivilegeConfig.xml b/li.strolch.agent/src/test/resources/transienttest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.agent/src/test/resources/transienttest/config/PrivilegeConfig.xml +++ b/li.strolch.agent/src/test/resources/transienttest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.agent/src/test/resources/transienttest/config/PrivilegeRoles.xml b/li.strolch.agent/src/test/resources/transienttest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.agent/src/test/resources/transienttest/config/PrivilegeRoles.xml +++ b/li.strolch.agent/src/test/resources/transienttest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.model/src/main/java/li/strolch/exception/StrolchAccessDeniedException.java b/li.strolch.model/src/main/java/li/strolch/exception/StrolchAccessDeniedException.java index 6f882c4c0..927d63aae 100644 --- a/li.strolch.model/src/main/java/li/strolch/exception/StrolchAccessDeniedException.java +++ b/li.strolch.model/src/main/java/li/strolch/exception/StrolchAccessDeniedException.java @@ -15,8 +15,8 @@ */ package li.strolch.exception; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.Restrictable; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.Restrictable; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/AbstractStrolchElement.java b/li.strolch.model/src/main/java/li/strolch/model/AbstractStrolchElement.java index 78da13090..7d7a149ca 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/AbstractStrolchElement.java +++ b/li.strolch.model/src/main/java/li/strolch/model/AbstractStrolchElement.java @@ -19,7 +19,7 @@ import java.text.MessageFormat; import li.strolch.exception.StrolchException; import li.strolch.model.Locator.LocatorBuilder; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/GroupedParameterizedElement.java b/li.strolch.model/src/main/java/li/strolch/model/GroupedParameterizedElement.java index f6dba5214..4b88df156 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/GroupedParameterizedElement.java +++ b/li.strolch.model/src/main/java/li/strolch/model/GroupedParameterizedElement.java @@ -22,10 +22,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.exception.StrolchException; import li.strolch.exception.StrolchModelException; import li.strolch.model.parameter.Parameter; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/Locator.java b/li.strolch.model/src/main/java/li/strolch/model/Locator.java index a1447d61f..89d0dada4 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/Locator.java +++ b/li.strolch.model/src/main/java/li/strolch/model/Locator.java @@ -22,7 +22,7 @@ import java.util.Iterator; import java.util.List; import li.strolch.exception.StrolchException; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** *

        diff --git a/li.strolch.model/src/main/java/li/strolch/model/ModelGenerator.java b/li.strolch.model/src/main/java/li/strolch/model/ModelGenerator.java index 432eddae8..765c3b6b7 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/ModelGenerator.java +++ b/li.strolch.model/src/main/java/li/strolch/model/ModelGenerator.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.Random; import java.util.Set; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.model.activity.Action; import li.strolch.model.activity.Activity; import li.strolch.model.audit.AccessType; @@ -55,6 +53,8 @@ import li.strolch.model.timevalue.impl.FloatValue; import li.strolch.model.timevalue.impl.IntegerValue; import li.strolch.model.timevalue.impl.StringSetValue; import li.strolch.model.timevalue.impl.ValueChange; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * Class which can be used to generate objects which implement {@link StrolchElement}. These generated classes can then diff --git a/li.strolch.model/src/main/java/li/strolch/model/ModelStatistics.java b/li.strolch.model/src/main/java/li/strolch/model/ModelStatistics.java index 30c21f1fa..66a7d7f24 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/ModelStatistics.java +++ b/li.strolch.model/src/main/java/li/strolch/model/ModelStatistics.java @@ -15,7 +15,7 @@ */ package li.strolch.model; -import static ch.eitchnet.utils.helper.StringHelper.NULL; +import static li.strolch.utils.helper.StringHelper.NULL; import java.util.Date; @@ -26,8 +26,8 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import li.strolch.model.xml.Iso8601DateAdapter; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/Order.java b/li.strolch.model/src/main/java/li/strolch/model/Order.java index d8fccb53c..07fddcb6e 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/Order.java +++ b/li.strolch.model/src/main/java/li/strolch/model/Order.java @@ -17,11 +17,11 @@ package li.strolch.model; import java.util.Date; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.exception.StrolchPolicyException; import li.strolch.model.Locator.LocatorBuilder; import li.strolch.model.policy.PolicyDefs; import li.strolch.model.visitor.StrolchRootElementVisitor; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * The Order is an object used in the EDF to transfer data from one range to another. Orders are not to be thought of as diff --git a/li.strolch.model/src/main/java/li/strolch/model/ParameterizedElement.java b/li.strolch.model/src/main/java/li/strolch/model/ParameterizedElement.java index edcef9f18..3a3eb7d77 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/ParameterizedElement.java +++ b/li.strolch.model/src/main/java/li/strolch/model/ParameterizedElement.java @@ -24,10 +24,10 @@ import java.util.List; import java.util.Map; import java.util.Set; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.exception.StrolchException; import li.strolch.model.Locator.LocatorBuilder; import li.strolch.model.parameter.Parameter; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/activity/Activity.java b/li.strolch.model/src/main/java/li/strolch/model/activity/Activity.java index f02aabce4..d4118c4eb 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/activity/Activity.java +++ b/li.strolch.model/src/main/java/li/strolch/model/activity/Activity.java @@ -21,7 +21,6 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.exception.StrolchException; import li.strolch.exception.StrolchPolicyException; import li.strolch.model.GroupedParameterizedElement; @@ -34,6 +33,7 @@ import li.strolch.model.StrolchRootElement; import li.strolch.model.Tags; import li.strolch.model.policy.PolicyDefs; import li.strolch.model.visitor.StrolchRootElementVisitor; +import li.strolch.utils.dbc.DBC; /** * Parameterized object grouping a collection of {@link Activity} and {@link Action} objects defining the process to be diff --git a/li.strolch.model/src/main/java/li/strolch/model/audit/ActionSelection.java b/li.strolch.model/src/main/java/li/strolch/model/audit/ActionSelection.java index 60c6ed1ca..68abb4fd7 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/audit/ActionSelection.java +++ b/li.strolch.model/src/main/java/li/strolch/model/audit/ActionSelection.java @@ -16,7 +16,7 @@ package li.strolch.model.audit; import li.strolch.model.query.StringSelection; -import ch.eitchnet.utils.StringMatchMode; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/audit/AuditFromDomReader.java b/li.strolch.model/src/main/java/li/strolch/model/audit/AuditFromDomReader.java index 44e886307..7e8c9afca 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/audit/AuditFromDomReader.java +++ b/li.strolch.model/src/main/java/li/strolch/model/audit/AuditFromDomReader.java @@ -18,14 +18,13 @@ package li.strolch.model.audit; import java.text.MessageFormat; import li.strolch.model.Tags; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.iso8601.ISO8601FormatFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; - /** * @author Robert von Burg */ diff --git a/li.strolch.model/src/main/java/li/strolch/model/audit/AuditQuery.java b/li.strolch.model/src/main/java/li/strolch/model/audit/AuditQuery.java index cf547e48b..0b08a1ddb 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/audit/AuditQuery.java +++ b/li.strolch.model/src/main/java/li/strolch/model/audit/AuditQuery.java @@ -19,8 +19,8 @@ import java.util.ArrayList; import java.util.List; import li.strolch.model.query.StrolchQuery; -import ch.eitchnet.utils.collections.DateRange; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.collections.DateRange; +import li.strolch.utils.dbc.DBC; /** * @@ -103,7 +103,7 @@ public class AuditQuery implements StrolchQuery { } /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeName() */ @Override public String getPrivilegeName() { @@ -111,7 +111,7 @@ public class AuditQuery implements StrolchQuery { } /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeValue() */ @Override public Object getPrivilegeValue() { diff --git a/li.strolch.model/src/main/java/li/strolch/model/audit/AuditToDomVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/audit/AuditToDomVisitor.java index 91f43753e..4ef48fef8 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/audit/AuditToDomVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/audit/AuditToDomVisitor.java @@ -18,13 +18,12 @@ package li.strolch.model.audit; import javax.xml.parsers.DocumentBuilder; import li.strolch.model.Tags; +import li.strolch.utils.helper.DomUtil; +import li.strolch.utils.iso8601.ISO8601FormatFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.utils.helper.DomUtil; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; - /** * @author Robert von Burg */ diff --git a/li.strolch.model/src/main/java/li/strolch/model/audit/ElementSelection.java b/li.strolch.model/src/main/java/li/strolch/model/audit/ElementSelection.java index b24014aaa..19023114a 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/audit/ElementSelection.java +++ b/li.strolch.model/src/main/java/li/strolch/model/audit/ElementSelection.java @@ -16,7 +16,7 @@ package li.strolch.model.audit; import li.strolch.model.query.StringSelection; -import ch.eitchnet.utils.StringMatchMode; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/audit/IdentitySelection.java b/li.strolch.model/src/main/java/li/strolch/model/audit/IdentitySelection.java index 0c6e88689..66c0656e7 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/audit/IdentitySelection.java +++ b/li.strolch.model/src/main/java/li/strolch/model/audit/IdentitySelection.java @@ -16,7 +16,7 @@ package li.strolch.model.audit; import li.strolch.model.query.StringSelection; -import ch.eitchnet.utils.StringMatchMode; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/json/StrolchElementToJsonVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/json/StrolchElementToJsonVisitor.java index d28367ec5..fa2519bee 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/json/StrolchElementToJsonVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/json/StrolchElementToJsonVisitor.java @@ -7,7 +7,6 @@ import java.util.SortedSet; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.model.AbstractStrolchElement; import li.strolch.model.GroupedParameterizedElement; import li.strolch.model.Order; @@ -23,6 +22,7 @@ import li.strolch.model.policy.PolicyDefs; import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.ITimeValue; import li.strolch.model.timevalue.IValue; +import li.strolch.utils.iso8601.ISO8601FormatFactory; public class StrolchElementToJsonVisitor { diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/AbstractParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/AbstractParameter.java index 72f07740e..8a6f68025 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/AbstractParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/AbstractParameter.java @@ -27,7 +27,7 @@ import li.strolch.model.Locator.LocatorBuilder; import li.strolch.model.ParameterizedElement; import li.strolch.model.StrolchRootElement; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/BooleanParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/BooleanParameter.java index 78b8d406c..2015a9821 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/BooleanParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/BooleanParameter.java @@ -17,8 +17,8 @@ package li.strolch.model.parameter; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/DateParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/DateParameter.java index 1106674e6..f99048aa6 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/DateParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/DateParameter.java @@ -19,8 +19,8 @@ import java.util.Date; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/DurationParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/DurationParameter.java index 5ff87199d..3f60d7a60 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/DurationParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/DurationParameter.java @@ -17,8 +17,8 @@ package li.strolch.model.parameter; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/FloatListParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/FloatListParameter.java index 3248224aa..cb641a5e6 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/FloatListParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/FloatListParameter.java @@ -22,8 +22,8 @@ import java.util.List; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/FloatParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/FloatParameter.java index 74a86ddeb..b4efa6b25 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/FloatParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/FloatParameter.java @@ -15,9 +15,9 @@ */ package li.strolch.model.parameter; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/IntegerListParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/IntegerListParameter.java index 32d2b9010..da510ed81 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/IntegerListParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/IntegerListParameter.java @@ -22,8 +22,8 @@ import java.util.List; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/IntegerParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/IntegerParameter.java index ee5321e4a..b1e17ca62 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/IntegerParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/IntegerParameter.java @@ -15,9 +15,9 @@ */ package li.strolch.model.parameter; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/LongListParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/LongListParameter.java index b61da8c34..b3f55ad92 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/LongListParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/LongListParameter.java @@ -22,8 +22,8 @@ import java.util.List; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/LongParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/LongParameter.java index 08491e178..f6d876155 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/LongParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/LongParameter.java @@ -15,9 +15,9 @@ */ package li.strolch.model.parameter; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/StringListParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/StringListParameter.java index 53e3180bd..8ef1685af 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/StringListParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/StringListParameter.java @@ -22,8 +22,8 @@ import java.util.List; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/parameter/StringParameter.java b/li.strolch.model/src/main/java/li/strolch/model/parameter/StringParameter.java index a10522e81..516ed4597 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/parameter/StringParameter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/parameter/StringParameter.java @@ -17,7 +17,7 @@ package li.strolch.model.parameter; import li.strolch.model.StrolchValueType; import li.strolch.model.visitor.ParameterVisitor; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/policy/PolicyDefs.java b/li.strolch.model/src/main/java/li/strolch/model/policy/PolicyDefs.java index 2576b4a0c..b3e6e372a 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/policy/PolicyDefs.java +++ b/li.strolch.model/src/main/java/li/strolch/model/policy/PolicyDefs.java @@ -19,10 +19,10 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.exception.StrolchPolicyException; import li.strolch.model.StrolchElement; import li.strolch.model.StrolchRootElement; +import li.strolch.utils.dbc.DBC; /** * The {@link PolicyDefs} contains the policy configuration of any {@link StrolchRootElement} which requires policies diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/ActivityQuery.java b/li.strolch.model/src/main/java/li/strolch/model/query/ActivityQuery.java index bd19935d7..3da10d7b9 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/ActivityQuery.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/ActivityQuery.java @@ -15,12 +15,12 @@ */ package li.strolch.model.query; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.model.ActivityVisitor; import li.strolch.model.activity.Action; import li.strolch.model.activity.Activity; import li.strolch.model.query.ordering.StrolchQueryOrdering; import li.strolch.model.visitor.NoStrategyActivityVisitor; +import li.strolch.utils.dbc.DBC; /** *

        diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/DateSelection.java b/li.strolch.model/src/main/java/li/strolch/model/query/DateSelection.java index ea3ef2645..8531e9522 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/DateSelection.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/DateSelection.java @@ -17,7 +17,7 @@ package li.strolch.model.query; import java.util.Date; -import ch.eitchnet.utils.collections.DateRange; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/IdSelection.java b/li.strolch.model/src/main/java/li/strolch/model/query/IdSelection.java index f4adc9cad..c65beaf43 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/IdSelection.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/IdSelection.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import ch.eitchnet.utils.StringMatchMode; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/NameSelection.java b/li.strolch.model/src/main/java/li/strolch/model/query/NameSelection.java index 9b618563d..41ce77a0e 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/NameSelection.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/NameSelection.java @@ -15,7 +15,7 @@ */ package li.strolch.model.query; -import ch.eitchnet.utils.StringMatchMode; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/OrderQuery.java b/li.strolch.model/src/main/java/li/strolch/model/query/OrderQuery.java index c7400c971..1bece9361 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/OrderQuery.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/OrderQuery.java @@ -15,12 +15,12 @@ */ package li.strolch.model.query; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.model.Order; import li.strolch.model.OrderVisitor; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.ordering.StrolchQueryOrdering; import li.strolch.model.visitor.NoStrategyOrderVisitor; +import li.strolch.utils.dbc.DBC; /** *

        diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/ParameterSelection.java b/li.strolch.model/src/main/java/li/strolch/model/query/ParameterSelection.java index 84fec6049..4c1fc64a3 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/ParameterSelection.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/ParameterSelection.java @@ -18,10 +18,10 @@ package li.strolch.model.query; import java.util.Date; import java.util.List; -import ch.eitchnet.utils.StringMatchMode; -import ch.eitchnet.utils.collections.DateRange; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +import li.strolch.utils.StringMatchMode; +import li.strolch.utils.collections.DateRange; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/ResourceQuery.java b/li.strolch.model/src/main/java/li/strolch/model/query/ResourceQuery.java index c57a9d609..a42e3fd05 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/ResourceQuery.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/ResourceQuery.java @@ -15,12 +15,12 @@ */ package li.strolch.model.query; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.model.Resource; import li.strolch.model.ResourceVisitor; import li.strolch.model.parameter.Parameter; import li.strolch.model.query.ordering.StrolchQueryOrdering; import li.strolch.model.visitor.NoStrategyResourceVisitor; +import li.strolch.utils.dbc.DBC; /** *

        diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/StringSelection.java b/li.strolch.model/src/main/java/li/strolch/model/query/StringSelection.java index f5fa5d39a..8fa88be1b 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/StringSelection.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/StringSelection.java @@ -15,7 +15,7 @@ */ package li.strolch.model.query; -import ch.eitchnet.utils.StringMatchMode; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/StrolchElementQuery.java b/li.strolch.model/src/main/java/li/strolch/model/query/StrolchElementQuery.java index 0537d4b93..a86f8cee3 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/StrolchElementQuery.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/StrolchElementQuery.java @@ -15,7 +15,7 @@ */ package li.strolch.model.query; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg @@ -98,7 +98,7 @@ public abstract class StrolchElementQuery implements Str } /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeName() */ @Override public String getPrivilegeName() { @@ -106,7 +106,7 @@ public abstract class StrolchElementQuery implements Str } /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeValue() */ @Override public Object getPrivilegeValue() { diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/StrolchQuery.java b/li.strolch.model/src/main/java/li/strolch/model/query/StrolchQuery.java index 018e3b9e4..de1d56cf5 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/StrolchQuery.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/StrolchQuery.java @@ -15,7 +15,7 @@ */ package li.strolch.model.query; -import ch.eitchnet.privilege.model.Restrictable; +import li.strolch.privilege.model.Restrictable; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/query/StrolchTypeNavigation.java b/li.strolch.model/src/main/java/li/strolch/model/query/StrolchTypeNavigation.java index 026209047..66a7d8340 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/query/StrolchTypeNavigation.java +++ b/li.strolch.model/src/main/java/li/strolch/model/query/StrolchTypeNavigation.java @@ -15,7 +15,7 @@ */ package li.strolch.model.query; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/timedstate/AbstractStrolchTimedState.java b/li.strolch.model/src/main/java/li/strolch/model/timedstate/AbstractStrolchTimedState.java index ca27e5365..4f65d5fc6 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/timedstate/AbstractStrolchTimedState.java +++ b/li.strolch.model/src/main/java/li/strolch/model/timedstate/AbstractStrolchTimedState.java @@ -29,7 +29,7 @@ import li.strolch.model.timevalue.ITimeVariable; import li.strolch.model.timevalue.IValue; import li.strolch.model.timevalue.IValueChange; import li.strolch.model.visitor.TimedStateVisitor; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * Wrapper for a {@link IntegerTimedState} diff --git a/li.strolch.model/src/main/java/li/strolch/model/timevalue/impl/AString.java b/li.strolch.model/src/main/java/li/strolch/model/timevalue/impl/AString.java index 3f8e34b6a..27f988192 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/timevalue/impl/AString.java +++ b/li.strolch.model/src/main/java/li/strolch/model/timevalue/impl/AString.java @@ -17,7 +17,7 @@ package li.strolch.model.timevalue.impl; import java.io.Serializable; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * Wrapper for java.util.String object defining a inverse to support algebraic operations. diff --git a/li.strolch.model/src/main/java/li/strolch/model/timevalue/impl/StringSetValue.java b/li.strolch.model/src/main/java/li/strolch/model/timevalue/impl/StringSetValue.java index da42f9b7e..9d3057c92 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/timevalue/impl/StringSetValue.java +++ b/li.strolch.model/src/main/java/li/strolch/model/timevalue/impl/StringSetValue.java @@ -21,12 +21,12 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.exception.StrolchException; import li.strolch.model.StrolchValueType; import li.strolch.model.timevalue.ITimeValue; import li.strolch.model.timevalue.IValue; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * {@link IValue} implementation to work with String valued {@link ITimeValue} objects. Since a java.util.String object diff --git a/li.strolch.model/src/main/java/li/strolch/model/visitor/StrolchElementDeepEqualsVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/visitor/StrolchElementDeepEqualsVisitor.java index a9812e9bb..eea7cdfcd 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/visitor/StrolchElementDeepEqualsVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/visitor/StrolchElementDeepEqualsVisitor.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map.Entry; import java.util.Set; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.model.GroupedParameterizedElement; import li.strolch.model.Locator; import li.strolch.model.Order; @@ -36,6 +35,7 @@ import li.strolch.model.policy.PolicyDef; import li.strolch.model.policy.PolicyDefs; import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.ITimeVariable; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/ActivityToDomVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/ActivityToDomVisitor.java index 293945e2a..b4e40a211 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/ActivityToDomVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/ActivityToDomVisitor.java @@ -20,10 +20,10 @@ import javax.xml.parsers.DocumentBuilder; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.utils.helper.DomUtil; import li.strolch.model.ActivityVisitor; import li.strolch.model.activity.Action; import li.strolch.model.activity.Activity; +import li.strolch.utils.helper.DomUtil; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/Iso8601DateAdapter.java b/li.strolch.model/src/main/java/li/strolch/model/xml/Iso8601DateAdapter.java index dbfb38c22..852b319c0 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/Iso8601DateAdapter.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/Iso8601DateAdapter.java @@ -24,7 +24,7 @@ import javax.xml.bind.annotation.adapters.XmlAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; public class Iso8601DateAdapter extends XmlAdapter { diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/OrderToDomVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/OrderToDomVisitor.java index 07dc7829b..643168e60 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/OrderToDomVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/OrderToDomVisitor.java @@ -19,12 +19,11 @@ import javax.xml.parsers.DocumentBuilder; import li.strolch.model.Order; import li.strolch.model.OrderVisitor; +import li.strolch.utils.helper.DomUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.utils.helper.DomUtil; - /** * @author Robert von Burg */ diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/OrderToXmlStringVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/OrderToXmlStringVisitor.java index bd4c5d1f7..cd9c0fbed 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/OrderToXmlStringVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/OrderToXmlStringVisitor.java @@ -25,7 +25,7 @@ import javax.xml.stream.XMLStreamWriter; import li.strolch.model.Order; import li.strolch.model.OrderVisitor; import li.strolch.model.StrolchModelConstants; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/ResourceToDomVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/ResourceToDomVisitor.java index e783bcfc1..4971650d9 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/ResourceToDomVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/ResourceToDomVisitor.java @@ -19,12 +19,11 @@ import javax.xml.parsers.DocumentBuilder; import li.strolch.model.Resource; import li.strolch.model.ResourceVisitor; +import li.strolch.utils.helper.DomUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.utils.helper.DomUtil; - /** * @author Robert von Burg */ diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/ResourceToXmlStringVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/ResourceToXmlStringVisitor.java index 0b520e3c8..70ed72726 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/ResourceToXmlStringVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/ResourceToXmlStringVisitor.java @@ -20,11 +20,11 @@ import java.io.StringWriter; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; -import ch.eitchnet.utils.dbc.DBC; import javanet.staxutils.IndentingXMLStreamWriter; import li.strolch.model.Resource; import li.strolch.model.ResourceVisitor; import li.strolch.model.StrolchModelConstants; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementFromDomVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementFromDomVisitor.java index 4811dfad3..195ff2adc 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementFromDomVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementFromDomVisitor.java @@ -22,9 +22,6 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.exception.StrolchException; import li.strolch.model.AbstractStrolchElement; import li.strolch.model.GroupedParameterizedElement; @@ -43,6 +40,9 @@ import li.strolch.model.policy.PolicyDefs; import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.IValue; import li.strolch.model.timevalue.impl.ValueChange; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToDomVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToDomVisitor.java index bf0d5b5f6..d930e5cd8 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToDomVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToDomVisitor.java @@ -22,7 +22,6 @@ import java.util.SortedSet; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.model.AbstractStrolchElement; import li.strolch.model.GroupedParameterizedElement; import li.strolch.model.Order; @@ -41,6 +40,7 @@ import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.ITimeValue; import li.strolch.model.timevalue.IValue; import li.strolch.model.timevalue.IValueChange; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToSaxVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToSaxVisitor.java index afdc5d313..215aec401 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToSaxVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToSaxVisitor.java @@ -28,7 +28,6 @@ import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.model.GroupedParameterizedElement; import li.strolch.model.Order; import li.strolch.model.ParameterBag; @@ -45,6 +44,7 @@ import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.ITimeValue; import li.strolch.model.timevalue.IValue; import li.strolch.model.timevalue.IValueChange; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToSaxWriterVisitor.java b/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToSaxWriterVisitor.java index 3dfd042f7..4b96a443d 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToSaxWriterVisitor.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/StrolchElementToSaxWriterVisitor.java @@ -30,7 +30,6 @@ import java.util.TreeSet; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.model.GroupedParameterizedElement; import li.strolch.model.Order; import li.strolch.model.ParameterBag; @@ -49,6 +48,7 @@ import li.strolch.model.timevalue.ITimeValue; import li.strolch.model.timevalue.ITimeVariable; import li.strolch.model.timevalue.IValue; import li.strolch.model.timevalue.IValueChange; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxFileReader.java b/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxFileReader.java index 1c2d9be32..4dc507655 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxFileReader.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxFileReader.java @@ -24,12 +24,11 @@ import javax.xml.parsers.SAXParserFactory; import li.strolch.exception.StrolchException; import li.strolch.model.Tags; +import li.strolch.utils.helper.StringHelper; import org.xml.sax.Attributes; import org.xml.sax.SAXException; -import ch.eitchnet.utils.helper.StringHelper; - /** * @author Robert von Burg */ diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxReader.java b/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxReader.java index e89feb89d..5ed039af3 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxReader.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxReader.java @@ -26,8 +26,6 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.exception.StrolchException; import li.strolch.exception.StrolchPolicyException; import li.strolch.model.GroupedParameterizedElement; @@ -46,6 +44,8 @@ import li.strolch.model.policy.PolicyDefs; import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.IValue; import li.strolch.model.timevalue.impl.ValueChange; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxStreamReader.java b/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxStreamReader.java index 6c7089b03..f1febea36 100644 --- a/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxStreamReader.java +++ b/li.strolch.model/src/main/java/li/strolch/model/xml/XmlModelSaxStreamReader.java @@ -26,12 +26,11 @@ import javax.xml.parsers.SAXParserFactory; import li.strolch.exception.StrolchException; import li.strolch.model.Tags; +import li.strolch.utils.helper.StringHelper; import org.xml.sax.Attributes; import org.xml.sax.SAXException; -import ch.eitchnet.utils.helper.StringHelper; - /** * @author Robert von Burg */ diff --git a/li.strolch.model/src/test/java/li/strolch/model/XmlModelDefaultHandlerTest.java b/li.strolch.model/src/test/java/li/strolch/model/XmlModelDefaultHandlerTest.java index 2d698523f..91c996474 100644 --- a/li.strolch.model/src/test/java/li/strolch/model/XmlModelDefaultHandlerTest.java +++ b/li.strolch.model/src/test/java/li/strolch/model/XmlModelDefaultHandlerTest.java @@ -24,13 +24,12 @@ import java.util.Map; import li.strolch.model.activity.Activity; import li.strolch.model.xml.StrolchElementListener; import li.strolch.model.xml.XmlModelSaxFileReader; +import li.strolch.utils.helper.StringHelper; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.StringHelper; - /** * @author Robert von Burg */ diff --git a/li.strolch.model/src/test/resources/log4j.xml b/li.strolch.model/src/test/resources/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.model/src/test/resources/log4j.xml +++ b/li.strolch.model/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.performancetest/src/main/resources/log4j.xml b/li.strolch.performancetest/src/main/resources/log4j.xml index db9a7424c..983562023 100644 --- a/li.strolch.performancetest/src/main/resources/log4j.xml +++ b/li.strolch.performancetest/src/main/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.performancetest/src/runtime_postgresql/config/PrivilegeConfig.xml b/li.strolch.performancetest/src/runtime_postgresql/config/PrivilegeConfig.xml index 706602750..aaf42b2c6 100644 --- a/li.strolch.performancetest/src/runtime_postgresql/config/PrivilegeConfig.xml +++ b/li.strolch.performancetest/src/runtime_postgresql/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,7 +24,7 @@ - + \ No newline at end of file diff --git a/li.strolch.performancetest/src/runtime_postgresql/config/PrivilegeRoles.xml b/li.strolch.performancetest/src/runtime_postgresql/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.performancetest/src/runtime_postgresql/config/PrivilegeRoles.xml +++ b/li.strolch.performancetest/src/runtime_postgresql/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.performancetest/src/runtime_xml/config/PrivilegeConfig.xml b/li.strolch.performancetest/src/runtime_xml/config/PrivilegeConfig.xml index 706602750..aaf42b2c6 100644 --- a/li.strolch.performancetest/src/runtime_xml/config/PrivilegeConfig.xml +++ b/li.strolch.performancetest/src/runtime_xml/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,7 +24,7 @@ - + \ No newline at end of file diff --git a/li.strolch.performancetest/src/runtime_xml/config/PrivilegeRoles.xml b/li.strolch.performancetest/src/runtime_xml/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.performancetest/src/runtime_xml/config/PrivilegeRoles.xml +++ b/li.strolch.performancetest/src/runtime_xml/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformancePostgreSqlTest.java b/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformancePostgreSqlTest.java index dfa33bea3..cf202f94e 100644 --- a/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformancePostgreSqlTest.java +++ b/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformancePostgreSqlTest.java @@ -25,11 +25,11 @@ import org.junit.BeforeClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.db.DbSchemaVersionCheck; -import ch.eitchnet.utils.Version; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.db.DbSchemaVersionCheck; import li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler; import li.strolch.testbase.runtime.RuntimeMock; +import li.strolch.utils.Version; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformanceTest.java b/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformanceTest.java index 5b2d774f2..7f9c1bfa5 100644 --- a/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformanceTest.java +++ b/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformanceTest.java @@ -2,7 +2,7 @@ package li.strolch.performance; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; +import li.strolch.privilege.model.Certificate; import li.strolch.service.api.ServiceHandler; import li.strolch.testbase.runtime.RuntimeMock; diff --git a/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformanceTestService.java b/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformanceTestService.java index df0dc86e1..3126c48aa 100644 --- a/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformanceTestService.java +++ b/li.strolch.performancetest/src/test/java/li/strolch/performance/PerformanceTestService.java @@ -17,7 +17,6 @@ package li.strolch.performance; import java.util.concurrent.TimeUnit; -import ch.eitchnet.utils.helper.SystemHelper; import li.strolch.agent.api.StrolchAgent; import li.strolch.command.AddResourceCommand; import li.strolch.command.RemoveResourceCommand; @@ -26,6 +25,7 @@ import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceResult; +import li.strolch.utils.helper.SystemHelper; public class PerformanceTestService extends AbstractService { diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditDao.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditDao.java index 93094c74a..ae51ea24f 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditDao.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditDao.java @@ -31,8 +31,8 @@ import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditQuery; import li.strolch.persistence.api.AuditDao; import li.strolch.persistence.api.StrolchPersistenceException; -import ch.eitchnet.utils.collections.DateRange; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.collections.DateRange; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditQueryVisitor.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditQueryVisitor.java index f3be5d0df..a69386e29 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditQueryVisitor.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlAuditQueryVisitor.java @@ -27,7 +27,7 @@ import li.strolch.model.audit.AuditQueryVisitor; import li.strolch.model.audit.ElementSelection; import li.strolch.model.audit.IdentitySelection; import li.strolch.model.query.StringSelection; -import ch.eitchnet.utils.StringMatchMode; +import li.strolch.utils.StringMatchMode; /** * @author Robert von Burg diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlDbConnectionBuilder.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlDbConnectionBuilder.java index 4ff8232f7..095b6ae84 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlDbConnectionBuilder.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlDbConnectionBuilder.java @@ -27,10 +27,10 @@ import javax.sql.DataSource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.agent.api.ComponentContainer; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.DbConnectionBuilder; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlDbInitializer.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlDbInitializer.java index b472d4261..5a7903850 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlDbInitializer.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlDbInitializer.java @@ -3,9 +3,9 @@ */ package li.strolch.persistence.postgresql; -import static ch.eitchnet.db.DbConstants.PROP_ALLOW_SCHEMA_CREATION; -import static ch.eitchnet.db.DbConstants.PROP_ALLOW_SCHEMA_DROP; -import static ch.eitchnet.db.DbConstants.PROP_ALLOW_SCHEMA_MIGRATION; +import static li.strolch.db.DbConstants.PROP_ALLOW_SCHEMA_CREATION; +import static li.strolch.db.DbConstants.PROP_ALLOW_SCHEMA_DROP; +import static li.strolch.db.DbConstants.PROP_ALLOW_SCHEMA_MIGRATION; import java.util.Map; import java.util.Map.Entry; @@ -14,16 +14,16 @@ import javax.sql.DataSource; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchRealm; +import li.strolch.db.DbConnectionCheck; +import li.strolch.db.DbException; +import li.strolch.db.DbMigrationState; +import li.strolch.db.DbSchemaVersionCheck; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.RuntimeConfiguration; import li.strolch.runtime.configuration.StrolchConfiguration; import li.strolch.runtime.configuration.StrolchConfigurationException; -import ch.eitchnet.db.DbConnectionCheck; -import ch.eitchnet.db.DbException; -import ch.eitchnet.db.DbMigrationState; -import ch.eitchnet.db.DbSchemaVersionCheck; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; public class PostgreSqlDbInitializer extends PostgreSqlInitializer { diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlHelper.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlHelper.java index b552d9e2d..36916fbea 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlHelper.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlHelper.java @@ -19,8 +19,8 @@ import java.sql.Date; import java.util.ArrayList; import java.util.List; -import ch.eitchnet.utils.StringMatchMode; -import ch.eitchnet.utils.collections.DateRange; +import li.strolch.utils.StringMatchMode; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlInitializer.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlInitializer.java index 8b351e95f..745bfd157 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlInitializer.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlInitializer.java @@ -24,9 +24,12 @@ import java.text.MessageFormat; import li.strolch.agent.api.RealmHandler; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.impl.StoreToDaoElementListener; +import li.strolch.db.DbMigrationState; import li.strolch.model.ModelStatistics; import li.strolch.model.xml.XmlModelSaxFileReader; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.RuntimeConfiguration; import li.strolch.runtime.configuration.StrolchConfiguration; @@ -34,10 +37,6 @@ import li.strolch.runtime.configuration.StrolchConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.db.DbMigrationState; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.Certificate; - /** * @author Robert von Burg */ diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java index 2a6a044e2..35f24d814 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlPersistenceHandler.java @@ -15,11 +15,11 @@ */ package li.strolch.persistence.postgresql; -import static ch.eitchnet.db.DbConstants.PROP_ALLOW_DATA_INIT_ON_SCHEMA_CREATE; -import static ch.eitchnet.db.DbConstants.PROP_ALLOW_SCHEMA_CREATION; -import static ch.eitchnet.db.DbConstants.PROP_ALLOW_SCHEMA_DROP; -import static ch.eitchnet.db.DbConstants.PROP_ALLOW_SCHEMA_MIGRATION; import static li.strolch.agent.api.RealmHandler.SYSTEM_USER_DB_INITIALIZER; +import static li.strolch.db.DbConstants.PROP_ALLOW_DATA_INIT_ON_SCHEMA_CREATE; +import static li.strolch.db.DbConstants.PROP_ALLOW_SCHEMA_CREATION; +import static li.strolch.db.DbConstants.PROP_ALLOW_SCHEMA_DROP; +import static li.strolch.db.DbConstants.PROP_ALLOW_SCHEMA_MIGRATION; import java.sql.Connection; import java.text.MessageFormat; @@ -30,14 +30,13 @@ import javax.sql.DataSource; import org.postgresql.Driver; -import ch.eitchnet.db.DbMigrationState; -import ch.eitchnet.db.DbSchemaVersionCheck; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.RealmHandler; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.api.StrolchRealm; +import li.strolch.db.DbMigrationState; +import li.strolch.db.DbSchemaVersionCheck; import li.strolch.persistence.api.ActivityDao; import li.strolch.persistence.api.AuditDao; import li.strolch.persistence.api.OrderDao; @@ -46,6 +45,7 @@ import li.strolch.persistence.api.ResourceDao; import li.strolch.persistence.api.StrolchPersistenceException; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.postgresql.PostgreSqlDbConnectionBuilder.StrolchPostgreDataSource; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.DbConnectionBuilder; import li.strolch.runtime.configuration.StrolchConfiguration; diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java index fe6b876ec..df24584b1 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlQueryVisitor.java @@ -23,10 +23,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import ch.eitchnet.utils.StringMatchMode; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.model.query.AndSelection; import li.strolch.model.query.IdSelection; import li.strolch.model.query.NameSelection; @@ -56,6 +52,10 @@ import li.strolch.model.query.ordering.OrderById; import li.strolch.model.query.ordering.OrderByName; import li.strolch.model.query.ordering.OrderByParameter; import li.strolch.model.query.ordering.StrolchQueryOrderingVisitor; +import li.strolch.utils.StringMatchMode; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlSchemaInitializer.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlSchemaInitializer.java index 93e7e56f1..2022ad195 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlSchemaInitializer.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlSchemaInitializer.java @@ -19,9 +19,9 @@ import java.util.Map; import java.util.Map.Entry; import li.strolch.agent.api.StrolchAgent; -import ch.eitchnet.db.DbMigrationState; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.db.DbMigrationState; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; /** * @author Robert von Burg diff --git a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java index 24c3d5b28..7b0519849 100644 --- a/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java +++ b/li.strolch.persistence.postgresql/src/main/java/li/strolch/persistence/postgresql/PostgreSqlStrolchTransaction.java @@ -25,13 +25,12 @@ import li.strolch.persistence.api.OrderDao; import li.strolch.persistence.api.PersistenceHandler; import li.strolch.persistence.api.ResourceDao; import li.strolch.persistence.api.TransactionResult; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; - public class PostgreSqlStrolchTransaction extends AbstractTransaction { private static final Logger logger = LoggerFactory.getLogger(PostgreSqlStrolchTransaction.class); diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/AuditQueryTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/AuditQueryTest.java index 19a0932b6..4cb0e019d 100644 --- a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/AuditQueryTest.java +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/AuditQueryTest.java @@ -43,9 +43,6 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.StringMatchMode; -import ch.eitchnet.utils.collections.DateRange; import li.strolch.agent.api.AuditTrail; import li.strolch.agent.api.StrolchRealm; import li.strolch.model.ModelGenerator; @@ -58,8 +55,11 @@ import li.strolch.model.audit.NoStrategyAuditVisitor; import li.strolch.persistence.api.AbstractTransaction; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.postgresql.PostgreSqlAuditQueryVisitor; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.testbase.runtime.RuntimeMock; +import li.strolch.utils.StringMatchMode; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/CachedDaoTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/CachedDaoTest.java index d8338283b..ece4d9b55 100644 --- a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/CachedDaoTest.java +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/CachedDaoTest.java @@ -26,12 +26,12 @@ import org.postgresql.Driver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.db.DbSchemaVersionCheck; -import ch.eitchnet.utils.Version; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.db.DbSchemaVersionCheck; import li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler; import li.strolch.testbase.runtime.AbstractModelTest; import li.strolch.testbase.runtime.RuntimeMock; +import li.strolch.utils.Version; +import li.strolch.utils.helper.StringHelper; public class CachedDaoTest extends AbstractModelTest { diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/DbMigrationTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/DbMigrationTest.java index 3ad34b7fa..b772b6535 100644 --- a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/DbMigrationTest.java +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/DbMigrationTest.java @@ -25,16 +25,15 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.text.MessageFormat; +import li.strolch.db.DbException; +import li.strolch.db.DbSchemaVersionCheck; import li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler; import li.strolch.runtime.StrolchConstants; +import li.strolch.utils.Version; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.db.DbException; -import ch.eitchnet.db.DbSchemaVersionCheck; -import ch.eitchnet.utils.Version; - /** * @author Robert von Burg */ diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java index 11e1df28e..dec3ee3be 100644 --- a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/ObserverUpdateTest.java @@ -38,6 +38,7 @@ import li.strolch.model.StrolchRootElement; import li.strolch.model.Tags; import li.strolch.persistence.api.ModificationResult; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.testbase.runtime.RuntimeMock; @@ -46,8 +47,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; - /** * @author Robert von Burg */ diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/QueryTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/QueryTest.java index 8d2a6ee9f..4ce3cebbf 100644 --- a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/QueryTest.java +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/QueryTest.java @@ -42,9 +42,6 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.StringMatchMode; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; import li.strolch.agent.api.OrderMap; import li.strolch.agent.api.ResourceMap; import li.strolch.agent.api.StrolchRealm; @@ -67,8 +64,11 @@ import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.postgresql.PostgreSqlOrderQueryVisitor; import li.strolch.persistence.postgresql.PostgreSqlQueryVisitor; import li.strolch.persistence.postgresql.PostgreSqlResourceQueryVisitor; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.testbase.runtime.RuntimeMock; +import li.strolch.utils.StringMatchMode; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/RealmTest.java b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/RealmTest.java index 77b6ca41e..da8613545 100644 --- a/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/RealmTest.java +++ b/li.strolch.persistence.postgresql/src/test/java/li/strolch/persistence/postgresql/dao/test/RealmTest.java @@ -26,12 +26,12 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.agent.api.StrolchRealm; import li.strolch.agent.impl.DataStoreMode; import li.strolch.model.ModelGenerator; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.testbase.runtime.AbstractModelTest; import li.strolch.testbase.runtime.RuntimeMock; diff --git a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeConfig.xml b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeConfig.xml +++ b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeRoles.xml b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeRoles.xml +++ b/li.strolch.persistence.postgresql/src/test/resources/cachedruntime/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.persistence.postgresql/src/test/resources/log4j.xml b/li.strolch.persistence.postgresql/src/test/resources/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.persistence.postgresql/src/test/resources/log4j.xml +++ b/li.strolch.persistence.postgresql/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml +++ b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeRoles.xml b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeRoles.xml +++ b/li.strolch.persistence.postgresql/src/test/resources/realmtest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml +++ b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeRoles.xml b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeRoles.xml +++ b/li.strolch.persistence.postgresql/src/test/resources/transactionalruntime/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/AbstractDao.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/AbstractDao.java index 932fa2e1a..711f83545 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/AbstractDao.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/AbstractDao.java @@ -23,10 +23,10 @@ import java.util.Set; import li.strolch.model.StrolchElement; import li.strolch.persistence.api.StrolchDao; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.SubTypeRef; -import ch.eitchnet.xmlpers.objref.TypeRef; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.SubTypeRef; +import li.strolch.xmlpers.objref.TypeRef; public abstract class AbstractDao implements StrolchDao { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlAuditDao.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlAuditDao.java index d135713f5..eace60248 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlAuditDao.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlAuditDao.java @@ -24,13 +24,13 @@ import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditQuery; import li.strolch.persistence.api.AuditDao; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.collections.DateRange; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.SubTypeRef; -import ch.eitchnet.xmlpers.objref.TypeRef; +import li.strolch.utils.collections.DateRange; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.SubTypeRef; +import li.strolch.xmlpers.objref.TypeRef; /** * @author Robert von Burg diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlPersistenceHandler.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlPersistenceHandler.java index c4f727777..61714539e 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlPersistenceHandler.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlPersistenceHandler.java @@ -18,12 +18,6 @@ package li.strolch.persistence.xml; import java.io.File; import java.util.Properties; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceManager; -import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.api.StrolchRealm; @@ -40,8 +34,14 @@ import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.xml.model.AuditContextFactory; import li.strolch.persistence.xml.model.OrderContextFactory; import li.strolch.persistence.xml.model.ResourceContextFactory; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.StrolchConfigurationException; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceManager; +import li.strolch.xmlpers.api.PersistenceManagerLoader; +import li.strolch.xmlpers.api.PersistenceTransaction; /** * @author Robert von Burg diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlStrolchTransaction.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlStrolchTransaction.java index 92098bd6f..02916aa12 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlStrolchTransaction.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/XmlStrolchTransaction.java @@ -20,11 +20,11 @@ import java.util.Set; import li.strolch.agent.api.StrolchRealm; import li.strolch.persistence.api.AbstractTransaction; import li.strolch.persistence.api.PersistenceHandler; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.xmlpers.api.ModificationResult; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.api.TransactionResult; +import li.strolch.xmlpers.api.ModificationResult; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.api.TransactionResult; public class XmlStrolchTransaction extends AbstractTransaction { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityContextFactory.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityContextFactory.java index 4b0a90b5a..ad89e27b1 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityContextFactory.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityContextFactory.java @@ -17,11 +17,11 @@ package li.strolch.persistence.xml.model; import li.strolch.model.Tags; import li.strolch.model.activity.Activity; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.ObjectReferenceCache; public class ActivityContextFactory implements PersistenceContextFactory { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityDomParser.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityDomParser.java index 79105de0d..177531365 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityDomParser.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityDomParser.java @@ -18,11 +18,10 @@ package li.strolch.persistence.xml.model; import li.strolch.model.activity.Activity; import li.strolch.model.xml.ActivityFromDomVisitor; import li.strolch.model.xml.ActivityToDomVisitor; +import li.strolch.xmlpers.api.DomParser; import org.w3c.dom.Document; -import ch.eitchnet.xmlpers.api.DomParser; - public class ActivityDomParser implements DomParser { private Activity activity; diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityParserFactory.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityParserFactory.java index 40707611e..cb0598051 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityParserFactory.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivityParserFactory.java @@ -16,9 +16,9 @@ package li.strolch.persistence.xml.model; import li.strolch.model.activity.Activity; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.api.ParserFactory; -import ch.eitchnet.xmlpers.api.SaxParser; +import li.strolch.xmlpers.api.DomParser; +import li.strolch.xmlpers.api.ParserFactory; +import li.strolch.xmlpers.api.SaxParser; public class ActivityParserFactory implements ParserFactory { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivitySaxParser.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivitySaxParser.java index 0833017d5..01f2dc811 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivitySaxParser.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ActivitySaxParser.java @@ -24,11 +24,10 @@ import li.strolch.model.activity.Activity; import li.strolch.model.xml.ActivityToSaxWriterVisitor; import li.strolch.model.xml.StrolchElementListener; import li.strolch.model.xml.XmlModelSaxReader; +import li.strolch.xmlpers.api.SaxParser; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.xmlpers.api.SaxParser; - /** * @author Robert von Burg */ diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditContextFactory.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditContextFactory.java index 0cad02fb2..5df91789b 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditContextFactory.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditContextFactory.java @@ -17,11 +17,11 @@ package li.strolch.persistence.xml.model; import li.strolch.model.Tags; import li.strolch.model.audit.Audit; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.ObjectReferenceCache; public class AuditContextFactory implements PersistenceContextFactory { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditDomParser.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditDomParser.java index a72e9459d..9d2a0d888 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditDomParser.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditDomParser.java @@ -18,12 +18,11 @@ package li.strolch.persistence.xml.model; import li.strolch.model.audit.Audit; import li.strolch.model.audit.AuditFromDomReader; import li.strolch.model.audit.AuditToDomVisitor; +import li.strolch.xmlpers.api.DomParser; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.xmlpers.api.DomParser; - public class AuditDomParser implements DomParser { private Audit audit; diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditParserFactory.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditParserFactory.java index 67b4f8b66..6e9d78503 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditParserFactory.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/AuditParserFactory.java @@ -16,9 +16,9 @@ package li.strolch.persistence.xml.model; import li.strolch.model.audit.Audit; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.api.ParserFactory; -import ch.eitchnet.xmlpers.api.SaxParser; +import li.strolch.xmlpers.api.DomParser; +import li.strolch.xmlpers.api.ParserFactory; +import li.strolch.xmlpers.api.SaxParser; public class AuditParserFactory implements ParserFactory { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderContextFactory.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderContextFactory.java index 5a88e0fec..8cc2017bc 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderContextFactory.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderContextFactory.java @@ -17,11 +17,11 @@ package li.strolch.persistence.xml.model; import li.strolch.model.Order; import li.strolch.model.Tags; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.ObjectReferenceCache; public class OrderContextFactory implements PersistenceContextFactory { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderDomParser.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderDomParser.java index 8e0698a75..dc3108ab1 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderDomParser.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderDomParser.java @@ -18,11 +18,10 @@ package li.strolch.persistence.xml.model; import li.strolch.model.Order; import li.strolch.model.xml.OrderFromDomVisitor; import li.strolch.model.xml.OrderToDomVisitor; +import li.strolch.xmlpers.api.DomParser; import org.w3c.dom.Document; -import ch.eitchnet.xmlpers.api.DomParser; - public class OrderDomParser implements DomParser { private Order order; diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderParserFactory.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderParserFactory.java index f7fc92c66..e3b762103 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderParserFactory.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderParserFactory.java @@ -16,9 +16,9 @@ package li.strolch.persistence.xml.model; import li.strolch.model.Order; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.api.ParserFactory; -import ch.eitchnet.xmlpers.api.SaxParser; +import li.strolch.xmlpers.api.DomParser; +import li.strolch.xmlpers.api.ParserFactory; +import li.strolch.xmlpers.api.SaxParser; public class OrderParserFactory implements ParserFactory { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderSaxParser.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderSaxParser.java index f0f923801..2a88f6765 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderSaxParser.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/OrderSaxParser.java @@ -24,11 +24,10 @@ import li.strolch.model.activity.Activity; import li.strolch.model.xml.OrderToSaxWriterVisitor; import li.strolch.model.xml.StrolchElementListener; import li.strolch.model.xml.XmlModelSaxReader; +import li.strolch.xmlpers.api.SaxParser; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.xmlpers.api.SaxParser; - /** * @author Robert von Burg */ diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceContextFactory.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceContextFactory.java index 408d68db2..e0e50d1fc 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceContextFactory.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceContextFactory.java @@ -17,11 +17,11 @@ package li.strolch.persistence.xml.model; import li.strolch.model.Resource; import li.strolch.model.Tags; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.ObjectReferenceCache; public class ResourceContextFactory implements PersistenceContextFactory { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceDomParser.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceDomParser.java index b79c423fe..fc208e4cd 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceDomParser.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceDomParser.java @@ -18,11 +18,10 @@ package li.strolch.persistence.xml.model; import li.strolch.model.Resource; import li.strolch.model.xml.ResourceFromDomVisitor; import li.strolch.model.xml.ResourceToDomVisitor; +import li.strolch.xmlpers.api.DomParser; import org.w3c.dom.Document; -import ch.eitchnet.xmlpers.api.DomParser; - public class ResourceDomParser implements DomParser { private Resource resource; diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceParserFactory.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceParserFactory.java index a938584d6..42d6762e9 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceParserFactory.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceParserFactory.java @@ -16,9 +16,9 @@ package li.strolch.persistence.xml.model; import li.strolch.model.Resource; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.api.ParserFactory; -import ch.eitchnet.xmlpers.api.SaxParser; +import li.strolch.xmlpers.api.DomParser; +import li.strolch.xmlpers.api.ParserFactory; +import li.strolch.xmlpers.api.SaxParser; public class ResourceParserFactory implements ParserFactory { diff --git a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceSaxParser.java b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceSaxParser.java index 9755e6004..31fce9c06 100644 --- a/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceSaxParser.java +++ b/li.strolch.persistence.xml/src/main/java/li/strolch/persistence/xml/model/ResourceSaxParser.java @@ -24,11 +24,10 @@ import li.strolch.model.activity.Activity; import li.strolch.model.xml.ResourceToSaxWriterVisitor; import li.strolch.model.xml.StrolchElementListener; import li.strolch.model.xml.XmlModelSaxReader; +import li.strolch.xmlpers.api.SaxParser; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.xmlpers.api.SaxParser; - /** * @author Robert von Burg */ diff --git a/li.strolch.persistence.xml/src/test/java/li/strolch/persistence/impl/dao/test/ExistingDbTest.java b/li.strolch.persistence.xml/src/test/java/li/strolch/persistence/impl/dao/test/ExistingDbTest.java index ceaa55a36..5afd57ecd 100644 --- a/li.strolch.persistence.xml/src/test/java/li/strolch/persistence/impl/dao/test/ExistingDbTest.java +++ b/li.strolch.persistence.xml/src/test/java/li/strolch/persistence/impl/dao/test/ExistingDbTest.java @@ -23,15 +23,15 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.helper.FileHelper; import li.strolch.model.Order; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.persistence.xml.XmlPersistenceHandler; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.testbase.runtime.RuntimeMock; +import li.strolch.utils.helper.FileHelper; public class ExistingDbTest { diff --git a/li.strolch.persistence.xml/src/test/java/li/strolch/persistence/impl/dao/test/ObserverUpdateTest.java b/li.strolch.persistence.xml/src/test/java/li/strolch/persistence/impl/dao/test/ObserverUpdateTest.java index b94b7f7fa..2161966a1 100644 --- a/li.strolch.persistence.xml/src/test/java/li/strolch/persistence/impl/dao/test/ObserverUpdateTest.java +++ b/li.strolch.persistence.xml/src/test/java/li/strolch/persistence/impl/dao/test/ObserverUpdateTest.java @@ -29,8 +29,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.xmlpers.api.ModificationResult; import li.strolch.agent.api.Observer; import li.strolch.agent.api.StrolchRealm; import li.strolch.model.Order; @@ -39,9 +37,11 @@ import li.strolch.model.State; import li.strolch.model.StrolchRootElement; import li.strolch.model.Tags; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.testbase.runtime.RuntimeMock; +import li.strolch.xmlpers.api.ModificationResult; /** * @author Robert von Burg diff --git a/li.strolch.persistence.xml/src/test/resources/cachedruntime/config/PrivilegeConfig.xml b/li.strolch.persistence.xml/src/test/resources/cachedruntime/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.persistence.xml/src/test/resources/cachedruntime/config/PrivilegeConfig.xml +++ b/li.strolch.persistence.xml/src/test/resources/cachedruntime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.persistence.xml/src/test/resources/cachedruntime/config/PrivilegeRoles.xml b/li.strolch.persistence.xml/src/test/resources/cachedruntime/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.persistence.xml/src/test/resources/cachedruntime/config/PrivilegeRoles.xml +++ b/li.strolch.persistence.xml/src/test/resources/cachedruntime/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.persistence.xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml b/li.strolch.persistence.xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.persistence.xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml +++ b/li.strolch.persistence.xml/src/test/resources/existingDbRuntime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.persistence.xml/src/test/resources/existingDbRuntime/config/PrivilegeRoles.xml b/li.strolch.persistence.xml/src/test/resources/existingDbRuntime/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.persistence.xml/src/test/resources/existingDbRuntime/config/PrivilegeRoles.xml +++ b/li.strolch.persistence.xml/src/test/resources/existingDbRuntime/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.persistence.xml/src/test/resources/log4j.xml b/li.strolch.persistence.xml/src/test/resources/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.persistence.xml/src/test/resources/log4j.xml +++ b/li.strolch.persistence.xml/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.persistence.xml/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml b/li.strolch.persistence.xml/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.persistence.xml/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml +++ b/li.strolch.persistence.xml/src/test/resources/transactionalruntime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.persistence.xml/src/test/resources/transactionalruntime/config/PrivilegeRoles.xml b/li.strolch.persistence.xml/src/test/resources/transactionalruntime/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.persistence.xml/src/test/resources/transactionalruntime/config/PrivilegeRoles.xml +++ b/li.strolch.persistence.xml/src/test/resources/transactionalruntime/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.planningwebapp/src/main/resources/log4j.xml b/li.strolch.planningwebapp/src/main/resources/log4j.xml index 6bb2f5090..86ea6fcdf 100644 --- a/li.strolch.planningwebapp/src/main/resources/log4j.xml +++ b/li.strolch.planningwebapp/src/main/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.planningwebapp/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml b/li.strolch.planningwebapp/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml index 908102df6..515324965 100644 --- a/li.strolch.planningwebapp/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml +++ b/li.strolch.planningwebapp/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + diff --git a/li.strolch.planningwebapp/src/main/webapp/WEB-INF/config/PrivilegeRoles.xml b/li.strolch.planningwebapp/src/main/webapp/WEB-INF/config/PrivilegeRoles.xml index 4aec2a73c..f2d4d7742 100644 --- a/li.strolch.planningwebapp/src/main/webapp/WEB-INF/config/PrivilegeRoles.xml +++ b/li.strolch.planningwebapp/src/main/webapp/WEB-INF/config/PrivilegeRoles.xml @@ -2,7 +2,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.privilege/README.md b/li.strolch.privilege/README.md index e5eeeb487..96303e372 100644 --- a/li.strolch.privilege/README.md +++ b/li.strolch.privilege/README.md @@ -1,7 +1,7 @@ -ch.eitchnet.privilege +li.strolch.privilege ================== -[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=ch.eitchnet.privilege)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.privilege/) +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=li.strolch.privilege)](http://jenkins.eitchnet.ch/view/li.strolch/job/li.strolch.privilege/) Overview ======================================================================= @@ -19,7 +19,7 @@ a LDAP repository with only the Privilege JAR. Privilege is distributed under the GNU Lesser General Public License on Github.com and can be downloaded at - https://github.com/eitchnet/ch.eitchnet.privilege + https://github.com/eitchnet/li.strolch.privilege The main developer is Robert von Burg who also maintains the Github repository. He is available for all questions regarding Privilege @@ -82,7 +82,7 @@ $ mvn compile Using ======================================================================= -To use Privilege see the ch.eitchnet.privilege.test.PrivilegeTest.java class +To use Privilege see the li.strolch.privilege.test.PrivilegeTest.java class which contains a few test cases including showing how to load Privilege. This documentation is still in need of more work, but for any questions please diff --git a/li.strolch.privilege/config/PrivilegeConfig.xml b/li.strolch.privilege/config/PrivilegeConfig.xml index 744933dcf..f2f535ce2 100644 --- a/li.strolch.privilege/config/PrivilegeConfig.xml +++ b/li.strolch.privilege/config/PrivilegeConfig.xml @@ -13,13 +13,13 @@ - + - + @@ -30,9 +30,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.privilege/config/PrivilegeConfigMerge.xml b/li.strolch.privilege/config/PrivilegeConfigMerge.xml index 403652054..ea847db9b 100644 --- a/li.strolch.privilege/config/PrivilegeConfigMerge.xml +++ b/li.strolch.privilege/config/PrivilegeConfigMerge.xml @@ -9,13 +9,13 @@ - + - + @@ -26,9 +26,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.privilege/config/PrivilegeRoles.xml b/li.strolch.privilege/config/PrivilegeRoles.xml index 95ca00403..d3607f38f 100644 --- a/li.strolch.privilege/config/PrivilegeRoles.xml +++ b/li.strolch.privilege/config/PrivilegeRoles.xml @@ -53,7 +53,7 @@ - + true @@ -71,17 +71,17 @@ - - ch.eitchnet.privilege.test.model.TestSystemUserAction - ch.eitchnet.privilege.test.model.TestSystemUserActionDeny + + li.strolch.privilege.test.model.TestSystemUserAction + li.strolch.privilege.test.model.TestSystemUserActionDeny - + true - + hello goodbye diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/AccessDeniedException.java similarity index 96% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/base/AccessDeniedException.java index 2a14841d7..c59b9a04e 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/AccessDeniedException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.base; +package li.strolch.privilege.base; /** * Exception thrown if access is denied during login, or if a certain privilege is not granted diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/InvalidCredentialsException.java similarity index 91% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/base/InvalidCredentialsException.java index 013e9dce8..9dcca5b74 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/InvalidCredentialsException.java @@ -1,4 +1,4 @@ -package ch.eitchnet.privilege.base; +package li.strolch.privilege.base; /** * Exception thrown if the given credentials are invalid diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeConflictResolution.java similarity index 91% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeConflictResolution.java index 0d60fb28d..5a2cfd089 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeConflictResolution.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.base; +package li.strolch.privilege.base; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; /** * The {@link PrivilegeConflictResolution} defines what should be done if a {@link User} has {@link Role Roles} which diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeException.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeException.java index 9d34cd7ed..45933786b 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/base/PrivilegeException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.base; +package li.strolch.privilege.base; /** * Main {@link RuntimeException} thrown if something goes wrong in Privilege diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java similarity index 95% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java index 5c083dbbe..5bab8c615 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultEncryptionHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.handler; +package li.strolch.privilege.handler; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; @@ -25,9 +25,9 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.utils.helper.StringHelper; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index 91e800a2e..3267ea527 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.handler; +package li.strolch.privilege.handler; import java.io.File; import java.io.FileInputStream; @@ -41,28 +41,28 @@ import javax.crypto.SecretKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.InvalidCredentialsException; -import ch.eitchnet.privilege.base.PrivilegeConflictResolution; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.RoleRep; -import ch.eitchnet.privilege.model.SimpleRestrictable; -import ch.eitchnet.privilege.model.UserRep; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.PrivilegeImpl; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.policy.PrivilegePolicy; -import ch.eitchnet.privilege.xml.CertificateStubsDomWriter; -import ch.eitchnet.privilege.xml.CertificateStubsSaxReader; -import ch.eitchnet.privilege.xml.CertificateStubsSaxReader.CertificateStub; -import ch.eitchnet.utils.collections.Tuple; -import ch.eitchnet.utils.helper.AesCryptoHelper; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.InvalidCredentialsException; +import li.strolch.privilege.base.PrivilegeConflictResolution; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.PrivilegeRep; +import li.strolch.privilege.model.RoleRep; +import li.strolch.privilege.model.SimpleRestrictable; +import li.strolch.privilege.model.UserRep; +import li.strolch.privilege.model.UserState; +import li.strolch.privilege.model.internal.PrivilegeImpl; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.privilege.xml.CertificateStubsDomWriter; +import li.strolch.privilege.xml.CertificateStubsSaxReader; +import li.strolch.privilege.xml.CertificateStubsSaxReader.CertificateStub; +import li.strolch.utils.collections.Tuple; +import li.strolch.utils.helper.AesCryptoHelper; +import li.strolch.utils.helper.StringHelper; /** *

        @@ -1342,7 +1342,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * This simple implementation validates that the password is not null, and that the password string is not empty * - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(byte[]) + * @see li.strolch.privilege.handler.PrivilegeHandler#validatePassword(byte[]) */ @Override public void validatePassword(byte[] password) throws PrivilegeException { diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/EncryptionHandler.java similarity index 98% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/handler/EncryptionHandler.java index 59eddcfbe..238772be8 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/EncryptionHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.handler; +package li.strolch.privilege.handler; import java.util.Map; diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java similarity index 93% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java index c3fd0fdc6..627f86333 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PersistenceHandler.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.handler; +package li.strolch.privilege.handler; import java.util.List; import java.util.Map; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.policy.PrivilegePolicy; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.policy.PrivilegePolicy; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java index cb612278d..65dcdaf79 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java @@ -13,25 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.handler; +package li.strolch.privilege.handler; import java.util.List; import java.util.Locale; import java.util.Map; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeConflictResolution; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.RoleRep; -import ch.eitchnet.privilege.model.UserRep; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.policy.PrivilegePolicy; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeConflictResolution; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.PrivilegeRep; +import li.strolch.privilege.model.RoleRep; +import li.strolch.privilege.model.UserRep; +import li.strolch.privilege.model.UserState; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.policy.PrivilegePolicy; /** * The {@link PrivilegeHandler} is the centrally exposed API for accessing the privilege library. It exposes all needed diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SystemUserAction.java similarity index 90% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SystemUserAction.java index 082bc2ca3..6bb57d15d 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SystemUserAction.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.handler; +package li.strolch.privilege.handler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; /** * With this interface system actions, which are to be performed in an automated fashion, i.e. by cron jobs, can be diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java similarity index 94% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java index 2ad3462b4..638b79f58 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/XmlPersistenceHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.handler; +package li.strolch.privilege.handler; import java.io.File; import java.text.MessageFormat; @@ -26,16 +26,16 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.xml.PrivilegeRolesDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeRolesSaxReader; -import ch.eitchnet.privilege.xml.PrivilegeUsersDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeUsersSaxReader; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.helper.XmlHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.xml.PrivilegeRolesDomWriter; +import li.strolch.privilege.xml.PrivilegeRolesSaxReader; +import li.strolch.privilege.xml.PrivilegeUsersDomWriter; +import li.strolch.privilege.xml.PrivilegeUsersSaxReader; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.helper.XmlHelper; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/BootstrapConfigurationHelper.java similarity index 86% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/helper/BootstrapConfigurationHelper.java index 6504e12d4..646129d5a 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/BootstrapConfigurationHelper.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.helper; +package li.strolch.privilege.helper; import java.io.File; import java.util.HashMap; import java.util.Map; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; -import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.internal.PrivilegeContainerModel; +import li.strolch.privilege.xml.PrivilegeConfigDomWriter; /** *

        @@ -53,8 +53,8 @@ public class BootstrapConfigurationHelper { //private static String modelFileName = "PrivilegeUsers.xml"; //private static String hashAlgorithm = "SHA-256"; - private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; - private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; + private static String defaultPersistenceHandler = "li.strolch.privilege.handler.DefaultPersistenceHandler"; + private static String defaultEncryptionHandler = "li.strolch.privilege.handler.DefaultEncryptionHandler"; /** * @param args @@ -94,7 +94,7 @@ public class BootstrapConfigurationHelper { containerModel.setPersistenceHandlerClassName(defaultPersistenceHandler); containerModel.setPersistenceHandlerParameterMap(persistenceHandlerParameterMap); - containerModel.addPolicy("DefaultPrivilege", "ch.eitchnet.privilege.policy.DefaultPrivilege"); + containerModel.addPolicy("DefaultPrivilege", "li.strolch.privilege.policy.DefaultPrivilege"); // now perform work: File configFile = new File(BootstrapConfigurationHelper.path + "/" diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PasswordCreaterUI.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PasswordCreaterUI.java index 62eab7fa8..40e68f95d 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PasswordCreaterUI.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.helper; +package li.strolch.privilege.helper; import java.awt.Dimension; import java.awt.GridLayout; @@ -31,7 +31,7 @@ import javax.swing.JPasswordField; import javax.swing.JTextField; import javax.swing.SwingConstants; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * Simple Swing UI to create passwords diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PasswordCreator.java similarity index 95% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PasswordCreator.java index 81e3bf567..2898e12e8 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PasswordCreator.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.helper; +package li.strolch.privilege.helper; import java.io.BufferedReader; import java.io.InputStreamReader; import java.security.MessageDigest; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PrivilegeInitializationHelper.java similarity index 89% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PrivilegeInitializationHelper.java index 5cdf1b556..2676b5069 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PrivilegeInitializationHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.helper; +package li.strolch.privilege.helper; import java.io.File; import java.io.FileInputStream; @@ -21,16 +21,16 @@ import java.io.InputStream; import java.text.MessageFormat; import java.util.Map; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; -import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.PersistenceHandler; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; -import ch.eitchnet.privilege.policy.PrivilegePolicy; -import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; -import ch.eitchnet.utils.helper.ClassHelper; -import ch.eitchnet.utils.helper.XmlHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.DefaultPrivilegeHandler; +import li.strolch.privilege.handler.EncryptionHandler; +import li.strolch.privilege.handler.PersistenceHandler; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.internal.PrivilegeContainerModel; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.privilege.xml.PrivilegeConfigSaxReader; +import li.strolch.utils.helper.ClassHelper; +import li.strolch.utils.helper.XmlHelper; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java similarity index 99% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java index 041ce2f5f..8c29f4710 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.helper; +package li.strolch.privilege.helper; /** * The constants used in parsing XML documents which contain the configuration for Privilege diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/i18n/PrivilegeMessages.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/i18n/PrivilegeMessages.java index 5b39df545..f8de8c881 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/i18n/PrivilegeMessages.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.i18n; +package li.strolch.privilege.i18n; import java.util.MissingResourceException; import java.util.ResourceBundle; diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/Certificate.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/Certificate.java index d170ab45b..fb95ae4fb 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/Certificate.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; import java.io.Serializable; import java.util.Collections; @@ -22,10 +22,10 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.internal.User; +import li.strolch.utils.helper.StringHelper; /** * The {@link Certificate} is the object a client keeps when accessing a Privilege enabled system. This object is the diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/IPrivilege.java similarity index 93% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/IPrivilege.java index 3d22e7b34..958911e4d 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/IPrivilege.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; import java.util.Set; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.policy.PrivilegePolicy; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.policy.PrivilegePolicy; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/PrivilegeContext.java similarity index 94% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/PrivilegeContext.java index 95a7a5efd..41d12d969 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/PrivilegeContext.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; import java.text.MessageFormat; import java.util.Collections; @@ -21,10 +21,10 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.policy.PrivilegePolicy; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.policy.PrivilegePolicy; /** * This context gives access to a logged in user's privilege data e.g. the {@link UserRep}, {@link Certificate} and the diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java similarity index 95% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java index 66dff555b..ac27dfb96 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/PrivilegeRep.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; import java.io.Serializable; import java.util.HashSet; @@ -25,11 +25,11 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.policy.PrivilegePolicy; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.helper.StringHelper; /** * To keep certain details of the {@link IPrivilege} itself hidden from remote clients and make sure instances are only diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/Restrictable.java similarity index 94% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/Restrictable.java index 87f889818..17e8a401a 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/Restrictable.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; -import ch.eitchnet.privilege.policy.PrivilegePolicy; +import li.strolch.privilege.policy.PrivilegePolicy; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/RoleRep.java similarity index 95% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/RoleRep.java index 6e449ff97..fc86b3792 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/RoleRep.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; import java.io.Serializable; import java.text.MessageFormat; @@ -26,9 +26,9 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.internal.Role; +import li.strolch.utils.helper.StringHelper; /** * To keep certain details of the {@link Role} itself hidden from remote clients and make sure instances are only edited diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/SimpleRestrictable.java similarity index 94% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/SimpleRestrictable.java index 341bbb37c..f33cea360 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/SimpleRestrictable.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; public class SimpleRestrictable implements Restrictable { diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/UserRep.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/UserRep.java index 6300fe96c..2fb0334f8 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/UserRep.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; import java.io.Serializable; import java.util.ArrayList; @@ -30,11 +30,11 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.xml.XmlKeyValue; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.xml.XmlKeyValue; /** * To keep certain details of the {@link User} itself hidden from remote clients and make sure instances are only edited diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/UserState.java similarity index 95% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/UserState.java index b78e492ff..12da7a2a6 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/UserState.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model; +package li.strolch.privilege.model; -import ch.eitchnet.privilege.model.internal.User; +import li.strolch.privilege.model.internal.User; /** * The {@link UserState} enum defines the different states a {@link User} can have: diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeContainerModel.java similarity index 96% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeContainerModel.java index cec81e18d..a85371886 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeContainerModel.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model.internal; +package li.strolch.privilege.model.internal; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.policy.PrivilegePolicy; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.policy.PrivilegePolicy; /** * This class is used during XML parsing to hold the model before it is properly validated and made accessible through diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeImpl.java similarity index 93% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeImpl.java index 89b67366a..3a5e45068 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeImpl.java @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model.internal; +package li.strolch.privilege.model.internal; import java.text.MessageFormat; import java.util.Collections; import java.util.HashSet; import java.util.Set; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.policy.PrivilegePolicy; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeRep; +import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.helper.StringHelper; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/Role.java similarity index 94% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/Role.java index bd8acdf8f..1d3fc6c87 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/Role.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model.internal; +package li.strolch.privilege.model.internal; import java.util.ArrayList; import java.util.Collections; @@ -23,11 +23,11 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.RoleRep; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeRep; +import li.strolch.privilege.model.RoleRep; +import li.strolch.utils.helper.StringHelper; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/User.java similarity index 96% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/User.java index 5afe247aa..70cd57b5b 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/User.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.model.internal; +package li.strolch.privilege.model.internal; import java.util.Collections; import java.util.HashMap; @@ -22,10 +22,10 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.UserRep; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.UserRep; +import li.strolch.privilege.model.UserState; +import li.strolch.utils.helper.StringHelper; /** * This class defines the actual login information for a given user which can be granted privileges. Every user is diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/DefaultPrivilege.java similarity index 82% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/policy/DefaultPrivilege.java index 05494e5c5..69c6bea21 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/DefaultPrivilege.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.policy; +package li.strolch.privilege.policy; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Role; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.model.internal.Role; /** * This is a simple implementation of {@link PrivilegePolicy} which uses the {@link Restrictable#getPrivilegeName()} to @@ -35,7 +35,7 @@ public class DefaultPrivilege implements PrivilegePolicy { /** * The value of {@link Restrictable#getPrivilegeValue()} is used to check if the {@link Role} has this privilege * - * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(IPrivilege, Restrictable) + * @see li.strolch.privilege.policy.PrivilegePolicy#validateAction(IPrivilege, Restrictable) */ @Override public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicy.java similarity index 81% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicy.java index ba698b174..fe6daff9e 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicy.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.policy; +package li.strolch.privilege.policy; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicyHelper.java similarity index 86% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicyHelper.java index b06456453..e749eed77 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/PrivilegePolicyHelper.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.policy; +package li.strolch.privilege.policy; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/RoleAccessPrivilege.java similarity index 89% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/policy/RoleAccessPrivilege.java index d82f3576d..e0dfb4676 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/RoleAccessPrivilege.java @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.policy; +package li.strolch.privilege.policy; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.utils.collections.Tuple; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.model.internal.Role; +import li.strolch.utils.collections.Tuple; +import li.strolch.utils.dbc.DBC; /** * This {@link PrivilegePolicy} expects a {@link Tuple} as {@link Restrictable#getPrivilegeValue()}. The Tuple must diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UserAccessPrivilege.java similarity index 93% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UserAccessPrivilege.java index cafbd37c5..ca46ec55e 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UserAccessPrivilege.java @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.policy; +package li.strolch.privilege.policy; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.utils.collections.Tuple; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.model.internal.User; +import li.strolch.utils.collections.Tuple; +import li.strolch.utils.dbc.DBC; /** * This {@link PrivilegePolicy} expects a {@link Tuple} as {@link Restrictable#getPrivilegeValue()} and then depending diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UserAccessWithSameOrganisationPrivilege.java similarity index 86% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UserAccessWithSameOrganisationPrivilege.java index 599c4a7c3..5f02ae266 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UserAccessWithSameOrganisationPrivilege.java @@ -13,21 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.policy; +package li.strolch.privilege.policy; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.utils.collections.Tuple; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.model.internal.User; +import li.strolch.utils.collections.Tuple; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * Validates that any access to a privilege User is done only by users in the same organisation diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificatePrivilege.java similarity index 86% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificatePrivilege.java index 425c4183d..a95d2d11e 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificatePrivilege.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.policy; +package li.strolch.privilege.policy; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java similarity index 85% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java index 124addf28..79ceb086d 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.policy; +package li.strolch.privilege.policy; import java.text.MessageFormat; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.Restrictable; +import li.strolch.utils.helper.StringHelper; /** *

        diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsDomWriter.java similarity index 91% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsDomWriter.java index 7dab6d30e..4725b4f0f 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsDomWriter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import java.io.OutputStream; import java.util.List; @@ -21,10 +21,10 @@ import java.util.List; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.helper.XmlHelper; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.Certificate; +import li.strolch.utils.helper.XmlHelper; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java similarity index 91% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java index 0cbfdb3c2..ff1f95121 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/CertificateStubsSaxReader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import java.io.InputStream; import java.util.ArrayList; @@ -25,11 +25,11 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.XmlHelper; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.XmlHelper; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/ElementParser.java similarity index 96% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/ElementParser.java index f35a1980e..149d70212 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/ElementParser.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import org.xml.sax.Attributes; import org.xml.sax.SAXException; diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/ElementParserAdapter.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/ElementParserAdapter.java index 11a11200e..a9716e7a2 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/ElementParserAdapter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import org.xml.sax.Attributes; import org.xml.sax.SAXException; diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigDomWriter.java similarity index 94% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigDomWriter.java index e2745a7eb..97dfb3458 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigDomWriter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import java.io.File; import java.util.Map.Entry; @@ -21,10 +21,10 @@ import java.util.Map.Entry; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; -import ch.eitchnet.privilege.policy.PrivilegePolicy; -import ch.eitchnet.utils.helper.XmlHelper; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.internal.PrivilegeContainerModel; +import li.strolch.privilege.policy.PrivilegePolicy; +import li.strolch.utils.helper.XmlHelper; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java similarity index 93% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java index 421aff46f..0a266ad1a 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import java.util.ArrayDeque; import java.util.Deque; @@ -24,8 +24,8 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.internal.PrivilegeContainerModel; /** * @author Robert von Burg @@ -91,12 +91,12 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { // // // -// +// // // // // -// +// // // // @@ -165,7 +165,7 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { class PoliciesParser extends ElementParserAdapter { -// +// @Override public void startElement(String uri, String localName, String qName, Attributes attributes) diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesDomWriter.java similarity index 92% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesDomWriter.java index a42963ecf..f973f7cda 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesDomWriter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import java.io.File; import java.util.List; @@ -21,10 +21,10 @@ import java.util.List; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.utils.helper.XmlHelper; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.internal.Role; +import li.strolch.utils.helper.XmlHelper; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java similarity index 92% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java index 5efa039b4..768a3b09a 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeRolesSaxReader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import java.text.MessageFormat; import java.util.ArrayDeque; @@ -31,11 +31,11 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.internal.PrivilegeImpl; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.internal.PrivilegeImpl; +import li.strolch.privilege.model.internal.Role; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg @@ -93,15 +93,15 @@ public class PrivilegeRolesSaxReader extends DefaultHandler { } // -// +// // true // // // -// +// // true // -// +// // true // // diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java similarity index 94% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java index 77b7879f0..dec448fce 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersDomWriter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import java.io.File; import java.util.List; @@ -22,10 +22,10 @@ import java.util.Map.Entry; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.helper.XmlHelper; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.internal.User; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.helper.XmlHelper; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java similarity index 97% rename from li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java rename to li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java index 701c617c2..e79efe676 100644 --- a/li.strolch.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeUsersSaxReader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.xml; +package li.strolch.privilege.xml; import java.text.MessageFormat; import java.util.ArrayDeque; @@ -32,9 +32,9 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.User; +import li.strolch.privilege.helper.XmlConstants; +import li.strolch.privilege.model.UserState; +import li.strolch.privilege.model.internal.User; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/AbstractPrivilegeTest.java similarity index 91% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/AbstractPrivilegeTest.java index 9f1c1a3ce..af4af356e 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/AbstractPrivilegeTest.java @@ -1,4 +1,4 @@ -package ch.eitchnet.privilege.test; +package li.strolch.privilege.test; import static org.junit.Assert.assertTrue; @@ -8,12 +8,12 @@ import java.nio.file.Files; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.helper.FileHelper; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.helper.PrivilegeInitializationHelper; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.utils.helper.FileHelper; public class AbstractPrivilegeTest { diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PersistSessionsTest.java similarity index 97% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/PersistSessionsTest.java index 57f6b9d03..e79ddbb57 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PersistSessionsTest.java @@ -1,4 +1,4 @@ -package ch.eitchnet.privilege.test; +package li.strolch.privilege.test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeConflictMergeTest.java similarity index 96% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeConflictMergeTest.java index 2fd50fad1..8943c1c5f 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeConflictMergeTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.test; +package li.strolch.privilege.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -24,7 +24,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.model.IPrivilege; +import li.strolch.privilege.model.IPrivilege; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java similarity index 94% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java index 242c265bb..c0a956842 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/PrivilegeTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.test; +package li.strolch.privilege.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -37,20 +37,20 @@ import org.junit.rules.ExpectedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeMessages; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.RoleRep; -import ch.eitchnet.privilege.model.UserRep; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.test.model.TestRestrictable; -import ch.eitchnet.privilege.test.model.TestSystemUserAction; -import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; -import ch.eitchnet.utils.helper.ArraysHelper; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.i18n.PrivilegeMessages; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeRep; +import li.strolch.privilege.model.Restrictable; +import li.strolch.privilege.model.RoleRep; +import li.strolch.privilege.model.UserRep; +import li.strolch.privilege.model.UserState; +import li.strolch.privilege.test.model.TestRestrictable; +import li.strolch.privilege.test.model.TestSystemUserAction; +import li.strolch.privilege.test.model.TestSystemUserActionDeny; +import li.strolch.utils.helper.ArraysHelper; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum.br /> @@ -179,7 +179,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { public void testPerformSystemRestrictableFailPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); this.exception.expectMessage( - "User system_admin does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction"); + "User system_admin does not have the privilege li.strolch.privilege.handler.SystemUserAction"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -198,7 +198,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); this.exception.expectMessage( - "User system_admin2 does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction needed for Restrictable ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); + "User system_admin2 does not have the privilege li.strolch.privilege.handler.SystemUserAction needed for Restrictable li.strolch.privilege.test.model.TestSystemUserActionDeny"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -562,7 +562,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { this.ctx.validateAction(restrictable); fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { - String msg = "User bob does not have the privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; + String msg = "User bob does not have the privilege li.strolch.privilege.test.model.TestRestrictable needed for Restrictable li.strolch.privilege.test.model.TestRestrictable"; assertEquals(msg, e.getLocalizedMessage()); } finally { logout(); diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/XmlTest.java similarity index 87% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/XmlTest.java index 54a2e1b4b..bd274388c 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/XmlTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.test; +package li.strolch.privilege.test; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; @@ -39,24 +39,24 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.handler.DefaultEncryptionHandler; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.handler.XmlPersistenceHandler; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; -import ch.eitchnet.privilege.model.internal.PrivilegeImpl; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; -import ch.eitchnet.privilege.xml.PrivilegeRolesDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeRolesSaxReader; -import ch.eitchnet.privilege.xml.PrivilegeUsersDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeUsersSaxReader; -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.helper.XmlHelper; +import li.strolch.privilege.handler.DefaultEncryptionHandler; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.handler.XmlPersistenceHandler; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.UserState; +import li.strolch.privilege.model.internal.PrivilegeContainerModel; +import li.strolch.privilege.model.internal.PrivilegeImpl; +import li.strolch.privilege.model.internal.Role; +import li.strolch.privilege.model.internal.User; +import li.strolch.privilege.xml.PrivilegeConfigDomWriter; +import li.strolch.privilege.xml.PrivilegeConfigSaxReader; +import li.strolch.privilege.xml.PrivilegeRolesDomWriter; +import li.strolch.privilege.xml.PrivilegeRolesSaxReader; +import li.strolch.privilege.xml.PrivilegeUsersDomWriter; +import li.strolch.privilege.xml.PrivilegeUsersSaxReader; +import li.strolch.utils.helper.FileHelper; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.helper.XmlHelper; /** * @author Robert von Burg @@ -154,14 +154,14 @@ public class XmlTest { containerModel.setPersistenceHandlerClassName(XmlPersistenceHandler.class.getName()); containerModel.setPersistenceHandlerParameterMap(persistenceHandlerParameterMap); - containerModel.addPolicy("DefaultPrivilege", "ch.eitchnet.privilege.policy.DefaultPrivilege"); + containerModel.addPolicy("DefaultPrivilege", "li.strolch.privilege.policy.DefaultPrivilege"); File configFile = new File("./target/test/PrivilegeTest.xml"); PrivilegeConfigDomWriter configSaxWriter = new PrivilegeConfigDomWriter(containerModel, configFile); configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - assertEquals("22d4ba39605d49c758184d9bd63beae5ccf8786f3dabbab45cd9f59c2afbcbd0", fileHash); + assertEquals("800b8e42e15b6b3bb425fa9c12a011d587d2b12343a1d1371eaa36dc1b2ea5f4", fileHash); } @Test @@ -250,11 +250,11 @@ public class XmlTest { // AppUser Role appUser = findRole("AppUser", roles); assertEquals("AppUser", appUser.getName()); - assertEquals(new HashSet<>(Arrays.asList("ch.eitchnet.privilege.test.model.TestRestrictable")), + assertEquals(new HashSet<>(Arrays.asList("li.strolch.privilege.test.model.TestRestrictable")), appUser.getPrivilegeNames()); - IPrivilege testRestrictable = appUser.getPrivilege("ch.eitchnet.privilege.test.model.TestRestrictable"); - assertEquals("ch.eitchnet.privilege.test.model.TestRestrictable", testRestrictable.getName()); + IPrivilege testRestrictable = appUser.getPrivilege("li.strolch.privilege.test.model.TestRestrictable"); + assertEquals("li.strolch.privilege.test.model.TestRestrictable", testRestrictable.getName()); assertEquals("DefaultPrivilege", testRestrictable.getPolicy()); assertTrue(testRestrictable.isAllAllowed()); assertEquals(0, testRestrictable.getAllowList().size()); @@ -265,20 +265,20 @@ public class XmlTest { assertEquals("system_admin_privileges", systemAdminPrivileges.getName()); assertEquals(2, systemAdminPrivileges.getPrivilegeNames().size()); assertThat(systemAdminPrivileges.getPrivilegeNames(), - containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction", - "ch.eitchnet.privilege.test.model.TestSystemRestrictable")); + containsInAnyOrder("li.strolch.privilege.handler.SystemUserAction", + "li.strolch.privilege.test.model.TestSystemRestrictable")); IPrivilege testSystemUserAction = systemAdminPrivileges - .getPrivilege("ch.eitchnet.privilege.handler.SystemUserAction"); - assertEquals("ch.eitchnet.privilege.handler.SystemUserAction", testSystemUserAction.getName()); + .getPrivilege("li.strolch.privilege.handler.SystemUserAction"); + assertEquals("li.strolch.privilege.handler.SystemUserAction", testSystemUserAction.getName()); assertEquals("DefaultPrivilege", testSystemUserAction.getPolicy()); assertFalse(testSystemUserAction.isAllAllowed()); assertEquals(1, testSystemUserAction.getAllowList().size()); assertEquals(1, testSystemUserAction.getDenyList().size()); IPrivilege testSystemRestrictable = systemAdminPrivileges - .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemRestrictable"); - assertEquals("ch.eitchnet.privilege.test.model.TestSystemRestrictable", testSystemRestrictable.getName()); + .getPrivilege("li.strolch.privilege.test.model.TestSystemRestrictable"); + assertEquals("li.strolch.privilege.test.model.TestSystemRestrictable", testSystemRestrictable.getName()); assertEquals("DefaultPrivilege", testSystemRestrictable.getPolicy()); assertTrue(testSystemRestrictable.isAllAllowed()); assertEquals(0, testSystemRestrictable.getAllowList().size()); @@ -289,11 +289,11 @@ public class XmlTest { assertEquals("restrictedRole", restrictedRole.getName()); assertEquals(1, restrictedRole.getPrivilegeNames().size()); assertThat(restrictedRole.getPrivilegeNames(), - containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction")); + containsInAnyOrder("li.strolch.privilege.handler.SystemUserAction")); IPrivilege testSystemUserAction2 = restrictedRole - .getPrivilege("ch.eitchnet.privilege.handler.SystemUserAction"); - assertEquals("ch.eitchnet.privilege.handler.SystemUserAction", testSystemUserAction2.getName()); + .getPrivilege("li.strolch.privilege.handler.SystemUserAction"); + assertEquals("li.strolch.privilege.handler.SystemUserAction", testSystemUserAction2.getName()); assertEquals("DefaultPrivilege", testSystemUserAction2.getPolicy()); assertFalse(testSystemUserAction2.isAllAllowed()); assertEquals(1, testSystemUserAction2.getAllowList().size()); diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestRestrictable.java similarity index 80% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestRestrictable.java index af7bb4e7f..66bc6c892 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestRestrictable.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.test.model; +package li.strolch.privilege.test.model; -import ch.eitchnet.privilege.model.Restrictable; +import li.strolch.privilege.model.Restrictable; /** * @author Robert von Burg @@ -24,7 +24,7 @@ import ch.eitchnet.privilege.model.Restrictable; public class TestRestrictable implements Restrictable { /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeName() */ @Override public String getPrivilegeName() { @@ -32,7 +32,7 @@ public class TestRestrictable implements Restrictable { } /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeValue() */ @Override public Object getPrivilegeValue() { diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemRestrictable.java similarity index 80% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemRestrictable.java index d73c731f5..1f8e9be39 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemRestrictable.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.test.model; +package li.strolch.privilege.test.model; -import ch.eitchnet.privilege.model.Restrictable; +import li.strolch.privilege.model.Restrictable; /** * @author Robert von Burg @@ -24,7 +24,7 @@ import ch.eitchnet.privilege.model.Restrictable; public class TestSystemRestrictable implements Restrictable { /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeName() */ @Override public String getPrivilegeName() { @@ -32,7 +32,7 @@ public class TestSystemRestrictable implements Restrictable { } /** - * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + * @see li.strolch.privilege.model.Restrictable#getPrivilegeValue() */ @Override public Object getPrivilegeValue() { diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemUserAction.java similarity index 85% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemUserAction.java index b3942b7cc..99c3f7744 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemUserAction.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.test.model; +package li.strolch.privilege.test.model; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.PrivilegeContext; /** * @author Robert von Burg diff --git a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemUserActionDeny.java similarity index 86% rename from li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java rename to li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemUserActionDeny.java index 51c1574cc..1eb7d1501 100644 --- a/li.strolch.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/model/TestSystemUserActionDeny.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.privilege.test.model; +package li.strolch.privilege.test.model; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.PrivilegeContext; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/model/query/parser/QueryParser.java b/li.strolch.rest/src/main/java/li/strolch/model/query/parser/QueryParser.java index 1c0046cb7..49de40c77 100644 --- a/li.strolch.rest/src/main/java/li/strolch/model/query/parser/QueryParser.java +++ b/li.strolch.rest/src/main/java/li/strolch/model/query/parser/QueryParser.java @@ -8,7 +8,6 @@ import org.petitparser.context.Result; import org.petitparser.parser.Parser; import org.petitparser.tools.CompositeParser; -import ch.eitchnet.utils.StringMatchMode; import li.strolch.model.Order; import li.strolch.model.Resource; import li.strolch.model.activity.Activity; @@ -23,6 +22,7 @@ import li.strolch.model.query.StrolchTypeNavigation; import li.strolch.model.visitor.NoStrategyActivityVisitor; import li.strolch.model.visitor.NoStrategyOrderVisitor; import li.strolch.model.visitor.NoStrategyResourceVisitor; +import li.strolch.utils.StringMatchMode; public class QueryParser extends CompositeParser { diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java b/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java index 54650d10b..ebf065e3b 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java @@ -38,18 +38,18 @@ import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.privilege.model.SimpleRestrictable; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.exception.StrolchException; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeContext; +import li.strolch.privilege.model.SimpleRestrictable; import li.strolch.rest.model.UserSession; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.privilege.PrivilegeHandler; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java b/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java index 09572d252..ddddfc7d4 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/RestfulStrolchComponent.java @@ -19,7 +19,6 @@ import java.text.MessageFormat; import org.glassfish.jersey.server.ServerProperties; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchComponent; @@ -27,6 +26,7 @@ import li.strolch.rest.filters.AccessControlResponseFilter; import li.strolch.rest.filters.HttpCacheResponseFilter; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.service.api.ServiceHandler; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java index 9f7d5beb3..5a3ef6c77 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchRestfulExceptionMapper.java @@ -27,10 +27,10 @@ import javax.ws.rs.ext.Provider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.exception.StrolchAccessDeniedException; +import li.strolch.privilege.model.Restrictable; import li.strolch.rest.model.Result; +import li.strolch.utils.helper.StringHelper; @Provider public class StrolchRestfulExceptionMapper implements ExceptionMapper { diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java index b3d6604a8..b8e1d0085 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/StrolchSessionHandler.java @@ -18,8 +18,8 @@ package li.strolch.rest; import java.util.List; import java.util.Locale; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.model.UserSession; -import ch.eitchnet.privilege.model.Certificate; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuditsService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuditsService.java index b8c4ab59d..0bee71f9f 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuditsService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuditsService.java @@ -32,13 +32,13 @@ import javax.ws.rs.core.Response; import li.strolch.agent.api.StrolchRealm; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.model.AuditQuery; import li.strolch.rest.model.AuditQueryResult; import li.strolch.rest.model.StringListResult; import li.strolch.rest.model.visitor.ToAuditQueryVisitor; -import ch.eitchnet.privilege.model.Certificate; @Path("strolch/audits") public class AuditsService { diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java index cd7d597f7..af721eb91 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java @@ -42,14 +42,13 @@ import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.JsonObject; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.InvalidCredentialsException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.exception.StrolchException; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.InvalidCredentialsException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.IPrivilege; +import li.strolch.privilege.model.PrivilegeContext; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.StrolchSessionHandler; @@ -57,6 +56,7 @@ import li.strolch.rest.model.Login; import li.strolch.rest.model.LoginResult; import li.strolch.rest.model.LogoutResult; import li.strolch.runtime.privilege.PrivilegeHandler; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/EnumQuery.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/EnumQuery.java index f07a9f7a4..8f6b23f39 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/EnumQuery.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/EnumQuery.java @@ -28,6 +28,7 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.helper.RestfulHelper; @@ -37,8 +38,6 @@ import li.strolch.runtime.query.enums.StrolchEnum; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; - /** * @author Robert von Burg */ diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/Inspector.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/Inspector.java index f68da826a..a29cb9c30 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/Inspector.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/Inspector.java @@ -38,7 +38,6 @@ import javax.xml.parsers.SAXParserFactory; import org.xml.sax.InputSource; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.OrderMap; import li.strolch.agent.api.ResourceMap; @@ -53,6 +52,7 @@ import li.strolch.model.xml.SimpleStrolchElementListener; import li.strolch.model.xml.XmlModelSaxReader; import li.strolch.persistence.api.StrolchPersistenceException; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.model.AgentOverview; diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/ModelQuery.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/ModelQuery.java index c9f00d80c..6a30c3fd1 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/ModelQuery.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/ModelQuery.java @@ -21,9 +21,6 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.collections.Paging; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.agent.api.ActivityMap; import li.strolch.agent.api.OrderMap; import li.strolch.agent.api.ResourceMap; @@ -42,9 +39,12 @@ import li.strolch.model.query.StrolchTypeNavigation; import li.strolch.model.query.parser.QueryParser; import li.strolch.model.visitor.StrolchElementVisitor; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.runtime.StrolchConstants; +import li.strolch.utils.collections.Paging; +import li.strolch.utils.helper.StringHelper; @Path("strolch/model") public class ModelQuery { diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegePoliciesService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegePoliciesService.java index 8a2253626..4adf20f5f 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegePoliciesService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegePoliciesService.java @@ -28,11 +28,11 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import li.strolch.agent.api.ComponentContainer; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.xml.XmlKeyValue; +import li.strolch.utils.xml.XmlKeyValue; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegeRolesService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegeRolesService.java index 410398db6..820658b75 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegeRolesService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegeRolesService.java @@ -33,6 +33,12 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import li.strolch.agent.api.ComponentContainer; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.PrivilegeRep; +import li.strolch.privilege.model.RoleRep; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.model.Result; @@ -47,12 +53,6 @@ import li.strolch.service.privilege.roles.PrivilegeRoleArgument; import li.strolch.service.privilege.roles.PrivilegeRoleNameArgument; import li.strolch.service.privilege.roles.PrivilegeRoleResult; import li.strolch.service.privilege.roles.PrivilegeUpdateRoleService; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeRep; -import ch.eitchnet.privilege.model.RoleRep; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java index 13de4c635..fb6640573 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/PrivilegeUsersService.java @@ -34,13 +34,13 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.UserRep; -import ch.eitchnet.privilege.model.UserState; import li.strolch.agent.api.ComponentContainer; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.UserRep; +import li.strolch.privilege.model.UserState; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.StrolchSessionHandler; diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/UserSessionsService.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/UserSessionsService.java index d11cee79b..2b9451ff2 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/UserSessionsService.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/UserSessionsService.java @@ -31,6 +31,7 @@ import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.StrolchSessionHandler; @@ -40,8 +41,6 @@ import li.strolch.rest.model.UserSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; - @Path("strolch/sessions") public class UserSessionsService { diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/VersionQuery.java b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/VersionQuery.java index 3d0be058c..4f62f2521 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/VersionQuery.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/endpoint/VersionQuery.java @@ -25,9 +25,9 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import li.strolch.agent.api.VersionQueryResult; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; -import ch.eitchnet.privilege.model.Certificate; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java b/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java index 75095c390..546df349f 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java @@ -31,11 +31,11 @@ import javax.ws.rs.ext.Provider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchRestfulConstants; import li.strolch.rest.StrolchSessionHandler; +import li.strolch.utils.helper.StringHelper; /** * @author Reto Breitenmoser diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationResponseFilter.java b/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationResponseFilter.java index a02481b2b..00a1f3c39 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationResponseFilter.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/filters/AuthenicationResponseFilter.java @@ -25,7 +25,7 @@ import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.ext.Provider; -import ch.eitchnet.privilege.model.Certificate; +import li.strolch.privilege.model.Certificate; /** * @author Reto Breitenmoser diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/helper/RestfulHelper.java b/li.strolch.rest/src/main/java/li/strolch/rest/helper/RestfulHelper.java index 90caef104..73e24bf48 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/helper/RestfulHelper.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/helper/RestfulHelper.java @@ -20,10 +20,10 @@ import java.util.Locale; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.HttpHeaders; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.privilege.model.Certificate; import li.strolch.rest.StrolchRestfulConstants; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/model/AuditQueryResult.java b/li.strolch.rest/src/main/java/li/strolch/rest/model/AuditQueryResult.java index 6c382bf9e..03b8cd0ec 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/model/AuditQueryResult.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/model/AuditQueryResult.java @@ -24,7 +24,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import li.strolch.model.audit.Audit; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; @XmlRootElement(name = "AuditQueryResult") @XmlAccessorType(XmlAccessType.NONE) diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/model/LoginResult.java b/li.strolch.rest/src/main/java/li/strolch/rest/model/LoginResult.java index e5e5831ba..a299d072a 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/model/LoginResult.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/model/LoginResult.java @@ -26,7 +26,7 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import ch.eitchnet.utils.xml.XmlKeyValue; +import li.strolch.utils.xml.XmlKeyValue; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/model/OrderOverview.java b/li.strolch.rest/src/main/java/li/strolch/rest/model/OrderOverview.java index 46b6581cf..824e96efe 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/model/OrderOverview.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/model/OrderOverview.java @@ -24,7 +24,7 @@ import javax.xml.bind.annotation.XmlRootElement; import li.strolch.model.Order; import li.strolch.model.State; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/model/Result.java b/li.strolch.rest/src/main/java/li/strolch/rest/model/Result.java index 3f0a20ac8..0c3866a40 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/model/Result.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/model/Result.java @@ -22,8 +22,8 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.service.api.ServiceResult; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/model/StringListResult.java b/li.strolch.rest/src/main/java/li/strolch/rest/model/StringListResult.java index 9b77a78ec..a2b5090c8 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/model/StringListResult.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/model/StringListResult.java @@ -23,7 +23,7 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; @XmlRootElement(name = "StringListResult") @XmlAccessorType(XmlAccessType.NONE) diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/model/UserSession.java b/li.strolch.rest/src/main/java/li/strolch/rest/model/UserSession.java index b86d19a1d..803e20a35 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/model/UserSession.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/model/UserSession.java @@ -25,7 +25,7 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import ch.eitchnet.privilege.model.Certificate; +import li.strolch.privilege.model.Certificate; @XmlRootElement(name = "UserSession") @XmlAccessorType(XmlAccessType.NONE) diff --git a/li.strolch.rest/src/main/java/li/strolch/rest/model/visitor/ToAuditQueryVisitor.java b/li.strolch.rest/src/main/java/li/strolch/rest/model/visitor/ToAuditQueryVisitor.java index 7c2dc9552..95a45215c 100644 --- a/li.strolch.rest/src/main/java/li/strolch/rest/model/visitor/ToAuditQueryVisitor.java +++ b/li.strolch.rest/src/main/java/li/strolch/rest/model/visitor/ToAuditQueryVisitor.java @@ -23,8 +23,8 @@ import li.strolch.rest.model.ActionSelection; import li.strolch.rest.model.AuditQuery; import li.strolch.rest.model.DateRange; import li.strolch.rest.model.IdentitySelection; -import ch.eitchnet.utils.StringMatchMode; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.StringMatchMode; +import li.strolch.utils.helper.StringHelper; public class ToAuditQueryVisitor { @@ -41,7 +41,7 @@ public class ToAuditQueryVisitor { if (dateRange == null || dateRange.getFromDate() == null || dateRange.getToDate() == null) { throw new IllegalArgumentException("DateRange on AuditQuery is not valid or is missing!"); } - ch.eitchnet.utils.collections.DateRange dr = new ch.eitchnet.utils.collections.DateRange().from( + li.strolch.utils.collections.DateRange dr = new li.strolch.utils.collections.DateRange().from( dateRange.getFromDate(), dateRange.isFromInclusive()).to(dateRange.getToDate(), dateRange.isToInclusive()); diff --git a/li.strolch.rest/src/test/resources/log4j.xml b/li.strolch.rest/src/test/resources/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.rest/src/test/resources/log4j.xml +++ b/li.strolch.rest/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml b/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml +++ b/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeModel.xml b/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeModel.xml index d0c963b04..0110c6a4d 100644 --- a/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeModel.xml +++ b/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeModel.xml @@ -57,7 +57,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeRoles.xml b/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeRoles.xml index a2dbfc80e..a113935c1 100644 --- a/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeRoles.xml +++ b/li.strolch.rest/src/test/resources/withPrivilegeRuntime/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.service/src/main/java/li/strolch/command/AddOrderCollectionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/AddOrderCollectionCommand.java index b8cda78ed..9c70b8a9d 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/AddOrderCollectionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/AddOrderCollectionCommand.java @@ -24,7 +24,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/AddOrderCommand.java b/li.strolch.service/src/main/java/li/strolch/command/AddOrderCommand.java index 96a12dcde..7b8e30de0 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/AddOrderCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/AddOrderCommand.java @@ -23,7 +23,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/AddResourceCollectionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/AddResourceCollectionCommand.java index e3c15b5fa..72a5d18fb 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/AddResourceCollectionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/AddResourceCollectionCommand.java @@ -24,7 +24,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/AddResourceCommand.java b/li.strolch.service/src/main/java/li/strolch/command/AddResourceCommand.java index efed9e001..7b9615a48 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/AddResourceCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/AddResourceCommand.java @@ -23,7 +23,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCollectionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCollectionCommand.java index fd0b73b9e..1fca5b8e9 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCollectionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCollectionCommand.java @@ -24,7 +24,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCommand.java b/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCommand.java index 908aa7958..ab1140540 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/RemoveOrderCommand.java @@ -23,7 +23,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCollectionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCollectionCommand.java index 438746af5..873cb5954 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCollectionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCollectionCommand.java @@ -24,7 +24,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCommand.java b/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCommand.java index 1a0e5b2b4..c392eceff 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/RemoveResourceCommand.java @@ -23,7 +23,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCollectionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCollectionCommand.java index 693af78dd..2959f7089 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCollectionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCollectionCommand.java @@ -24,7 +24,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCommand.java b/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCommand.java index 48c81ffd2..450570037 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/UpdateOrderCommand.java @@ -23,7 +23,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCollectionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCollectionCommand.java index 031ca510c..5dc693af3 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCollectionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCollectionCommand.java @@ -24,7 +24,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCommand.java b/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCommand.java index 6c3194ee9..e1638b582 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/UpdateResourceCommand.java @@ -23,7 +23,7 @@ import li.strolch.exception.StrolchException; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/XmlExportModelCommand.java b/li.strolch.service/src/main/java/li/strolch/command/XmlExportModelCommand.java index 52969fd42..f32915d01 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/XmlExportModelCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/XmlExportModelCommand.java @@ -15,7 +15,7 @@ */ package li.strolch.command; -import static ch.eitchnet.utils.helper.StringHelper.UNDERLINE; +import static li.strolch.utils.helper.StringHelper.UNDERLINE; import java.io.File; import java.io.FileOutputStream; @@ -53,7 +53,7 @@ import li.strolch.model.xml.ResourceToSaxWriterVisitor; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.runtime.StrolchConstants; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/XmlImportModelCommand.java b/li.strolch.service/src/main/java/li/strolch/command/XmlImportModelCommand.java index ad2db34c3..ecdac62c0 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/XmlImportModelCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/XmlImportModelCommand.java @@ -24,7 +24,7 @@ import li.strolch.model.ModelStatistics; import li.strolch.model.xml.XmlModelSaxFileReader; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/parameter/AddParameterCommand.java b/li.strolch.service/src/main/java/li/strolch/command/parameter/AddParameterCommand.java index 491220c54..1273e8720 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/parameter/AddParameterCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/parameter/AddParameterCommand.java @@ -25,7 +25,7 @@ import li.strolch.model.parameter.Parameter; import li.strolch.persistence.api.StrolchPersistenceException; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/parameter/RemoveParameterCommand.java b/li.strolch.service/src/main/java/li/strolch/command/parameter/RemoveParameterCommand.java index 5ce3c0347..2d44e3f5d 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/parameter/RemoveParameterCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/parameter/RemoveParameterCommand.java @@ -25,7 +25,7 @@ import li.strolch.model.parameter.Parameter; import li.strolch.persistence.api.StrolchPersistenceException; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/parameter/SetParameterCommand.java b/li.strolch.service/src/main/java/li/strolch/command/parameter/SetParameterCommand.java index a40b415f3..bcffd8189 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/parameter/SetParameterCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/parameter/SetParameterCommand.java @@ -22,7 +22,7 @@ import li.strolch.model.parameter.Parameter; import li.strolch.model.visitor.SetParameterValueVisitor; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/command/plan/AssignActionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/plan/AssignActionCommand.java index a9047d915..fadddf8ba 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/plan/AssignActionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/plan/AssignActionCommand.java @@ -19,7 +19,7 @@ import li.strolch.agent.api.ComponentContainer; import li.strolch.model.Resource; import li.strolch.model.activity.Action; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * Command to assign an {@link Action} to a new {@link Resource}. diff --git a/li.strolch.service/src/main/java/li/strolch/command/plan/PlanActionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/plan/PlanActionCommand.java index acc4f9dae..cfe86e67f 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/plan/PlanActionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/plan/PlanActionCommand.java @@ -22,7 +22,7 @@ import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.IValueChange; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * Command to plan an {@link Action} to a {@link Resource}. This {@link Command} diff --git a/li.strolch.service/src/main/java/li/strolch/command/plan/PlanActivityCommand.java b/li.strolch.service/src/main/java/li/strolch/command/plan/PlanActivityCommand.java index d90f65a44..dab68784f 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/plan/PlanActivityCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/plan/PlanActivityCommand.java @@ -27,7 +27,7 @@ import li.strolch.model.timedstate.StrolchTimedState; import li.strolch.model.timevalue.IValueChange; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.Command; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * Command to plan an {@link Activity} to a {@link Resource}. This diff --git a/li.strolch.service/src/main/java/li/strolch/command/plan/ShiftActionCommand.java b/li.strolch.service/src/main/java/li/strolch/command/plan/ShiftActionCommand.java index 84b1db80f..ac4fae40d 100644 --- a/li.strolch.service/src/main/java/li/strolch/command/plan/ShiftActionCommand.java +++ b/li.strolch.service/src/main/java/li/strolch/command/plan/ShiftActionCommand.java @@ -22,7 +22,7 @@ import li.strolch.model.State; import li.strolch.model.timevalue.IValue; import li.strolch.model.timevalue.IValueChange; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Martin Smock diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/CodeMigration.java b/li.strolch.service/src/main/java/li/strolch/migrations/CodeMigration.java index 1cd48a1b7..5cab7782e 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/CodeMigration.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/CodeMigration.java @@ -21,8 +21,8 @@ import li.strolch.agent.api.ComponentContainer; import li.strolch.command.parameter.SetParameterCommand; import li.strolch.model.Resource; import li.strolch.model.parameter.StringParameter; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.Version; +import li.strolch.privilege.model.Certificate; +import li.strolch.utils.Version; public class CodeMigration extends Migration { diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/CurrentMigrationVersionQuery.java b/li.strolch.service/src/main/java/li/strolch/migrations/CurrentMigrationVersionQuery.java index ae056e4d1..777dadce5 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/CurrentMigrationVersionQuery.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/CurrentMigrationVersionQuery.java @@ -29,9 +29,9 @@ import li.strolch.agent.api.StrolchRealm; import li.strolch.model.Resource; import li.strolch.model.parameter.StringParameter; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.configuration.StrolchConfigurationException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.Version; +import li.strolch.utils.Version; public class CurrentMigrationVersionQuery { diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/DataMigration.java b/li.strolch.service/src/main/java/li/strolch/migrations/DataMigration.java index 3bdea55d2..69470c330 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/DataMigration.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/DataMigration.java @@ -27,8 +27,8 @@ import li.strolch.model.ModelStatistics; import li.strolch.model.Resource; import li.strolch.model.parameter.StringParameter; import li.strolch.persistence.api.StrolchTransaction; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.Version; +import li.strolch.privilege.model.Certificate; +import li.strolch.utils.Version; public class DataMigration extends Migration { diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/Migration.java b/li.strolch.service/src/main/java/li/strolch/migrations/Migration.java index d1ecb7878..ec3342383 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/Migration.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/Migration.java @@ -24,13 +24,12 @@ import li.strolch.model.ParameterBag; import li.strolch.model.Resource; import li.strolch.model.parameter.StringParameter; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; +import li.strolch.utils.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.Version; - public abstract class Migration { public static final String MIGRATIONS_TYPE = "Migrations"; diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/MigrationVersion.java b/li.strolch.service/src/main/java/li/strolch/migrations/MigrationVersion.java index 6ead4d919..ecd4c9a73 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/MigrationVersion.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/MigrationVersion.java @@ -3,7 +3,7 @@ */ package li.strolch.migrations; -import ch.eitchnet.utils.Version; +import li.strolch.utils.Version; /** * Migration versions for data and code migrations diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java b/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java index 184691b84..661ce138e 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/Migrations.java @@ -27,15 +27,14 @@ import java.util.SortedSet; import java.util.TreeSet; import li.strolch.agent.api.ComponentContainer; +import li.strolch.privilege.model.Certificate; +import li.strolch.utils.Version; +import li.strolch.utils.collections.MapOfLists; +import li.strolch.utils.dbc.DBC; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.Version; -import ch.eitchnet.utils.collections.MapOfLists; -import ch.eitchnet.utils.dbc.DBC; - public class Migrations { private static final Logger logger = LoggerFactory.getLogger(Migrations.class); diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java b/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java index 6ac3a33e7..e5bbeddd5 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/MigrationsHandler.java @@ -24,12 +24,12 @@ import java.util.concurrent.TimeUnit; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.RealmHandler; import li.strolch.agent.api.StrolchComponent; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.runtime.configuration.RuntimeConfiguration; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.Version; -import ch.eitchnet.utils.collections.MapOfLists; +import li.strolch.utils.Version; +import li.strolch.utils.collections.MapOfLists; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/QueryCurrentVersionsAction.java b/li.strolch.service/src/main/java/li/strolch/migrations/QueryCurrentVersionsAction.java index af12fdc03..df6181d67 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/QueryCurrentVersionsAction.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/QueryCurrentVersionsAction.java @@ -15,8 +15,8 @@ */ package li.strolch.migrations; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.PrivilegeContext; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/migrations/RunMigrationsAction.java b/li.strolch.service/src/main/java/li/strolch/migrations/RunMigrationsAction.java index 4ee126ec6..22fe00ca8 100644 --- a/li.strolch.service/src/main/java/li/strolch/migrations/RunMigrationsAction.java +++ b/li.strolch.service/src/main/java/li/strolch/migrations/RunMigrationsAction.java @@ -17,8 +17,8 @@ package li.strolch.migrations; import java.util.Map; -import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.PrivilegeContext; +import li.strolch.privilege.handler.SystemUserAction; +import li.strolch.privilege.model.PrivilegeContext; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/ClearModelService.java b/li.strolch.service/src/main/java/li/strolch/service/ClearModelService.java index b1a5d3812..b5ac46b9a 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/ClearModelService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/ClearModelService.java @@ -22,7 +22,7 @@ import li.strolch.model.ModelStatistics; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceResult; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/XmlExportModelService.java b/li.strolch.service/src/main/java/li/strolch/service/XmlExportModelService.java index ae7feb45b..adc36c120 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/XmlExportModelService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/XmlExportModelService.java @@ -18,12 +18,12 @@ package li.strolch.service; import java.io.File; import java.text.MessageFormat; -import ch.eitchnet.utils.dbc.DBC; import li.strolch.command.XmlExportModelCommand; import li.strolch.model.ModelStatistics; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceResult; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/XmlImportModelService.java b/li.strolch.service/src/main/java/li/strolch/service/XmlImportModelService.java index f577b0a33..afb245366 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/XmlImportModelService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/XmlImportModelService.java @@ -24,7 +24,7 @@ import li.strolch.model.ModelStatistics; import li.strolch.persistence.api.StrolchTransaction; import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceResultState; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionHandler.java b/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionHandler.java index 81b095635..29fce8d13 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionHandler.java +++ b/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionHandler.java @@ -21,16 +21,16 @@ import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.helper.ExceptionHelper; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchComponent; import li.strolch.exception.StrolchException; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.configuration.ComponentConfiguration; import li.strolch.service.api.Service; import li.strolch.service.api.ServiceArgument; import li.strolch.service.api.ServiceHandler; import li.strolch.service.api.ServiceResult; +import li.strolch.utils.helper.ExceptionHelper; /** * The {@link ServiceExecutionHandler} is used to perform long running services so that no singletons etc. are required. diff --git a/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionStatus.java b/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionStatus.java index 91f4d72a7..6824e8ca6 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionStatus.java +++ b/li.strolch.service/src/main/java/li/strolch/service/executor/ServiceExecutionStatus.java @@ -21,7 +21,7 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; import li.strolch.service.api.ServiceResult; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleArgument.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleArgument.java index 79a7cedac..4e9014eeb 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleArgument.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleArgument.java @@ -15,7 +15,7 @@ */ package li.strolch.service.privilege.roles; -import ch.eitchnet.privilege.model.PrivilegeRep; +import li.strolch.privilege.model.PrivilegeRep; import li.strolch.service.api.ServiceArgument; public class PrivilegeAddOrReplacePrivilegeOnRoleArgument extends ServiceArgument { diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleService.java index 3c7d0eb05..7d39db95e 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddOrReplacePrivilegeOnRoleService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.roles; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.RoleRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.RoleRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddRoleService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddRoleService.java index 85adf71a5..a2685bddb 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddRoleService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeAddRoleService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.roles; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.RoleRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.RoleRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleService.java index 5c8421892..77cfc29f2 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemovePrivilegeFromRoleService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.roles; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.RoleRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.RoleRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemoveRoleService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemoveRoleService.java index 95c1bf2cd..aee46596b 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemoveRoleService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRemoveRoleService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.roles; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.RoleRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.RoleRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRoleArgument.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRoleArgument.java index df6a1773f..a7d258442 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRoleArgument.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRoleArgument.java @@ -15,8 +15,8 @@ */ package li.strolch.service.privilege.roles; +import li.strolch.privilege.model.RoleRep; import li.strolch.service.api.ServiceArgument; -import ch.eitchnet.privilege.model.RoleRep; public class PrivilegeRoleArgument extends ServiceArgument { private static final long serialVersionUID = 1L; diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRoleResult.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRoleResult.java index 463505811..9902b8950 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRoleResult.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeRoleResult.java @@ -15,9 +15,9 @@ */ package li.strolch.service.privilege.roles; +import li.strolch.privilege.model.RoleRep; import li.strolch.service.api.ServiceResult; import li.strolch.service.api.ServiceResultState; -import ch.eitchnet.privilege.model.RoleRep; public class PrivilegeRoleResult extends ServiceResult { private static final long serialVersionUID = 1L; diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeUpdateRoleService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeUpdateRoleService.java index de11892c7..50ffc6fed 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeUpdateRoleService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/roles/PrivilegeUpdateRoleService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.roles; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.RoleRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.RoleRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddRoleToUserService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddRoleToUserService.java index 31be74506..4d99a769c 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddRoleToUserService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddRoleToUserService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.users; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.UserRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.UserRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddUserService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddUserService.java index 8f940a678..ecf4f3aaa 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddUserService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeAddUserService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.users; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.UserRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.UserRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveRoleFromUserService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveRoleFromUserService.java index 93e860d47..a0b9a33a7 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveRoleFromUserService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveRoleFromUserService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.users; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.UserRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.UserRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserService.java index 80a3ed6c6..6c8e6a3a1 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeRemoveUserService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.users; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.UserRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.UserRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserLocaleService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserLocaleService.java index 6e56be423..eab459d5b 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserLocaleService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserLocaleService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.users; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.UserRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.UserRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserPasswordService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserPasswordService.java index 8c2dd96d9..924ac4775 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserPasswordService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserPasswordService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.users; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceResult; -import ch.eitchnet.privilege.handler.PrivilegeHandler; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserStateArgument.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserStateArgument.java index 6a5333099..98c56e196 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserStateArgument.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserStateArgument.java @@ -15,8 +15,8 @@ */ package li.strolch.service.privilege.users; +import li.strolch.privilege.model.UserState; import li.strolch.service.api.ServiceArgument; -import ch.eitchnet.privilege.model.UserState; public class PrivilegeSetUserStateArgument extends ServiceArgument { private static final long serialVersionUID = 1L; diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserStateService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserStateService.java index a6bd59d13..99fd7d8ce 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserStateService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeSetUserStateService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.users; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.UserRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.UserRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserService.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserService.java index f197713ff..1b647bb55 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserService.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUpdateUserService.java @@ -18,10 +18,10 @@ package li.strolch.service.privilege.users; import li.strolch.model.audit.AccessType; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.handler.PrivilegeHandler; +import li.strolch.privilege.model.UserRep; import li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants; import li.strolch.service.api.AbstractService; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.UserRep; /** * @author Robert von Burg diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUserArgument.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUserArgument.java index db0402e09..f7b8bc6ed 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUserArgument.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUserArgument.java @@ -15,8 +15,8 @@ */ package li.strolch.service.privilege.users; +import li.strolch.privilege.model.UserRep; import li.strolch.service.api.ServiceArgument; -import ch.eitchnet.privilege.model.UserRep; public class PrivilegeUserArgument extends ServiceArgument { private static final long serialVersionUID = 1L; diff --git a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUserResult.java b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUserResult.java index 51f46266c..94ee5553a 100644 --- a/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUserResult.java +++ b/li.strolch.service/src/main/java/li/strolch/service/privilege/users/PrivilegeUserResult.java @@ -15,9 +15,9 @@ */ package li.strolch.service.privilege.users; +import li.strolch.privilege.model.UserRep; import li.strolch.service.api.ServiceResult; import li.strolch.service.api.ServiceResultState; -import ch.eitchnet.privilege.model.UserRep; public class PrivilegeUserResult extends ServiceResult { private static final long serialVersionUID = 1L; diff --git a/li.strolch.service/src/test/java/li/strolch/command/AbstractRealmCommandTest.java b/li.strolch.service/src/test/java/li/strolch/command/AbstractRealmCommandTest.java index 967f14d28..8d4481dfc 100644 --- a/li.strolch.service/src/test/java/li/strolch/command/AbstractRealmCommandTest.java +++ b/li.strolch.service/src/test/java/li/strolch/command/AbstractRealmCommandTest.java @@ -28,6 +28,7 @@ import java.io.File; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchRealm; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.service.api.Command; import li.strolch.service.api.ServiceHandler; import li.strolch.testbase.runtime.RuntimeMock; @@ -38,8 +39,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import ch.eitchnet.privilege.model.Certificate; - /** * @author Robert von Burg */ diff --git a/li.strolch.service/src/test/java/li/strolch/migrations/MigrationsTest.java b/li.strolch.service/src/test/java/li/strolch/migrations/MigrationsTest.java index 3e4a5b4b3..5503a8192 100644 --- a/li.strolch.service/src/test/java/li/strolch/migrations/MigrationsTest.java +++ b/li.strolch.service/src/test/java/li/strolch/migrations/MigrationsTest.java @@ -29,17 +29,16 @@ import li.strolch.command.RemoveOrderCommand; import li.strolch.model.ModelGenerator; import li.strolch.model.Order; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.testbase.runtime.RuntimeMock; +import li.strolch.utils.Version; +import li.strolch.utils.collections.MapOfLists; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.Version; -import ch.eitchnet.utils.collections.MapOfLists; - public class MigrationsTest { public static final String RUNTIME_PATH = "target/migrationstest/"; //$NON-NLS-1$ diff --git a/li.strolch.service/src/test/java/li/strolch/service/FlushTxTest.java b/li.strolch.service/src/test/java/li/strolch/service/FlushTxTest.java index 8f59bec5e..0849dd1ab 100644 --- a/li.strolch.service/src/test/java/li/strolch/service/FlushTxTest.java +++ b/li.strolch.service/src/test/java/li/strolch/service/FlushTxTest.java @@ -25,11 +25,10 @@ import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceArgument; import li.strolch.service.api.ServiceResult; import li.strolch.service.test.AbstractRealmServiceTest; +import li.strolch.utils.dbc.DBC; import org.junit.Test; -import ch.eitchnet.utils.dbc.DBC; - public class FlushTxTest extends AbstractRealmServiceTest { @Test diff --git a/li.strolch.service/src/test/java/li/strolch/service/TxTest.java b/li.strolch.service/src/test/java/li/strolch/service/TxTest.java index ee9ef3e25..99a2ba526 100644 --- a/li.strolch.service/src/test/java/li/strolch/service/TxTest.java +++ b/li.strolch.service/src/test/java/li/strolch/service/TxTest.java @@ -26,12 +26,11 @@ import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceArgument; import li.strolch.service.api.ServiceResult; import li.strolch.service.test.AbstractRealmServiceTest; +import li.strolch.utils.dbc.DBC; import org.junit.Ignore; import org.junit.Test; -import ch.eitchnet.utils.dbc.DBC; - public class TxTest extends AbstractRealmServiceTest { @Test diff --git a/li.strolch.service/src/test/java/li/strolch/service/test/AbstractRealmServiceTest.java b/li.strolch.service/src/test/java/li/strolch/service/test/AbstractRealmServiceTest.java index edb419b22..fc0816869 100644 --- a/li.strolch.service/src/test/java/li/strolch/service/test/AbstractRealmServiceTest.java +++ b/li.strolch.service/src/test/java/li/strolch/service/test/AbstractRealmServiceTest.java @@ -27,12 +27,11 @@ import org.postgresql.Driver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.db.DbSchemaVersionCheck; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.Version; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchRealm; +import li.strolch.db.DbSchemaVersionCheck; import li.strolch.persistence.postgresql.PostgreSqlPersistenceHandler; +import li.strolch.privilege.model.Certificate; import li.strolch.service.XmlImportModelArgument; import li.strolch.service.XmlImportModelResult; import li.strolch.service.XmlImportModelService; @@ -42,6 +41,7 @@ import li.strolch.service.api.ServiceHandler; import li.strolch.service.api.ServiceResult; import li.strolch.service.api.ServiceResultState; import li.strolch.testbase.runtime.RuntimeMock; +import li.strolch.utils.Version; /** * @author Robert von Burg diff --git a/li.strolch.service/src/test/java/li/strolch/service/test/GreetingServiceTest.java b/li.strolch.service/src/test/java/li/strolch/service/test/GreetingServiceTest.java index ae9f2c278..5c3dec1ba 100644 --- a/li.strolch.service/src/test/java/li/strolch/service/test/GreetingServiceTest.java +++ b/li.strolch.service/src/test/java/li/strolch/service/test/GreetingServiceTest.java @@ -17,14 +17,14 @@ package li.strolch.service.test; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; + +import li.strolch.privilege.model.Certificate; import li.strolch.service.test.model.GreetingResult; import li.strolch.service.test.model.GreetingService; import li.strolch.service.test.model.GreetingService.GreetingArgument; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; - /** * @author Robert von Burg */ diff --git a/li.strolch.service/src/test/java/li/strolch/service/test/LockingTest.java b/li.strolch.service/src/test/java/li/strolch/service/test/LockingTest.java index 6e952b041..fd1577d37 100644 --- a/li.strolch.service/src/test/java/li/strolch/service/test/LockingTest.java +++ b/li.strolch.service/src/test/java/li/strolch/service/test/LockingTest.java @@ -28,10 +28,10 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; import li.strolch.model.Locator; import li.strolch.model.Resource; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceArgument; import li.strolch.service.api.ServiceHandler; diff --git a/li.strolch.service/src/test/java/li/strolch/service/test/ServiceTest.java b/li.strolch.service/src/test/java/li/strolch/service/test/ServiceTest.java index 601d6d495..c730704da 100644 --- a/li.strolch.service/src/test/java/li/strolch/service/test/ServiceTest.java +++ b/li.strolch.service/src/test/java/li/strolch/service/test/ServiceTest.java @@ -27,10 +27,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import ch.eitchnet.privilege.base.AccessDeniedException; -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.UserState; +import li.strolch.privilege.base.AccessDeniedException; +import li.strolch.privilege.base.PrivilegeException; +import li.strolch.privilege.model.Certificate; +import li.strolch.privilege.model.UserState; import li.strolch.service.api.ServiceResult; import li.strolch.service.test.model.GreetingResult; import li.strolch.service.test.model.GreetingService; diff --git a/li.strolch.service/src/test/java/li/strolch/service/test/XmlExportServiceTest.java b/li.strolch.service/src/test/java/li/strolch/service/test/XmlExportServiceTest.java index c8699c5c8..6e42318ce 100644 --- a/li.strolch.service/src/test/java/li/strolch/service/test/XmlExportServiceTest.java +++ b/li.strolch.service/src/test/java/li/strolch/service/test/XmlExportServiceTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import java.io.File; import java.io.FilenameFilter; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.service.XmlExportModelArgument; import li.strolch.service.XmlExportModelService; @@ -34,8 +35,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.model.Certificate; - /** * @author Robert von Burg */ diff --git a/li.strolch.service/src/test/java/li/strolch/service/test/model/GreetingService.java b/li.strolch.service/src/test/java/li/strolch/service/test/model/GreetingService.java index 656d53abe..df7f331a7 100644 --- a/li.strolch.service/src/test/java/li/strolch/service/test/model/GreetingService.java +++ b/li.strolch.service/src/test/java/li/strolch/service/test/model/GreetingService.java @@ -20,7 +20,7 @@ import java.text.MessageFormat; import li.strolch.service.api.AbstractService; import li.strolch.service.api.ServiceArgument; import li.strolch.service.test.model.GreetingService.GreetingArgument; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.service/src/test/resources/log4j.xml b/li.strolch.service/src/test/resources/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.service/src/test/resources/log4j.xml +++ b/li.strolch.service/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.service/src/test/resources/migrationstest/config/PrivilegeConfig.xml b/li.strolch.service/src/test/resources/migrationstest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.service/src/test/resources/migrationstest/config/PrivilegeConfig.xml +++ b/li.strolch.service/src/test/resources/migrationstest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.service/src/test/resources/migrationstest/config/PrivilegeRoles.xml b/li.strolch.service/src/test/resources/migrationstest/config/PrivilegeRoles.xml index 114bbe099..f032d7ffe 100644 --- a/li.strolch.service/src/test/resources/migrationstest/config/PrivilegeRoles.xml +++ b/li.strolch.service/src/test/resources/migrationstest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms li.strolch.migrations.QueryCurrentVersionsAction li.strolch.migrations.RunMigrationsAction diff --git a/li.strolch.service/src/test/resources/svctest/config/PrivilegeConfig.xml b/li.strolch.service/src/test/resources/svctest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.service/src/test/resources/svctest/config/PrivilegeConfig.xml +++ b/li.strolch.service/src/test/resources/svctest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.service/src/test/resources/svctest/config/PrivilegeRoles.xml b/li.strolch.service/src/test/resources/svctest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.service/src/test/resources/svctest/config/PrivilegeRoles.xml +++ b/li.strolch.service/src/test/resources/svctest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.service/src/test/resources/transienttest/config/PrivilegeConfig.xml b/li.strolch.service/src/test/resources/transienttest/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.service/src/test/resources/transienttest/config/PrivilegeConfig.xml +++ b/li.strolch.service/src/test/resources/transienttest/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.service/src/test/resources/transienttest/config/PrivilegeRoles.xml b/li.strolch.service/src/test/resources/transienttest/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.service/src/test/resources/transienttest/config/PrivilegeRoles.xml +++ b/li.strolch.service/src/test/resources/transienttest/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml b/li.strolch.service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml index 2faba827e..cfab85b24 100644 --- a/li.strolch.service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml +++ b/li.strolch.service/src/test/resources/withPrivilegeRuntime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,9 +24,9 @@ - - - + + + \ No newline at end of file diff --git a/li.strolch.service/src/test/resources/withPrivilegeRuntime/config/PrivilegeRoles.xml b/li.strolch.service/src/test/resources/withPrivilegeRuntime/config/PrivilegeRoles.xml index 207e89bc0..3a412fb38 100644 --- a/li.strolch.service/src/test/resources/withPrivilegeRuntime/config/PrivilegeRoles.xml +++ b/li.strolch.service/src/test/resources/withPrivilegeRuntime/config/PrivilegeRoles.xml @@ -2,7 +2,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AbstractModelTest.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AbstractModelTest.java index 294016477..6abb15e7f 100644 --- a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AbstractModelTest.java +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AbstractModelTest.java @@ -16,6 +16,7 @@ package li.strolch.testbase.runtime; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.StrolchConstants; import li.strolch.runtime.privilege.PrivilegeHandler; @@ -23,8 +24,6 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; - public abstract class AbstractModelTest { protected static final Logger logger = LoggerFactory.getLogger(AbstractModelTest.class); diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AuditModelTestRunner.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AuditModelTestRunner.java index 20f14e66d..32591ca8f 100644 --- a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AuditModelTestRunner.java +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/AuditModelTestRunner.java @@ -33,9 +33,9 @@ import li.strolch.agent.api.StrolchRealm; import li.strolch.model.ModelGenerator; import li.strolch.model.audit.Audit; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.collections.DateRange; +import li.strolch.utils.collections.DateRange; /** * @author Robert von Burg diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/OrderModelTestRunner.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/OrderModelTestRunner.java index db5bee2e8..ffd2af630 100644 --- a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/OrderModelTestRunner.java +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/OrderModelTestRunner.java @@ -36,8 +36,8 @@ import li.strolch.agent.impl.DataStoreMode; import li.strolch.model.Order; import li.strolch.model.parameter.Parameter; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; @SuppressWarnings("nls") public class OrderModelTestRunner { diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ResourceModelTestRunner.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ResourceModelTestRunner.java index d2dc212d3..fa259f29b 100644 --- a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ResourceModelTestRunner.java +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/ResourceModelTestRunner.java @@ -36,8 +36,8 @@ import li.strolch.agent.impl.DataStoreMode; import li.strolch.model.Resource; import li.strolch.model.parameter.Parameter; import li.strolch.persistence.api.StrolchTransaction; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; @SuppressWarnings("nls") public class ResourceModelTestRunner { diff --git a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/RuntimeMock.java b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/RuntimeMock.java index c9f5a183c..25e9f35d3 100644 --- a/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/RuntimeMock.java +++ b/li.strolch.testbase/src/main/java/li/strolch/testbase/runtime/RuntimeMock.java @@ -26,20 +26,20 @@ import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.utils.helper.StringHelper; import li.strolch.agent.api.ComponentContainer; import li.strolch.agent.api.StrolchAgent; import li.strolch.agent.api.StrolchBootstrapper; import li.strolch.agent.api.StrolchRealm; import li.strolch.agent.api.StrolchVersion; +import li.strolch.privilege.model.Certificate; import li.strolch.runtime.privilege.PrivilegeHandler; import li.strolch.service.api.Service; import li.strolch.service.api.ServiceArgument; import li.strolch.service.api.ServiceHandler; import li.strolch.service.api.ServiceResult; import li.strolch.service.api.ServiceResultState; +import li.strolch.utils.helper.FileHelper; +import li.strolch.utils.helper.StringHelper; public final class RuntimeMock { diff --git a/li.strolch.tutorialapp/src/main/resources/log4j.xml b/li.strolch.tutorialapp/src/main/resources/log4j.xml index 1d2f3b080..c886d766d 100644 --- a/li.strolch.tutorialapp/src/main/resources/log4j.xml +++ b/li.strolch.tutorialapp/src/main/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.tutorialapp/src/runtime/config/PrivilegeConfig.xml b/li.strolch.tutorialapp/src/runtime/config/PrivilegeConfig.xml index 706602750..aaf42b2c6 100644 --- a/li.strolch.tutorialapp/src/runtime/config/PrivilegeConfig.xml +++ b/li.strolch.tutorialapp/src/runtime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,7 +24,7 @@ - + \ No newline at end of file diff --git a/li.strolch.tutorialapp/src/runtime/config/PrivilegeRoles.xml b/li.strolch.tutorialapp/src/runtime/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.tutorialapp/src/runtime/config/PrivilegeRoles.xml +++ b/li.strolch.tutorialapp/src/runtime/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.tutorialwebapp/src/main/resources/log4j.xml b/li.strolch.tutorialwebapp/src/main/resources/log4j.xml index 6bb2f5090..86ea6fcdf 100644 --- a/li.strolch.tutorialwebapp/src/main/resources/log4j.xml +++ b/li.strolch.tutorialwebapp/src/main/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml b/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml index 706602750..aaf42b2c6 100644 --- a/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml +++ b/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,7 +24,7 @@ - + \ No newline at end of file diff --git a/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/PrivilegeRoles.xml b/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/PrivilegeRoles.xml index bff38da9f..3a9e8ab07 100644 --- a/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/PrivilegeRoles.xml +++ b/li.strolch.tutorialwebapp/src/main/webapp/WEB-INF/config/PrivilegeRoles.xml @@ -1,7 +1,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/li.strolch.utils/README.md b/li.strolch.utils/README.md index 46c7a0bcd..76345938f 100644 --- a/li.strolch.utils/README.md +++ b/li.strolch.utils/README.md @@ -1,7 +1,7 @@ -ch.eitchnet.utils +li.strolch.utils ====================== -[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=ch.eitchnet.utils)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.utils/) +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=li.strolch.utils)](http://jenkins.eitchnet.ch/view/li.strolch/job/li.strolch.utils/) Java Utilites which ease daily work when programming in the Java language diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommandKey.java b/li.strolch.utils/src/main/java/li/strolch/communication/CommandKey.java similarity index 95% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/CommandKey.java rename to li.strolch.utils/src/main/java/li/strolch/communication/CommandKey.java index f34389bf0..a0f5eff3a 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommandKey.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/CommandKey.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java b/li.strolch.utils/src/main/java/li/strolch/communication/CommunicationConnection.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java rename to li.strolch.utils/src/main/java/li/strolch/communication/CommunicationConnection.java index 46db7c6ba..770f0de66 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationConnection.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/CommunicationConnection.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import java.text.MessageFormat; import java.util.ArrayList; @@ -25,10 +25,10 @@ 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; +import li.strolch.communication.IoMessage.State; +import li.strolch.utils.collections.MapOfLists; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java b/li.strolch.utils/src/main/java/li/strolch/communication/CommunicationEndpoint.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java rename to li.strolch.utils/src/main/java/li/strolch/communication/CommunicationEndpoint.java index 54579f7b9..b56f9f12c 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/CommunicationEndpoint.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/CommunicationEndpoint.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionException.java similarity index 93% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java rename to li.strolch.utils/src/main/java/li/strolch/communication/ConnectionException.java index 465477567..2e92b0838 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionException.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionException.java @@ -1,33 +1,33 @@ -/* - * Copyright 2014 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.communication; - -/** - * Base Exception for exceptions thrown by the communication classes - * - * @author Robert von Burg - */ -public class ConnectionException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public ConnectionException(String message, Throwable cause) { - super(message, cause); - } - - public ConnectionException(String message) { - super(message); - } -} +/* + * Copyright 2014 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.communication; + +/** + * Base Exception for exceptions thrown by the communication classes + * + * @author Robert von Burg + */ +public class ConnectionException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ConnectionException(String message, Throwable cause) { + super(message, cause); + } + + public ConnectionException(String message) { + super(message); + } +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionInfo.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java rename to li.strolch.utils/src/main/java/li/strolch/communication/ConnectionInfo.java index c5bfd51a3..163081406 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionInfo.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionInfo.java @@ -19,7 +19,7 @@ * along with XXX. If not, see * . */ -package ch.eitchnet.communication; +package li.strolch.communication; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionMessages.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java rename to li.strolch.utils/src/main/java/li/strolch/communication/ConnectionMessages.java index 28f6baef1..fd967b842 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMessages.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionMessages.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import java.text.MessageFormat; import java.util.HashMap; @@ -22,7 +22,7 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * Helper class to thrown connection messages diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionMode.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java rename to li.strolch.utils/src/main/java/li/strolch/communication/ConnectionMode.java index 8cf2e2640..5b313dde5 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionMode.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionMode.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import java.io.IOException; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionObserver.java similarity index 95% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java rename to li.strolch.utils/src/main/java/li/strolch/communication/ConnectionObserver.java index 8445ca357..a19a60cd5 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionObserver.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionObserver.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionState.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java rename to li.strolch.utils/src/main/java/li/strolch/communication/ConnectionState.java index 13724331e..d0f34939e 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionState.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionState.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; /** *

        diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionStateObserver.java similarity index 82% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java rename to li.strolch.utils/src/main/java/li/strolch/communication/ConnectionStateObserver.java index 3b98f1f17..156dd1056 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/ConnectionStateObserver.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/ConnectionStateObserver.java @@ -1,4 +1,4 @@ -package ch.eitchnet.communication; +package li.strolch.communication; public interface ConnectionStateObserver { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessage.java b/li.strolch.utils/src/main/java/li/strolch/communication/IoMessage.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessage.java rename to li.strolch.utils/src/main/java/li/strolch/communication/IoMessage.java index 45d2ba77e..f9bcb9eca 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessage.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/IoMessage.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.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; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** *

        diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java b/li.strolch.utils/src/main/java/li/strolch/communication/IoMessageArchive.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java rename to li.strolch.utils/src/main/java/li/strolch/communication/IoMessageArchive.java index ee0af8077..8a806c0ee 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageArchive.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/IoMessageArchive.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import java.util.List; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java b/li.strolch.utils/src/main/java/li/strolch/communication/IoMessageStateObserver.java similarity index 93% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java rename to li.strolch.utils/src/main/java/li/strolch/communication/IoMessageStateObserver.java index cf7d11a54..e7685d261 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageStateObserver.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/IoMessageStateObserver.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; -import ch.eitchnet.communication.IoMessage.State; +import li.strolch.communication.IoMessage.State; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java b/li.strolch.utils/src/main/java/li/strolch/communication/IoMessageVisitor.java similarity index 93% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java rename to li.strolch.utils/src/main/java/li/strolch/communication/IoMessageVisitor.java index b5a08f9da..011caca8b 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/IoMessageVisitor.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/IoMessageVisitor.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.communication.console.ConsoleMessageVisitor; +import li.strolch.communication.console.ConsoleMessageVisitor; /** *

        diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java b/li.strolch.utils/src/main/java/li/strolch/communication/SimpleMessageArchive.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java rename to li.strolch.utils/src/main/java/li/strolch/communication/SimpleMessageArchive.java index 00ad303b6..12eae7e5e 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/SimpleMessageArchive.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/SimpleMessageArchive.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import java.util.ArrayList; import java.util.Comparator; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java b/li.strolch.utils/src/main/java/li/strolch/communication/StreamMessageVisitor.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java rename to li.strolch.utils/src/main/java/li/strolch/communication/StreamMessageVisitor.java index 894ebf883..6a4fda216 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/StreamMessageVisitor.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/StreamMessageVisitor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import java.io.InputStream; import java.io.OutputStream; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java b/li.strolch.utils/src/main/java/li/strolch/communication/chat/Chat.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java rename to li.strolch.utils/src/main/java/li/strolch/communication/chat/Chat.java index 5dd90d451..f66605be5 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/Chat.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/chat/Chat.java @@ -1,4 +1,4 @@ -package ch.eitchnet.communication.chat; +package li.strolch.communication.chat; import java.net.InetAddress; import java.net.NetworkInterface; @@ -7,7 +7,7 @@ import java.net.UnknownHostException; import java.text.MessageFormat; import java.util.Enumeration; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; public class Chat { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java b/li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatClient.java similarity index 77% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java rename to li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatClient.java index 5f0807281..dabf9288b 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatClient.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatClient.java @@ -1,23 +1,23 @@ -package ch.eitchnet.communication.chat; +package li.strolch.communication.chat; -import static ch.eitchnet.communication.chat.ChatMessageVisitor.inboundKey; -import static ch.eitchnet.communication.chat.ChatMessageVisitor.outboundKey; +import static li.strolch.communication.chat.ChatMessageVisitor.inboundKey; +import static li.strolch.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; +import li.strolch.communication.CommandKey; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.ConnectionMode; +import li.strolch.communication.ConnectionObserver; +import li.strolch.communication.ConnectionState; +import li.strolch.communication.ConnectionStateObserver; +import li.strolch.communication.IoMessage; +import li.strolch.communication.tcpip.ClientSocketEndpoint; +import li.strolch.communication.tcpip.SocketEndpointConstants; +import li.strolch.communication.tcpip.SocketMessageVisitor; public class ChatClient implements ConnectionObserver, ConnectionStateObserver { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java b/li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatIoMessage.java similarity index 78% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java rename to li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatIoMessage.java index a9bf851eb..9806d1a4e 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatIoMessage.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatIoMessage.java @@ -1,8 +1,8 @@ -package ch.eitchnet.communication.chat; +package li.strolch.communication.chat; -import ch.eitchnet.communication.CommandKey; -import ch.eitchnet.communication.IoMessage; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.communication.CommandKey; +import li.strolch.communication.IoMessage; +import li.strolch.utils.helper.StringHelper; public class ChatIoMessage extends IoMessage { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java b/li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatMessageVisitor.java similarity index 84% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java rename to li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatMessageVisitor.java index 29667480f..13df4566b 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatMessageVisitor.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatMessageVisitor.java @@ -1,14 +1,14 @@ -package ch.eitchnet.communication.chat; +package li.strolch.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; +import li.strolch.communication.CommandKey; +import li.strolch.communication.IoMessage; +import li.strolch.communication.tcpip.SocketMessageVisitor; +import li.strolch.utils.helper.StringHelper; public class ChatMessageVisitor extends SocketMessageVisitor { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java b/li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatServer.java similarity index 78% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java rename to li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatServer.java index fbd9b3bde..0880029f2 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/chat/ChatServer.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/chat/ChatServer.java @@ -1,23 +1,23 @@ -package ch.eitchnet.communication.chat; +package li.strolch.communication.chat; -import static ch.eitchnet.communication.chat.ChatMessageVisitor.inboundKey; -import static ch.eitchnet.communication.chat.ChatMessageVisitor.outboundKey; +import static li.strolch.communication.chat.ChatMessageVisitor.inboundKey; +import static li.strolch.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; +import li.strolch.communication.CommandKey; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.ConnectionMode; +import li.strolch.communication.ConnectionObserver; +import li.strolch.communication.ConnectionState; +import li.strolch.communication.ConnectionStateObserver; +import li.strolch.communication.IoMessage; +import li.strolch.communication.tcpip.ServerSocketEndpoint; +import li.strolch.communication.tcpip.SocketEndpointConstants; +import li.strolch.communication.tcpip.SocketMessageVisitor; public class ChatServer implements ConnectionObserver, ConnectionStateObserver { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java b/li.strolch.utils/src/main/java/li/strolch/communication/console/ConsoleEndpoint.java similarity index 86% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java rename to li.strolch.utils/src/main/java/li/strolch/communication/console/ConsoleEndpoint.java index 26bcf14e8..3e6d6a513 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleEndpoint.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/console/ConsoleEndpoint.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication.console; +package li.strolch.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; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.CommunicationEndpoint; +import li.strolch.communication.ConnectionMessages; +import li.strolch.communication.ConnectionState; +import li.strolch.communication.IoMessage; +import li.strolch.communication.IoMessageVisitor; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java b/li.strolch.utils/src/main/java/li/strolch/communication/console/ConsoleMessageVisitor.java similarity index 85% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java rename to li.strolch.utils/src/main/java/li/strolch/communication/console/ConsoleMessageVisitor.java index ccad0ef30..f21f0891e 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/console/ConsoleMessageVisitor.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/console/ConsoleMessageVisitor.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication.console; +package li.strolch.communication.console; import org.slf4j.Logger; -import ch.eitchnet.communication.IoMessage; -import ch.eitchnet.communication.IoMessageVisitor; +import li.strolch.communication.IoMessage; +import li.strolch.communication.IoMessageVisitor; public abstract class ConsoleMessageVisitor extends IoMessageVisitor { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java b/li.strolch.utils/src/main/java/li/strolch/communication/file/FileEndpoint.java similarity index 93% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java rename to li.strolch.utils/src/main/java/li/strolch/communication/file/FileEndpoint.java index 459bf36ab..68c803d23 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpoint.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/file/FileEndpoint.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication.file; +package li.strolch.communication.file; import java.io.File; import java.io.FileInputStream; @@ -25,15 +25,15 @@ 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; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.CommunicationEndpoint; +import li.strolch.communication.ConnectionException; +import li.strolch.communication.ConnectionMessages; +import li.strolch.communication.ConnectionState; +import li.strolch.communication.IoMessage; +import li.strolch.communication.IoMessageVisitor; +import li.strolch.communication.StreamMessageVisitor; +import li.strolch.utils.helper.StringHelper; /** * An {@link CommunicationEndpoint} which writes and/or reads from a designated file diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java b/li.strolch.utils/src/main/java/li/strolch/communication/file/FileEndpointMode.java similarity index 95% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java rename to li.strolch.utils/src/main/java/li/strolch/communication/file/FileEndpointMode.java index 842b0b00e..bfbb2208d 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/file/FileEndpointMode.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/file/FileEndpointMode.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication.file; +package li.strolch.communication.file; public enum FileEndpointMode { READ(true, false), WRITE(false, true), READ_WRITE(true, true); diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java b/li.strolch.utils/src/main/java/li/strolch/communication/tcpip/ClientSocketEndpoint.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java rename to li.strolch.utils/src/main/java/li/strolch/communication/tcpip/ClientSocketEndpoint.java index 7ea688be1..7f4170344 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ClientSocketEndpoint.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/tcpip/ClientSocketEndpoint.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication.tcpip; +package li.strolch.communication.tcpip; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -27,15 +27,15 @@ 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; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.CommunicationEndpoint; +import li.strolch.communication.ConnectionException; +import li.strolch.communication.ConnectionMessages; +import li.strolch.communication.ConnectionState; +import li.strolch.communication.IoMessage; +import li.strolch.communication.IoMessageVisitor; +import li.strolch.communication.IoMessage.State; +import li.strolch.utils.helper.StringHelper; /** *

        diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java b/li.strolch.utils/src/main/java/li/strolch/communication/tcpip/ServerSocketEndpoint.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java rename to li.strolch.utils/src/main/java/li/strolch/communication/tcpip/ServerSocketEndpoint.java index 57f6399ab..191ce30ec 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/ServerSocketEndpoint.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/tcpip/ServerSocketEndpoint.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication.tcpip; +package li.strolch.communication.tcpip; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -30,14 +30,14 @@ 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; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.CommunicationEndpoint; +import li.strolch.communication.ConnectionException; +import li.strolch.communication.ConnectionMessages; +import li.strolch.communication.ConnectionState; +import li.strolch.communication.IoMessage; +import li.strolch.communication.IoMessageVisitor; +import li.strolch.utils.helper.StringHelper; /** *

        diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java b/li.strolch.utils/src/main/java/li/strolch/communication/tcpip/SocketEndpointConstants.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java rename to li.strolch.utils/src/main/java/li/strolch/communication/tcpip/SocketEndpointConstants.java index df7173e46..b9a98daa8 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketEndpointConstants.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/tcpip/SocketEndpointConstants.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication.tcpip; +package li.strolch.communication.tcpip; /** * Constants used in the communication classes diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java b/li.strolch.utils/src/main/java/li/strolch/communication/tcpip/SocketMessageVisitor.java similarity index 92% rename from li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java rename to li.strolch.utils/src/main/java/li/strolch/communication/tcpip/SocketMessageVisitor.java index 52f535300..f7c35e225 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/communication/tcpip/SocketMessageVisitor.java +++ b/li.strolch.utils/src/main/java/li/strolch/communication/tcpip/SocketMessageVisitor.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication.tcpip; +package li.strolch.communication.tcpip; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.Socket; -import ch.eitchnet.communication.IoMessage; -import ch.eitchnet.communication.IoMessageVisitor; +import li.strolch.communication.IoMessage; +import li.strolch.communication.IoMessageVisitor; /** * This {@link IoMessageVisitor} implements and endpoint connecting to a {@link Socket}. diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java b/li.strolch.utils/src/main/java/li/strolch/db/DbConnectionCheck.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java rename to li.strolch.utils/src/main/java/li/strolch/db/DbConnectionCheck.java index 967cdacf3..8efd3e029 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbConnectionCheck.java +++ b/li.strolch.utils/src/main/java/li/strolch/db/DbConnectionCheck.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.db; +package li.strolch.db; import java.sql.Connection; import java.sql.ResultSet; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbConstants.java b/li.strolch.utils/src/main/java/li/strolch/db/DbConstants.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/db/DbConstants.java rename to li.strolch.utils/src/main/java/li/strolch/db/DbConstants.java index edec0cf3a..fe1b2c9d4 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbConstants.java +++ b/li.strolch.utils/src/main/java/li/strolch/db/DbConstants.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.db; +package li.strolch.db; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java b/li.strolch.utils/src/main/java/li/strolch/db/DbDataSourceBuilder.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java rename to li.strolch.utils/src/main/java/li/strolch/db/DbDataSourceBuilder.java index eb33af6f1..aa5ada96c 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbDataSourceBuilder.java +++ b/li.strolch.utils/src/main/java/li/strolch/db/DbDataSourceBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.db; +package li.strolch.db; import java.util.Properties; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbException.java b/li.strolch.utils/src/main/java/li/strolch/db/DbException.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/db/DbException.java rename to li.strolch.utils/src/main/java/li/strolch/db/DbException.java index f1c6bb942..78f420717 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbException.java +++ b/li.strolch.utils/src/main/java/li/strolch/db/DbException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.db; +package li.strolch.db; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java b/li.strolch.utils/src/main/java/li/strolch/db/DbMigrationState.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java rename to li.strolch.utils/src/main/java/li/strolch/db/DbMigrationState.java index f937ef344..d88c4c7cd 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbMigrationState.java +++ b/li.strolch.utils/src/main/java/li/strolch/db/DbMigrationState.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.db; +package li.strolch.db; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java b/li.strolch.utils/src/main/java/li/strolch/db/DbSchemaVersionCheck.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java rename to li.strolch.utils/src/main/java/li/strolch/db/DbSchemaVersionCheck.java index 8f870fec0..bf90c0691 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/db/DbSchemaVersionCheck.java +++ b/li.strolch.utils/src/main/java/li/strolch/db/DbSchemaVersionCheck.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.db; +package li.strolch.db; -import static ch.eitchnet.db.DbConstants.PROP_DB_VERSION; -import static ch.eitchnet.db.DbConstants.RESOURCE_DB_VERSION; +import static li.strolch.db.DbConstants.PROP_DB_VERSION; +import static li.strolch.db.DbConstants.RESOURCE_DB_VERSION; import java.io.IOException; import java.io.InputStream; @@ -36,9 +36,9 @@ 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; +import li.strolch.utils.Version; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.helper.FileHelper; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java b/li.strolch.utils/src/main/java/li/strolch/fileserver/FileClient.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java rename to li.strolch.utils/src/main/java/li/strolch/fileserver/FileClient.java index 0924c8689..8c0fd2dd8 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClient.java +++ b/li.strolch.utils/src/main/java/li/strolch/fileserver/FileClient.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.fileserver; +package li.strolch.fileserver; import java.rmi.RemoteException; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java b/li.strolch.utils/src/main/java/li/strolch/fileserver/FileClientUtil.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java rename to li.strolch.utils/src/main/java/li/strolch/fileserver/FileClientUtil.java index 102f205d6..c78f61456 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileClientUtil.java +++ b/li.strolch.utils/src/main/java/li/strolch/fileserver/FileClientUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.fileserver; +package li.strolch.fileserver; import java.io.BufferedInputStream; import java.io.File; @@ -25,8 +25,8 @@ import java.text.MessageFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.FileHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java b/li.strolch.utils/src/main/java/li/strolch/fileserver/FileDeletion.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java rename to li.strolch.utils/src/main/java/li/strolch/fileserver/FileDeletion.java index b669aa39e..1954782b8 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileDeletion.java +++ b/li.strolch.utils/src/main/java/li/strolch/fileserver/FileDeletion.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.fileserver; +package li.strolch.fileserver; import java.io.Serializable; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java b/li.strolch.utils/src/main/java/li/strolch/fileserver/FileHandler.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java rename to li.strolch.utils/src/main/java/li/strolch/fileserver/FileHandler.java index bee06ab65..60bf99a4b 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FileHandler.java +++ b/li.strolch.utils/src/main/java/li/strolch/fileserver/FileHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.fileserver; +package li.strolch.fileserver; import java.io.File; import java.io.FileInputStream; @@ -24,8 +24,8 @@ import java.text.MessageFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.FileHelper; +import li.strolch.utils.helper.StringHelper; /** * This class handles remote requests of clients to upload or download a file. Uploading a file is done by calling diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java b/li.strolch.utils/src/main/java/li/strolch/fileserver/FilePart.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java rename to li.strolch.utils/src/main/java/li/strolch/fileserver/FilePart.java index cfd2f5ce3..9101ca833 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/fileserver/FilePart.java +++ b/li.strolch.utils/src/main/java/li/strolch/fileserver/FilePart.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.fileserver; +package li.strolch.fileserver; import java.io.Serializable; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java b/li.strolch.utils/src/main/java/li/strolch/utils/StringMatchMode.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java rename to li.strolch.utils/src/main/java/li/strolch/utils/StringMatchMode.java index aec70ad28..a58bd7226 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/StringMatchMode.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/StringMatchMode.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils; +package li.strolch.utils; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/Version.java b/li.strolch.utils/src/main/java/li/strolch/utils/Version.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/Version.java rename to li.strolch.utils/src/main/java/li/strolch/utils/Version.java index 7b6b93dd6..e7eabc1d6 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/Version.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/Version.java @@ -1,507 +1,507 @@ -package ch.eitchnet.utils; - -import java.util.NoSuchElementException; -import java.util.StringTokenizer; - -import ch.eitchnet.utils.helper.StringHelper; - -/** - * This class has been adapted from org.osgi.framework.Version - * - * Version identifier. - * - *

        - * Version identifiers have four components. - *

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

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

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

        - * - *

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

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

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

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

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

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

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

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

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

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

        - * The format of the version string will be {@code major.minor.micro} if qualifier is the empty string or - * {@code major.minor.micro.qualifier} otherwise. - * - * @return The string representation of this version identifier. - */ - @Override - public String toString() { - if (this.versionString == null) - this.versionString = toString(this.osgiStyle); - return this.versionString; - } - - private String toString(final boolean withOsgiStyle) { - int q = this.qualifier.length(); - StringBuilder result = new StringBuilder(20 + q); - result.append(this.major); - result.append(SEPARATOR); - result.append(this.minor); - result.append(SEPARATOR); - result.append(this.micro); - if (q > 0) { - if (withOsgiStyle) { - result.append(OSGI_QUALIFIER_SEPARATOR); - } else { - result.append(MAVEN_QUALIFIER_SEPARATOR); - } - result.append(createQualifier(withOsgiStyle)); - } - return result.toString(); - } - - private String createQualifier(boolean withOsgiStyle) { - if (this.qualifier.equals(MAVEN_SNAPSHOT_QUALIFIER) || this.qualifier.equals(OSGI_SNAPSHOT_QUALIFIER)) { - if (withOsgiStyle) - return OSGI_SNAPSHOT_QUALIFIER; - return MAVEN_SNAPSHOT_QUALIFIER; - } - - return this.qualifier; - } - - /** - * @return This version represented in a maven compatible form. - */ - public String toMavenStyleString() { - return toString(false); - } - - /** - * @return This version represented in an OSGi compatible form. - */ - public String toOsgiStyleString() { - return toString(true); - } - - /** - * @return This only the major and minor version in a string - */ - public String toMajorAndMinorString() { - StringBuilder result = new StringBuilder(20); - result.append(this.major); - result.append(SEPARATOR); - result.append(this.minor); - return result.toString(); - } -} +package li.strolch.utils; + +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +import li.strolch.utils.helper.StringHelper; + +/** + * This class has been adapted from org.osgi.framework.Version + * + * Version identifier. + * + *

        + * Version identifiers have four components. + *

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

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

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

        + * + *

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

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

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

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

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

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

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

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

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

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

        + * The format of the version string will be {@code major.minor.micro} if qualifier is the empty string or + * {@code major.minor.micro.qualifier} otherwise. + * + * @return The string representation of this version identifier. + */ + @Override + public String toString() { + if (this.versionString == null) + this.versionString = toString(this.osgiStyle); + return this.versionString; + } + + private String toString(final boolean withOsgiStyle) { + int q = this.qualifier.length(); + StringBuilder result = new StringBuilder(20 + q); + result.append(this.major); + result.append(SEPARATOR); + result.append(this.minor); + result.append(SEPARATOR); + result.append(this.micro); + if (q > 0) { + if (withOsgiStyle) { + result.append(OSGI_QUALIFIER_SEPARATOR); + } else { + result.append(MAVEN_QUALIFIER_SEPARATOR); + } + result.append(createQualifier(withOsgiStyle)); + } + return result.toString(); + } + + private String createQualifier(boolean withOsgiStyle) { + if (this.qualifier.equals(MAVEN_SNAPSHOT_QUALIFIER) || this.qualifier.equals(OSGI_SNAPSHOT_QUALIFIER)) { + if (withOsgiStyle) + return OSGI_SNAPSHOT_QUALIFIER; + return MAVEN_SNAPSHOT_QUALIFIER; + } + + return this.qualifier; + } + + /** + * @return This version represented in a maven compatible form. + */ + public String toMavenStyleString() { + return toString(false); + } + + /** + * @return This version represented in an OSGi compatible form. + */ + public String toOsgiStyleString() { + return toString(true); + } + + /** + * @return This only the major and minor version in a string + */ + public String toMajorAndMinorString() { + StringBuilder result = new StringBuilder(20); + result.append(this.major); + result.append(SEPARATOR); + result.append(this.minor); + return result.toString(); + } +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java b/li.strolch.utils/src/main/java/li/strolch/utils/collections/DateRange.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java rename to li.strolch.utils/src/main/java/li/strolch/utils/collections/DateRange.java index b4410b8a9..6f2f196ad 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DateRange.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/collections/DateRange.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; import java.util.Date; -import ch.eitchnet.utils.dbc.DBC; -import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.iso8601.ISO8601FormatFactory; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java b/li.strolch.utils/src/main/java/li/strolch/utils/collections/DefaultedHashMap.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java rename to li.strolch.utils/src/main/java/li/strolch/utils/collections/DefaultedHashMap.java index 10ef2d75b..4f3de0c2b 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/DefaultedHashMap.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/collections/DefaultedHashMap.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; import java.util.HashMap; import java.util.Map; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java b/li.strolch.utils/src/main/java/li/strolch/utils/collections/MapOfLists.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java rename to li.strolch.utils/src/main/java/li/strolch/utils/collections/MapOfLists.java index e40c9a849..a656e9f72 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfLists.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/collections/MapOfLists.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; import java.util.ArrayList; import java.util.HashMap; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java b/li.strolch.utils/src/main/java/li/strolch/utils/collections/MapOfMaps.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java rename to li.strolch.utils/src/main/java/li/strolch/utils/collections/MapOfMaps.java index 724caa53d..388352cab 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/MapOfMaps.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/collections/MapOfMaps.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; import java.util.ArrayList; import java.util.HashMap; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java b/li.strolch.utils/src/main/java/li/strolch/utils/collections/Paging.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java rename to li.strolch.utils/src/main/java/li/strolch/utils/collections/Paging.java index dde14cd4f..335ad05a1 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Paging.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/collections/Paging.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; import java.util.List; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java b/li.strolch.utils/src/main/java/li/strolch/utils/collections/Tuple.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java rename to li.strolch.utils/src/main/java/li/strolch/utils/collections/Tuple.java index ec61f3338..e7ec21dba 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/collections/Tuple.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/collections/Tuple.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; /** * Simple wrapper for two elements diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java b/li.strolch.utils/src/main/java/li/strolch/utils/dbc/DBC.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java rename to li.strolch.utils/src/main/java/li/strolch/utils/dbc/DBC.java index 1b43adf76..6800f5fa1 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/dbc/DBC.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/dbc/DBC.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.dbc; +package li.strolch.utils.dbc; import java.io.File; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java b/li.strolch.utils/src/main/java/li/strolch/utils/exceptions/XmlException.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java rename to li.strolch.utils/src/main/java/li/strolch/utils/exceptions/XmlException.java index 097ac910c..0f48939df 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/exceptions/XmlException.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/exceptions/XmlException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.exceptions; +package li.strolch.utils.exceptions; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/AesCryptoHelper.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/AesCryptoHelper.java index 1f580405c..e63a26a82 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AesCryptoHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/AesCryptoHelper.java @@ -1,4 +1,4 @@ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -20,7 +20,7 @@ import javax.crypto.spec.SecretKeySpec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; public class AesCryptoHelper { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ArraysHelper.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/ArraysHelper.java index 5404d9e7f..fd32e29e7 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ArraysHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ArraysHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.util.Arrays; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/AsciiHelper.java similarity index 95% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/AsciiHelper.java index 7f648de0f..ce6b3ff45 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/AsciiHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/AsciiHelper.java @@ -1,322 +1,322 @@ -/* - * Copyright 2013 Robert von Burg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.helper; - -/** - * ASCII constants - * - * @author Robert von Burg - */ -public class AsciiHelper { - - /** - * ASCII Value 0, interpretation: NUL
        - * Description: Null character - */ - public static final char NUL = (char) 0; // Null character - - /** - * ASCII Value 1, interpretation: SOH
        - * Description: Start of Header - */ - public static final char SOH = (char) 1; // Start of Header - - /** - * ASCII Value 2, interpretation: STX
        - * Description: Start of Text - */ - public static final char STX = (char) 2; // Start of Text - - /** - * ASCII Value 3, interpretation: ETX
        - * Description: End of Text - */ - public static final char ETX = (char) 3; // End of Text - - /** - * ASCII Value 4, interpretation: EOT
        - * Description: End of Transmission - */ - public static final char EOT = (char) 4; // End of Transmission - - /** - * ASCII Value 5, interpretation: ENQ
        - * Description: Enquiry - */ - public static final char ENQ = (char) 5; // Enquiry - - /** - * ASCII Value 6, interpretation: ACK
        - * Description: Acknowledgement - */ - public static final char ACK = (char) 6; // Acknowledgement - - /** - * ASCII Value 7, interpretation: BEL
        - * Description: Bell - */ - public static final char BEL = (char) 7; // Bell - - /** - * ASCII Value 8, interpretation: BS
        - * Description: Backspace - */ - public static final char BS = (char) 8; // Backspace - - /** - * ASCII Value 9, interpretation: HT
        - * Description: Horizontal Tab - */ - public static final char HT = (char) 9; // Horizontal Tab - - /** - * ASCII Value 10, interpretation: LF
        - * Description: Line Feed - */ - public static final char LF = (char) 10; // Line Feed - - /** - * ASCII Value 11, interpretation: VT
        - * Description: Vertical Tab - */ - public static final char VT = (char) 11; // Vertical Tab - - /** - * ASCII Value 12, interpretation: FF
        - * Description: Form Feed - */ - public static final char FF = (char) 12; // Form Feed - - /** - * ASCII Value 13, interpretation: CR
        - * Description: Carriage Return - */ - public static final char CR = (char) 13; // Carriage Return - - /** - * ASCII Value 14, interpretation: SO
        - * Description: Shift Out - */ - public static final char SO = (char) 14; // Shift Out - - /** - * ASCII Value 15, interpretation: SI
        - * Description: Shift In - */ - public static final char SI = (char) 15; // Shift In - - /** - * ASCII Value 16, interpretation: DLE
        - * Description: Data Link Escape - */ - public static final char DLE = (char) 16; // Data Link Escape - - /** - * ASCII Value 17, interpretation: DC1
        - * Description: (XON) Device Control 1 - */ - public static final char DC1 = (char) 17; // (XON) Device Control 1 - - /** - * ASCII Value 18, interpretation: DC2
        - * Description: Device Control 2 - */ - public static final char DC2 = (char) 18; // Device Control 2 - - /** - * ASCII Value 19 interpretation: DC3
        - * Description: (XOFF) Device Control 3 - */ - public static final char DC3 = (char) 19; // (XOFF) Device Control 3 - - /** - * ASCII Value 20, interpretation: DC4
        - * Description: Device Control 4 - */ - public static final char DC4 = (char) 20; // Device Control 4 - - /** - * ASCII Value 21, interpretation: NAK
        - * Description: Negative Acknowledgment - */ - public static final char NAK = (char) 21; // Negative Acknowledgment - - /** - * ASCII Value 22, interpretation: SYN
        - * Description: Synchronous Idle - */ - public static final char SYN = (char) 22; // Synchronous Idle - - /** - * ASCII Value 23, interpretation: ETB
        - * Description: End of Transmission Block - */ - public static final char ETB = (char) 23; // End of Transmission Block - - /** - * ASCII Value 24, interpretation: CAN
        - * Description: Cancel - */ - public static final char CAN = (char) 24; // Cancel - - /** - * ASCII Value 25, interpretation: EM
        - * Description: End of Medium - */ - public static final char EM = (char) 25; // End of Medium - - /** - * ASCII Value 26, interpretation: SUB
        - * Description: Substitute - */ - public static final char SUB = (char) 26; // Substitute - - /** - * ASCII Value 27, interpretation: ESC
        - * Description: Escape - */ - public static final char ESC = (char) 27; // Escape - - /** - * ASCII Value 28, interpretation: FS
        - * Description: File Separator - */ - public static final char FS = (char) 28; // File Separator - - /** - * ASCII Value 29, interpretation: GS
        - * Description: Group Separator - */ - public static final char GS = (char) 29; // Group Separator - - /** - * ASCII Value 30, interpretation: RS
        - * Description: Request to Send (Record Separator) - */ - public static final char RS = (char) 30; // Request to Send (Record Separator) - - /** - * ASCII Value 31, interpretation: US
        - * Description: Unit Separator - */ - public static final char US = (char) 31; // Unit Separator - - /** - * ASCII Value 32, interpretation: SP
        - * Description: Space - */ - public static final char SP = (char) 32; // Space - - /** - * ASCII Value 127, interpretation: DEL
        - * Description: Delete - */ - public static final char DEL = (char) 127; // Delete - - /** - * Returns the ASCII Text of a certain bye value - * - * @param b - * @return String - */ - public static String getAsciiText(byte b) { - return getAsciiText((char) b); - } - - /** - * Returns the ASCII Text of a certain char value - * - * @param c - * @return String - */ - @SuppressWarnings("nls") - public static String getAsciiText(char c) { - // else if(c == ) { return "";} - if (c == NUL) { - return "NUL"; - } else if (c == SOH) { - return "SOH"; - } else if (c == STX) { - return "STX"; - } else if (c == ETX) { - return "ETX"; - } else if (c == EOT) { - return "EOT"; - } else if (c == ENQ) { - return "ENQ"; - } else if (c == ACK) { - return "ACK"; - } else if (c == BEL) { - return "BEL"; - } else if (c == BS) { - return "BS"; - } else if (c == HT) { - return "HT"; - } else if (c == LF) { - return "LF"; - } else if (c == VT) { - return "VT"; - } else if (c == FF) { - return "FF"; - } else if (c == CR) { - return "CR"; - } else if (c == SO) { - return "SO"; - } else if (c == SI) { - return "SI"; - } else if (c == DLE) { - return "DLE"; - } else if (c == DC1) { - return "DC1"; - } else if (c == DC2) { - return "DC2"; - } else if (c == DC3) { - return "DC3"; - } else if (c == DC4) { - return "DC4"; - } else if (c == NAK) { - return "NAK"; - } else if (c == SYN) { - return "SYN"; - } else if (c == ETB) { - return "ETB"; - } else if (c == CAN) { - return "CAN"; - } else if (c == EM) { - return "EM"; - } else if (c == SUB) { - return "SUB"; - } else if (c == ESC) { - return "ESC"; - } else if (c == FS) { - return "FS"; - } else if (c == GS) { - return "GS"; - } else if (c == RS) { - return "RS"; - } else if (c == US) { - return "US"; - } else if (c == SP) { - return "SP"; - } else if (c == DEL) { - return "DEL"; - } else if ((c) > 32 && (c) < 127) { - return String.valueOf(c); - } else { - return "(null:" + (byte) c + ")"; - } - } -} +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.utils.helper; + +/** + * ASCII constants + * + * @author Robert von Burg + */ +public class AsciiHelper { + + /** + * ASCII Value 0, interpretation: NUL
        + * Description: Null character + */ + public static final char NUL = (char) 0; // Null character + + /** + * ASCII Value 1, interpretation: SOH
        + * Description: Start of Header + */ + public static final char SOH = (char) 1; // Start of Header + + /** + * ASCII Value 2, interpretation: STX
        + * Description: Start of Text + */ + public static final char STX = (char) 2; // Start of Text + + /** + * ASCII Value 3, interpretation: ETX
        + * Description: End of Text + */ + public static final char ETX = (char) 3; // End of Text + + /** + * ASCII Value 4, interpretation: EOT
        + * Description: End of Transmission + */ + public static final char EOT = (char) 4; // End of Transmission + + /** + * ASCII Value 5, interpretation: ENQ
        + * Description: Enquiry + */ + public static final char ENQ = (char) 5; // Enquiry + + /** + * ASCII Value 6, interpretation: ACK
        + * Description: Acknowledgement + */ + public static final char ACK = (char) 6; // Acknowledgement + + /** + * ASCII Value 7, interpretation: BEL
        + * Description: Bell + */ + public static final char BEL = (char) 7; // Bell + + /** + * ASCII Value 8, interpretation: BS
        + * Description: Backspace + */ + public static final char BS = (char) 8; // Backspace + + /** + * ASCII Value 9, interpretation: HT
        + * Description: Horizontal Tab + */ + public static final char HT = (char) 9; // Horizontal Tab + + /** + * ASCII Value 10, interpretation: LF
        + * Description: Line Feed + */ + public static final char LF = (char) 10; // Line Feed + + /** + * ASCII Value 11, interpretation: VT
        + * Description: Vertical Tab + */ + public static final char VT = (char) 11; // Vertical Tab + + /** + * ASCII Value 12, interpretation: FF
        + * Description: Form Feed + */ + public static final char FF = (char) 12; // Form Feed + + /** + * ASCII Value 13, interpretation: CR
        + * Description: Carriage Return + */ + public static final char CR = (char) 13; // Carriage Return + + /** + * ASCII Value 14, interpretation: SO
        + * Description: Shift Out + */ + public static final char SO = (char) 14; // Shift Out + + /** + * ASCII Value 15, interpretation: SI
        + * Description: Shift In + */ + public static final char SI = (char) 15; // Shift In + + /** + * ASCII Value 16, interpretation: DLE
        + * Description: Data Link Escape + */ + public static final char DLE = (char) 16; // Data Link Escape + + /** + * ASCII Value 17, interpretation: DC1
        + * Description: (XON) Device Control 1 + */ + public static final char DC1 = (char) 17; // (XON) Device Control 1 + + /** + * ASCII Value 18, interpretation: DC2
        + * Description: Device Control 2 + */ + public static final char DC2 = (char) 18; // Device Control 2 + + /** + * ASCII Value 19 interpretation: DC3
        + * Description: (XOFF) Device Control 3 + */ + public static final char DC3 = (char) 19; // (XOFF) Device Control 3 + + /** + * ASCII Value 20, interpretation: DC4
        + * Description: Device Control 4 + */ + public static final char DC4 = (char) 20; // Device Control 4 + + /** + * ASCII Value 21, interpretation: NAK
        + * Description: Negative Acknowledgment + */ + public static final char NAK = (char) 21; // Negative Acknowledgment + + /** + * ASCII Value 22, interpretation: SYN
        + * Description: Synchronous Idle + */ + public static final char SYN = (char) 22; // Synchronous Idle + + /** + * ASCII Value 23, interpretation: ETB
        + * Description: End of Transmission Block + */ + public static final char ETB = (char) 23; // End of Transmission Block + + /** + * ASCII Value 24, interpretation: CAN
        + * Description: Cancel + */ + public static final char CAN = (char) 24; // Cancel + + /** + * ASCII Value 25, interpretation: EM
        + * Description: End of Medium + */ + public static final char EM = (char) 25; // End of Medium + + /** + * ASCII Value 26, interpretation: SUB
        + * Description: Substitute + */ + public static final char SUB = (char) 26; // Substitute + + /** + * ASCII Value 27, interpretation: ESC
        + * Description: Escape + */ + public static final char ESC = (char) 27; // Escape + + /** + * ASCII Value 28, interpretation: FS
        + * Description: File Separator + */ + public static final char FS = (char) 28; // File Separator + + /** + * ASCII Value 29, interpretation: GS
        + * Description: Group Separator + */ + public static final char GS = (char) 29; // Group Separator + + /** + * ASCII Value 30, interpretation: RS
        + * Description: Request to Send (Record Separator) + */ + public static final char RS = (char) 30; // Request to Send (Record Separator) + + /** + * ASCII Value 31, interpretation: US
        + * Description: Unit Separator + */ + public static final char US = (char) 31; // Unit Separator + + /** + * ASCII Value 32, interpretation: SP
        + * Description: Space + */ + public static final char SP = (char) 32; // Space + + /** + * ASCII Value 127, interpretation: DEL
        + * Description: Delete + */ + public static final char DEL = (char) 127; // Delete + + /** + * Returns the ASCII Text of a certain bye value + * + * @param b + * @return String + */ + public static String getAsciiText(byte b) { + return getAsciiText((char) b); + } + + /** + * Returns the ASCII Text of a certain char value + * + * @param c + * @return String + */ + @SuppressWarnings("nls") + public static String getAsciiText(char c) { + // else if(c == ) { return "";} + if (c == NUL) { + return "NUL"; + } else if (c == SOH) { + return "SOH"; + } else if (c == STX) { + return "STX"; + } else if (c == ETX) { + return "ETX"; + } else if (c == EOT) { + return "EOT"; + } else if (c == ENQ) { + return "ENQ"; + } else if (c == ACK) { + return "ACK"; + } else if (c == BEL) { + return "BEL"; + } else if (c == BS) { + return "BS"; + } else if (c == HT) { + return "HT"; + } else if (c == LF) { + return "LF"; + } else if (c == VT) { + return "VT"; + } else if (c == FF) { + return "FF"; + } else if (c == CR) { + return "CR"; + } else if (c == SO) { + return "SO"; + } else if (c == SI) { + return "SI"; + } else if (c == DLE) { + return "DLE"; + } else if (c == DC1) { + return "DC1"; + } else if (c == DC2) { + return "DC2"; + } else if (c == DC3) { + return "DC3"; + } else if (c == DC4) { + return "DC4"; + } else if (c == NAK) { + return "NAK"; + } else if (c == SYN) { + return "SYN"; + } else if (c == ETB) { + return "ETB"; + } else if (c == CAN) { + return "CAN"; + } else if (c == EM) { + return "EM"; + } else if (c == SUB) { + return "SUB"; + } else if (c == ESC) { + return "ESC"; + } else if (c == FS) { + return "FS"; + } else if (c == GS) { + return "GS"; + } else if (c == RS) { + return "RS"; + } else if (c == US) { + return "US"; + } else if (c == SP) { + return "SP"; + } else if (c == DEL) { + return "DEL"; + } else if ((c) > 32 && (c) < 127) { + return String.valueOf(c); + } else { + return "(null:" + (byte) c + ")"; + } + } +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/BaseEncoding.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/BaseEncoding.java index 8a3437a49..4bb9849d7 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/BaseEncoding.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/BaseEncoding.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.text.MessageFormat; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ByteHelper.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/ByteHelper.java index 97697ee28..eab5b937a 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ByteHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ByteHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ClassHelper.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/ClassHelper.java index b24d3cd9f..6c4a4b648 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ClassHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ClassHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.text.MessageFormat; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/DomUtil.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/DomUtil.java index 20851de57..bc7acfa45 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/DomUtil.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/DomUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.text.MessageFormat; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ExceptionHelper.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/ExceptionHelper.java index a498c20cb..1acebe698 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ExceptionHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ExceptionHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/FileHelper.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/FileHelper.java index 57cf58bc9..6106cff8c 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/FileHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/FileHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/MathHelper.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/MathHelper.java index 6c868b889..cf2ccbf90 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/MathHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/MathHelper.java @@ -1,125 +1,125 @@ -/* - * Copyright 2013 Martin Smock - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.helper; - -import java.math.BigDecimal; -import java.math.RoundingMode; - -/** - * A helper class that contains mathematical computations that can be used throughout. - * - * @author Martin Smock - * @author Michael Gatto - */ -public class MathHelper { - - public static final double PRECISION = 1.0E08; - public static final int PRECISION_DIGITS = 3; - - /** - * Check if the two values are equal with respect to the precision - * - * @param firstValue - * the first value to compare - * @param secondValue - * the second value to compare to - * @return boolean True, if the two values are equal under the set precision. Fales, otherwise. - */ - public static boolean isEqualPrecision(double firstValue, double secondValue) { - - return (java.lang.Math.abs(firstValue - secondValue) < (1.0d / PRECISION)); - } - - /** - * Comparison between the two values. Given the precision, this function determines if the given value is smaller - * than the given bound. - *

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

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

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

        - * - *

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

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

        - * - * @param value - * the double value to round - * @param decimals - * number of decimals - * - * @return the rounded number - */ - public static double toPrecision(double value, int decimals) { - if (value == 0.0) - return 0.0; - if (Double.isNaN(value)) - return Double.NaN; - if (value == Double.NEGATIVE_INFINITY) - return Double.NEGATIVE_INFINITY; - if (value == Double.POSITIVE_INFINITY) - return Double.POSITIVE_INFINITY; - return new BigDecimal(value).setScale(decimals, RoundingMode.HALF_EVEN).doubleValue(); - } - - /** - * Returns the value with the precision where precision is set to {@link #PRECISION_DIGITS} - * - * @see #toPrecision(double, int) - */ - public static double toPrecision(double value) { - return toPrecision(value, PRECISION_DIGITS); - } -} +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.utils.helper; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * A helper class that contains mathematical computations that can be used throughout. + * + * @author Martin Smock + * @author Michael Gatto + */ +public class MathHelper { + + public static final double PRECISION = 1.0E08; + public static final int PRECISION_DIGITS = 3; + + /** + * Check if the two values are equal with respect to the precision + * + * @param firstValue + * the first value to compare + * @param secondValue + * the second value to compare to + * @return boolean True, if the two values are equal under the set precision. Fales, otherwise. + */ + public static boolean isEqualPrecision(double firstValue, double secondValue) { + + return (java.lang.Math.abs(firstValue - secondValue) < (1.0d / PRECISION)); + } + + /** + * Comparison between the two values. Given the precision, this function determines if the given value is smaller + * than the given bound. + *

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

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

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

        + * + *

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

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

        + * + * @param value + * the double value to round + * @param decimals + * number of decimals + * + * @return the rounded number + */ + public static double toPrecision(double value, int decimals) { + if (value == 0.0) + return 0.0; + if (Double.isNaN(value)) + return Double.NaN; + if (value == Double.NEGATIVE_INFINITY) + return Double.NEGATIVE_INFINITY; + if (value == Double.POSITIVE_INFINITY) + return Double.POSITIVE_INFINITY; + return new BigDecimal(value).setScale(decimals, RoundingMode.HALF_EVEN).doubleValue(); + } + + /** + * Returns the value with the precision where precision is set to {@link #PRECISION_DIGITS} + * + * @see #toPrecision(double, int) + */ + public static double toPrecision(double value) { + return toPrecision(value, PRECISION_DIGITS); + } +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ProcessHelper.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/ProcessHelper.java index 2d84b0624..a157b100c 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/ProcessHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/ProcessHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.io.BufferedReader; import java.io.File; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/PropertiesHelper.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/PropertiesHelper.java index f09f66e6c..ec787c66b 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/PropertiesHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/PropertiesHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.text.MessageFormat; import java.util.Properties; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/StringHelper.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/StringHelper.java index a7a3fcc94..2a11a0e1d 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/StringHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/StringHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/SystemHelper.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/SystemHelper.java index f7f97d8f0..193d619fc 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/SystemHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/SystemHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; /** * A helper class for {@link System} methods diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/XmlDomSigner.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/XmlDomSigner.java index 175df4922..1fd64b254 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlDomSigner.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/XmlDomSigner.java @@ -1,4 +1,4 @@ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -51,7 +51,7 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import ch.eitchnet.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC; public class XmlDomSigner { diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java b/li.strolch.utils/src/main/java/li/strolch/utils/helper/XmlHelper.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java rename to li.strolch.utils/src/main/java/li/strolch/utils/helper/XmlHelper.java index 15410fd5b..b88a07aeb 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/helper/XmlHelper.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/helper/XmlHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.io.ByteArrayOutputStream; import java.io.File; @@ -44,7 +44,7 @@ import org.w3c.dom.Element; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.utils.exceptions.XmlException; +import li.strolch.utils.exceptions.XmlException; /** * Helper class for performing XML based tasks diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java b/li.strolch.utils/src/main/java/li/strolch/utils/io/FileProgressListener.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java rename to li.strolch.utils/src/main/java/li/strolch/utils/io/FileProgressListener.java index 60a236da7..cd23dae2e 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileProgressListener.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/io/FileProgressListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.io; +package li.strolch.utils.io; /** *

        diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java b/li.strolch.utils/src/main/java/li/strolch/utils/io/FileStreamProgressWatcher.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java rename to li.strolch.utils/src/main/java/li/strolch/utils/io/FileStreamProgressWatcher.java index 10c0a2ca2..56e84a455 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/FileStreamProgressWatcher.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/io/FileStreamProgressWatcher.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.io; +package li.strolch.utils.io; import java.text.MessageFormat; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java b/li.strolch.utils/src/main/java/li/strolch/utils/io/LoggingFileProgressListener.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java rename to li.strolch.utils/src/main/java/li/strolch/utils/io/LoggingFileProgressListener.java index 96b133e07..e9e361292 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/LoggingFileProgressListener.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/io/LoggingFileProgressListener.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.io; +package li.strolch.utils.io; import java.text.MessageFormat; import org.slf4j.Logger; -import ch.eitchnet.utils.helper.FileHelper; +import li.strolch.utils.helper.FileHelper; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java b/li.strolch.utils/src/main/java/li/strolch/utils/io/ProgressableFileInputStream.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java rename to li.strolch.utils/src/main/java/li/strolch/utils/io/ProgressableFileInputStream.java index b98f525a3..45513bd25 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/io/ProgressableFileInputStream.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/io/ProgressableFileInputStream.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.io; +package li.strolch.utils.io; import java.io.File; import java.io.FileInputStream; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/DateFormat.java similarity index 93% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java rename to li.strolch.utils/src/main/java/li/strolch/utils/iso8601/DateFormat.java index b3ef20ada..25a834fa1 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DateFormat.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/DateFormat.java @@ -1,58 +1,58 @@ -/* - * Copyright 2013 Martin Smock - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.iso8601; - -import java.util.Date; - -/** - * Interface for date formatting - * - * Martin Smock - */ -public interface DateFormat { - - /** - * format a long to string - * - * @param timepoint - * @return the formatted string of the long value - */ - public String format(long timepoint); - - /** - * format a Date to string - * - * @param date - * @return the formatted string of the long value - */ - public String format(Date date); - - /** - * parse a string to long - * - * @param s - * @return the value parsed - */ - public long parseLong(String s); - - /** - * parse a string to Date - * - * @param s - * @return the value parsed - */ - public Date parse(String s); -} +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.utils.iso8601; + +import java.util.Date; + +/** + * Interface for date formatting + * + * Martin Smock + */ +public interface DateFormat { + + /** + * format a long to string + * + * @param timepoint + * @return the formatted string of the long value + */ + public String format(long timepoint); + + /** + * format a Date to string + * + * @param date + * @return the formatted string of the long value + */ + public String format(Date date); + + /** + * parse a string to long + * + * @param s + * @return the value parsed + */ + public long parseLong(String s); + + /** + * parse a string to Date + * + * @param s + * @return the value parsed + */ + public Date parse(String s); +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/DurationFormat.java similarity index 93% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java rename to li.strolch.utils/src/main/java/li/strolch/utils/iso8601/DurationFormat.java index b5189d2e7..dbda3b20d 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/DurationFormat.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/DurationFormat.java @@ -1,41 +1,41 @@ -/* - * Copyright 2013 Martin Smock - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.iso8601; - -/** - * Interface for duration formatting - * - * Martin Smock - */ -public interface DurationFormat { - - /** - * format a long to string - * - * @param l - * @return formatted string if the long argument - */ - public String format(long l); - - /** - * parse a string to long - * - * @param s - * @return the long value parsed - */ - public long parse(String s); - -} +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.utils.iso8601; + +/** + * Interface for duration formatting + * + * Martin Smock + */ +public interface DurationFormat { + + /** + * format a long to string + * + * @param l + * @return formatted string if the long argument + */ + public String format(long l); + + /** + * parse a string to long + * + * @param s + * @return the long value parsed + */ + public long parse(String s); + +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/FormatFactory.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java rename to li.strolch.utils/src/main/java/li/strolch/utils/iso8601/FormatFactory.java index e728ebe38..50bb21e49 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/FormatFactory.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/FormatFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.iso8601; +package li.strolch.utils.iso8601; import java.util.Date; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601.java similarity index 94% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java rename to li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601.java index 9f66894a1..fe06e5761 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601.java @@ -1,297 +1,297 @@ -/* - * Copyright 2013 Martin Smock - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.iso8601; - -import java.text.DecimalFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ch.eitchnet.utils.helper.StringHelper; - -/** - * @author Martin Smock - */ -@SuppressWarnings("nls") -public class ISO8601 implements DateFormat { - - private static final Logger logger = LoggerFactory.getLogger(ISO8601.class); - - //misc. numeric formats used in formatting - private DecimalFormat xxFormat = new DecimalFormat("00"); - private DecimalFormat xxxFormat = new DecimalFormat("000"); - private DecimalFormat xxxxFormat = new DecimalFormat("0000"); - - /** - * - */ - private Calendar parseToCalendar(String text) { - - // check optional leading sign - char sign; - int start; - if (text.startsWith("-")) { - sign = '-'; - start = 1; - } else if (text.startsWith("+")) { - sign = '+'; - start = 1; - } else { - sign = '+'; // no sign specified, implied '+' - start = 0; - } - - /** - * format of the string is: YYYY-MM-DDThh:mm:ss.SSSTZD - */ - int year, month, day, hour, min, sec, millisec; - String timeZone; - try { - - // year (YYYY) - year = Integer.parseInt(text.substring(start, start + 4)); - start += 4; - // delimiter '-' - if (text.charAt(start) != '-') { - return null; - } - start++; - - // month (MM) - month = Integer.parseInt(text.substring(start, start + 2)); - start += 2; - // delimiter '-' - if (text.charAt(start) != '-') { - return null; - } - start++; - - // day (DD) - day = Integer.parseInt(text.substring(start, start + 2)); - start += 2; - // delimiter 'T' - if (text.charAt(start) != 'T') { - return null; - } - start++; - - // hour (hh) - hour = Integer.parseInt(text.substring(start, start + 2)); - start += 2; - // delimiter ':' - if (text.charAt(start) != ':') { - return null; - } - start++; - - // minute (mm) - min = Integer.parseInt(text.substring(start, start + 2)); - start += 2; - // delimiter ':' - if (text.charAt(start) != ':') { - return null; - } - start++; - - // second (ss) - sec = Integer.parseInt(text.substring(start, start + 2)); - start += 2; - - // delimiter '.' - if (text.charAt(start) == '.') { - start++; - // millisecond (SSS) - millisec = Integer.parseInt(text.substring(start, start + 3)); - start += 3; - } else { - millisec = 0; - } - - if (text.charAt(start) == '+' || text.charAt(start) == '-') { - timeZone = "GMT" + text.substring(start); - } else if (text.substring(start).equals("Z")) { - timeZone = "GMT"; - } else { - return null; - } - - } catch (IndexOutOfBoundsException e) { - return null; - } catch (NumberFormatException e) { - return null; - } - - TimeZone tz = TimeZone.getTimeZone(timeZone); - if (!tz.getID().equals(timeZone)) { - // invalid time zone - return null; - } - - // create Calendar - Calendar cal = Calendar.getInstance(tz); - cal.setLenient(false); - - if (sign == '-' || year == 0) { - // - cal.set(Calendar.YEAR, year + 1); - cal.set(Calendar.ERA, GregorianCalendar.BC); - } else { - cal.set(Calendar.YEAR, year); - cal.set(Calendar.ERA, GregorianCalendar.AD); - } - - // - cal.set(Calendar.MONTH, month - 1); - cal.set(Calendar.DAY_OF_MONTH, day); - cal.set(Calendar.HOUR_OF_DAY, hour); - cal.set(Calendar.MINUTE, min); - cal.set(Calendar.SECOND, sec); - cal.set(Calendar.MILLISECOND, millisec); - - try { - cal.getTime(); - } catch (IllegalArgumentException e) { - return null; - } - - return cal; - } - - /** - * - */ - private String format(Calendar cal) { - - if (cal == null) { - throw new IllegalArgumentException("argument can not be null"); - } - - // determine era and adjust year if necessary - int year = cal.get(Calendar.YEAR); - if (cal.isSet(Calendar.ERA) && cal.get(Calendar.ERA) == GregorianCalendar.BC) { - /** - * calculate year using astronomical system: year n BCE => astronomical year -n + 1 - */ - year = 0 - year + 1; - } - - /** - * format of date/time string is: YYYY-MM-DDThh:mm:ss.SSSTZD - */ - StringBuilder sWriter = new StringBuilder(); - sWriter.append(this.xxxxFormat.format(year)); - sWriter.append('-'); - sWriter.append(this.xxFormat.format(cal.get(Calendar.MONTH) + 1)); - sWriter.append('-'); - sWriter.append(this.xxFormat.format(cal.get(Calendar.DAY_OF_MONTH))); - sWriter.append('T'); - sWriter.append(this.xxFormat.format(cal.get(Calendar.HOUR_OF_DAY))); - sWriter.append(':'); - sWriter.append(this.xxFormat.format(cal.get(Calendar.MINUTE))); - sWriter.append(':'); - sWriter.append(this.xxFormat.format(cal.get(Calendar.SECOND))); - sWriter.append('.'); - sWriter.append(this.xxxFormat.format(cal.get(Calendar.MILLISECOND))); - TimeZone tz = cal.getTimeZone(); - - int offset = tz.getOffset(cal.getTimeInMillis()); - if (offset != 0) { - int hours = Math.abs((offset / (60 * 1000)) / 60); - int minutes = Math.abs((offset / (60 * 1000)) % 60); - sWriter.append(offset < 0 ? '-' : '+'); - sWriter.append(this.xxFormat.format(hours)); - sWriter.append(':'); - sWriter.append(this.xxFormat.format(minutes)); - } else { - sWriter.append('Z'); - } - return sWriter.toString(); - } - - @Override - public String format(Date date) { - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - return format(cal); - } - - /** - * added by msmock convert a long to ISO8601 - * - * @param timePoint - * @return time point as ISO8601 String - */ - @Override - public String format(long timePoint) { - - if (timePoint == Long.MAX_VALUE || timePoint == Long.MIN_VALUE) { - return "-"; - } - - // else - try { - Date date = new Date(); - date.setTime(timePoint); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - return format(cal); - } catch (Exception e) { - logger.error(e.getMessage(), e); - return null; - } - } - - @Override - public long parseLong(String s) { - return parse(s).getTime(); - } - - /** - * parse ISO8601 date to long - * - * @param s - * the string to parse - * @return time point as long - * @throws NumberFormatException - */ - @Override - public Date parse(String s) { - - if (StringHelper.isEmpty(s)) { - String msg = "An empty value can not pe parsed to a date!"; - throw new IllegalArgumentException(msg); - } - - if (s.equals("-")) { - Calendar cal = Calendar.getInstance(); - cal.clear(); - cal.setTimeZone(TimeZone.getTimeZone("GMT0")); - return cal.getTime(); - } - - Calendar cal = parseToCalendar(s); - if (cal != null) { - return cal.getTime(); - } - - String msg = "Input string '" + s + "' cannot be parsed to date."; - throw new IllegalArgumentException(msg); - } -} +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.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 li.strolch.utils.helper.StringHelper; + +/** + * @author Martin Smock + */ +@SuppressWarnings("nls") +public class ISO8601 implements DateFormat { + + private static final Logger logger = LoggerFactory.getLogger(ISO8601.class); + + //misc. numeric formats used in formatting + private DecimalFormat xxFormat = new DecimalFormat("00"); + private DecimalFormat xxxFormat = new DecimalFormat("000"); + private DecimalFormat xxxxFormat = new DecimalFormat("0000"); + + /** + * + */ + private Calendar parseToCalendar(String text) { + + // check optional leading sign + char sign; + int start; + if (text.startsWith("-")) { + sign = '-'; + start = 1; + } else if (text.startsWith("+")) { + sign = '+'; + start = 1; + } else { + sign = '+'; // no sign specified, implied '+' + start = 0; + } + + /** + * format of the string is: YYYY-MM-DDThh:mm:ss.SSSTZD + */ + int year, month, day, hour, min, sec, millisec; + String timeZone; + try { + + // year (YYYY) + year = Integer.parseInt(text.substring(start, start + 4)); + start += 4; + // delimiter '-' + if (text.charAt(start) != '-') { + return null; + } + start++; + + // month (MM) + month = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + // delimiter '-' + if (text.charAt(start) != '-') { + return null; + } + start++; + + // day (DD) + day = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + // delimiter 'T' + if (text.charAt(start) != 'T') { + return null; + } + start++; + + // hour (hh) + hour = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + // delimiter ':' + if (text.charAt(start) != ':') { + return null; + } + start++; + + // minute (mm) + min = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + // delimiter ':' + if (text.charAt(start) != ':') { + return null; + } + start++; + + // second (ss) + sec = Integer.parseInt(text.substring(start, start + 2)); + start += 2; + + // delimiter '.' + if (text.charAt(start) == '.') { + start++; + // millisecond (SSS) + millisec = Integer.parseInt(text.substring(start, start + 3)); + start += 3; + } else { + millisec = 0; + } + + if (text.charAt(start) == '+' || text.charAt(start) == '-') { + timeZone = "GMT" + text.substring(start); + } else if (text.substring(start).equals("Z")) { + timeZone = "GMT"; + } else { + return null; + } + + } catch (IndexOutOfBoundsException e) { + return null; + } catch (NumberFormatException e) { + return null; + } + + TimeZone tz = TimeZone.getTimeZone(timeZone); + if (!tz.getID().equals(timeZone)) { + // invalid time zone + return null; + } + + // create Calendar + Calendar cal = Calendar.getInstance(tz); + cal.setLenient(false); + + if (sign == '-' || year == 0) { + // + cal.set(Calendar.YEAR, year + 1); + cal.set(Calendar.ERA, GregorianCalendar.BC); + } else { + cal.set(Calendar.YEAR, year); + cal.set(Calendar.ERA, GregorianCalendar.AD); + } + + // + cal.set(Calendar.MONTH, month - 1); + cal.set(Calendar.DAY_OF_MONTH, day); + cal.set(Calendar.HOUR_OF_DAY, hour); + cal.set(Calendar.MINUTE, min); + cal.set(Calendar.SECOND, sec); + cal.set(Calendar.MILLISECOND, millisec); + + try { + cal.getTime(); + } catch (IllegalArgumentException e) { + return null; + } + + return cal; + } + + /** + * + */ + private String format(Calendar cal) { + + if (cal == null) { + throw new IllegalArgumentException("argument can not be null"); + } + + // determine era and adjust year if necessary + int year = cal.get(Calendar.YEAR); + if (cal.isSet(Calendar.ERA) && cal.get(Calendar.ERA) == GregorianCalendar.BC) { + /** + * calculate year using astronomical system: year n BCE => astronomical year -n + 1 + */ + year = 0 - year + 1; + } + + /** + * format of date/time string is: YYYY-MM-DDThh:mm:ss.SSSTZD + */ + StringBuilder sWriter = new StringBuilder(); + sWriter.append(this.xxxxFormat.format(year)); + sWriter.append('-'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.MONTH) + 1)); + sWriter.append('-'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.DAY_OF_MONTH))); + sWriter.append('T'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.HOUR_OF_DAY))); + sWriter.append(':'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.MINUTE))); + sWriter.append(':'); + sWriter.append(this.xxFormat.format(cal.get(Calendar.SECOND))); + sWriter.append('.'); + sWriter.append(this.xxxFormat.format(cal.get(Calendar.MILLISECOND))); + TimeZone tz = cal.getTimeZone(); + + int offset = tz.getOffset(cal.getTimeInMillis()); + if (offset != 0) { + int hours = Math.abs((offset / (60 * 1000)) / 60); + int minutes = Math.abs((offset / (60 * 1000)) % 60); + sWriter.append(offset < 0 ? '-' : '+'); + sWriter.append(this.xxFormat.format(hours)); + sWriter.append(':'); + sWriter.append(this.xxFormat.format(minutes)); + } else { + sWriter.append('Z'); + } + return sWriter.toString(); + } + + @Override + public String format(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return format(cal); + } + + /** + * added by msmock convert a long to ISO8601 + * + * @param timePoint + * @return time point as ISO8601 String + */ + @Override + public String format(long timePoint) { + + if (timePoint == Long.MAX_VALUE || timePoint == Long.MIN_VALUE) { + return "-"; + } + + // else + try { + Date date = new Date(); + date.setTime(timePoint); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return format(cal); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return null; + } + } + + @Override + public long parseLong(String s) { + return parse(s).getTime(); + } + + /** + * parse ISO8601 date to long + * + * @param s + * the string to parse + * @return time point as long + * @throws NumberFormatException + */ + @Override + public Date parse(String s) { + + if (StringHelper.isEmpty(s)) { + String msg = "An empty value can not pe parsed to a date!"; + throw new IllegalArgumentException(msg); + } + + if (s.equals("-")) { + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.setTimeZone(TimeZone.getTimeZone("GMT0")); + return cal.getTime(); + } + + Calendar cal = parseToCalendar(s); + if (cal != null) { + return cal.getTime(); + } + + String msg = "Input string '" + s + "' cannot be parsed to date."; + throw new IllegalArgumentException(msg); + } +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601Duration.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java rename to li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601Duration.java index 61e63f94d..dffb65b59 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Duration.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601Duration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.iso8601; +package li.strolch.utils.iso8601; /** *

        diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601FormatFactory.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java rename to li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601FormatFactory.java index 2cc57f670..cf9f24116 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601FormatFactory.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601FormatFactory.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.iso8601; +package li.strolch.utils.iso8601; import java.util.Date; -import ch.eitchnet.utils.helper.MathHelper; +import li.strolch.utils.helper.MathHelper; /** * Default factory for date formats used for serialization. diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601Worktime.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java rename to li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601Worktime.java index e107a7be7..5f233b7d4 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/ISO8601Worktime.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/ISO8601Worktime.java @@ -1,237 +1,237 @@ -/* - * Copyright 2013 Martin Smock - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.iso8601; - -/** - *

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

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

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

        - *

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

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

        - * Thus, a duration of 86401000 (one day and one second) will add the representation of one day if unit is DAY (1D) - * and return 1000 as the remainder with respect of this unit. If the given unit is HOUR, then this function adds - * 24H to the {@link StringBuilder}, and returns 1000 as the remainder. - * - * @param sb - * The {@link StringBuilder} to add the given duration with the right unit - * @param duration - * The duration to add - * @param unit - * The unit of this duration - * @return The remainder of the given duration, modulo the time unit. - */ - private long formatTimeDuration(StringBuilder sb, long duration, TimeDuration unit) { - - long remainder = duration; - - if (unit.equals(TimeDuration.SECOND) || remainder >= unit.duration()) { - - long quantity = remainder / unit.duration(); - remainder = remainder % unit.duration(); - sb.append(quantity); - - if (unit.equals(TimeDuration.SECOND)) { - - long millis = remainder; - if (millis == 0) { - // to not have the decimal point - } else if (millis > 99) { - sb.append("." + millis); - } else if (millis > 9) { - sb.append(".0" + millis); - } else { - sb.append(".00" + millis); - } - } - - sb.append(unit.isoChar); - } - return remainder; - } - - /** - * Formats the given time duration to a pseudo ISO 8601 duration string - * - * @param duration - * @return String the duration formatted as a ISO8601 duration string - */ - @Override - public String format(long duration) { - - if (duration == 0) - return "PT0S"; - - StringBuilder sb = new StringBuilder(); - sb.append('P'); - sb.append('T'); - long remainder = formatTimeDuration(sb, duration, TimeDuration.HOUR); - remainder = formatTimeDuration(sb, remainder, TimeDuration.MINUTE); - remainder = formatTimeDuration(sb, remainder, TimeDuration.SECOND); - - return sb.toString(); - } - -} +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.utils.iso8601; + +/** + *

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

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

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

        + *

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

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

        + * Thus, a duration of 86401000 (one day and one second) will add the representation of one day if unit is DAY (1D) + * and return 1000 as the remainder with respect of this unit. If the given unit is HOUR, then this function adds + * 24H to the {@link StringBuilder}, and returns 1000 as the remainder. + * + * @param sb + * The {@link StringBuilder} to add the given duration with the right unit + * @param duration + * The duration to add + * @param unit + * The unit of this duration + * @return The remainder of the given duration, modulo the time unit. + */ + private long formatTimeDuration(StringBuilder sb, long duration, TimeDuration unit) { + + long remainder = duration; + + if (unit.equals(TimeDuration.SECOND) || remainder >= unit.duration()) { + + long quantity = remainder / unit.duration(); + remainder = remainder % unit.duration(); + sb.append(quantity); + + if (unit.equals(TimeDuration.SECOND)) { + + long millis = remainder; + if (millis == 0) { + // to not have the decimal point + } else if (millis > 99) { + sb.append("." + millis); + } else if (millis > 9) { + sb.append(".0" + millis); + } else { + sb.append(".00" + millis); + } + } + + sb.append(unit.isoChar); + } + return remainder; + } + + /** + * Formats the given time duration to a pseudo ISO 8601 duration string + * + * @param duration + * @return String the duration formatted as a ISO8601 duration string + */ + @Override + public String format(long duration) { + + if (duration == 0) + return "PT0S"; + + StringBuilder sb = new StringBuilder(); + sb.append('P'); + sb.append('T'); + long remainder = formatTimeDuration(sb, duration, TimeDuration.HOUR); + remainder = formatTimeDuration(sb, remainder, TimeDuration.MINUTE); + remainder = formatTimeDuration(sb, remainder, TimeDuration.SECOND); + + return sb.toString(); + } + +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/WorktimeFormat.java similarity index 93% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java rename to li.strolch.utils/src/main/java/li/strolch/utils/iso8601/WorktimeFormat.java index c026e060f..48c1e0f6f 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/iso8601/WorktimeFormat.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/iso8601/WorktimeFormat.java @@ -1,41 +1,41 @@ -/* - * Copyright 2013 Martin Smock - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.eitchnet.utils.iso8601; - -/** - * interface for the worktime format - * - * @author Martin Smock - */ -public interface WorktimeFormat { - - /** - * format a long to string - * - * @param l - * @return formatted string if the long argument - */ - public String format(long l); - - /** - * parse a string to long - * - * @param s - * @return the long value parsed - */ - public long parse(String s); - -} +/* + * Copyright 2013 Martin Smock + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.utils.iso8601; + +/** + * interface for the worktime format + * + * @author Martin Smock + */ +public interface WorktimeFormat { + + /** + * format a long to string + * + * @param l + * @return formatted string if the long argument + */ + public String format(long l); + + /** + * parse a string to long + * + * @param s + * @return the long value parsed + */ + public long parse(String s); + +} diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java b/li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/ObjectCache.java similarity index 98% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java rename to li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/ObjectCache.java index 8739e86d1..f1d25d897 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectCache.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/ObjectCache.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.objectfilter; +package li.strolch.utils.objectfilter; import java.text.MessageFormat; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java b/li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/ObjectFilter.java similarity index 99% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java rename to li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/ObjectFilter.java index 6ea65d1ac..b42c06139 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/ObjectFilter.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/ObjectFilter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.objectfilter; +package li.strolch.utils.objectfilter; import java.text.MessageFormat; import java.util.Collection; diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java b/li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/Operation.java similarity index 96% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java rename to li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/Operation.java index a54197fe4..21ae1c2b8 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/objectfilter/Operation.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/objectfilter/Operation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.objectfilter; +package li.strolch.utils.objectfilter; /** * A discrete set of operations associated to some object / state diff --git a/li.strolch.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java b/li.strolch.utils/src/main/java/li/strolch/utils/xml/XmlKeyValue.java similarity index 97% rename from li.strolch.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java rename to li.strolch.utils/src/main/java/li/strolch/utils/xml/XmlKeyValue.java index f813b230c..7856fc1a7 100644 --- a/li.strolch.utils/src/main/java/ch/eitchnet/utils/xml/XmlKeyValue.java +++ b/li.strolch.utils/src/main/java/li/strolch/utils/xml/XmlKeyValue.java @@ -1,4 +1,4 @@ -package ch.eitchnet.utils.xml; +package li.strolch.utils.xml; import java.util.ArrayList; import java.util.HashMap; diff --git a/li.strolch.utils/src/main/java/log4j.xml b/li.strolch.utils/src/main/java/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.utils/src/main/java/log4j.xml +++ b/li.strolch.utils/src/main/java/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java b/li.strolch.utils/src/test/java/li/strolch/communication/AbstractEndpointTest.java similarity index 96% rename from li.strolch.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java rename to li.strolch.utils/src/test/java/li/strolch/communication/AbstractEndpointTest.java index ec25837e9..e53f93825 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/communication/AbstractEndpointTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/communication/AbstractEndpointTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import static org.junit.Assert.fail; @@ -24,6 +24,8 @@ import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import li.strolch.communication.CommandKey; + /* * Copyright 2014 Robert von Burg * diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java b/li.strolch.utils/src/test/java/li/strolch/communication/ConsoleEndpointTest.java similarity index 84% rename from li.strolch.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java rename to li.strolch.utils/src/test/java/li/strolch/communication/ConsoleEndpointTest.java index c7090924f..852d9a5ec 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/communication/ConsoleEndpointTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/communication/ConsoleEndpointTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import static org.junit.Assert.assertEquals; @@ -24,8 +24,13 @@ import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; -import ch.eitchnet.communication.console.ConsoleEndpoint; -import ch.eitchnet.communication.console.ConsoleMessageVisitor; +import li.strolch.communication.CommandKey; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.CommunicationEndpoint; +import li.strolch.communication.ConnectionMode; +import li.strolch.communication.IoMessage; +import li.strolch.communication.console.ConsoleEndpoint; +import li.strolch.communication.console.ConsoleMessageVisitor; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java b/li.strolch.utils/src/test/java/li/strolch/communication/FileEndpointTest.java similarity index 90% rename from li.strolch.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java rename to li.strolch.utils/src/test/java/li/strolch/communication/FileEndpointTest.java index 9ae899d8c..e4da66b04 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/communication/FileEndpointTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/communication/FileEndpointTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import static org.junit.Assert.assertEquals; @@ -32,9 +32,15 @@ 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; +import li.strolch.communication.CommandKey; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.CommunicationEndpoint; +import li.strolch.communication.ConnectionMode; +import li.strolch.communication.IoMessage; +import li.strolch.communication.StreamMessageVisitor; +import li.strolch.communication.file.FileEndpoint; +import li.strolch.communication.file.FileEndpointMode; +import li.strolch.utils.helper.FileHelper; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java b/li.strolch.utils/src/test/java/li/strolch/communication/SimpleMessageArchiveTest.java similarity index 90% rename from li.strolch.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java rename to li.strolch.utils/src/test/java/li/strolch/communication/SimpleMessageArchiveTest.java index 483041519..ea38127f4 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/communication/SimpleMessageArchiveTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/communication/SimpleMessageArchiveTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import static org.junit.Assert.assertEquals; @@ -25,6 +25,11 @@ import java.util.UUID; import org.junit.Test; +import li.strolch.communication.CommandKey; +import li.strolch.communication.IoMessage; +import li.strolch.communication.IoMessageArchive; +import li.strolch.communication.SimpleMessageArchive; + /** * @author Robert von Burg */ diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java b/li.strolch.utils/src/test/java/li/strolch/communication/SocketEndpointTest.java similarity index 91% rename from li.strolch.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java rename to li.strolch.utils/src/test/java/li/strolch/communication/SocketEndpointTest.java index d2db3c0b5..bd2d5007c 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/communication/SocketEndpointTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/communication/SocketEndpointTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import static org.junit.Assert.assertEquals; @@ -32,10 +32,15 @@ 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; +import li.strolch.communication.CommandKey; +import li.strolch.communication.CommunicationConnection; +import li.strolch.communication.CommunicationEndpoint; +import li.strolch.communication.ConnectionMode; +import li.strolch.communication.IoMessage; +import li.strolch.communication.tcpip.ClientSocketEndpoint; +import li.strolch.communication.tcpip.ServerSocketEndpoint; +import li.strolch.communication.tcpip.SocketEndpointConstants; +import li.strolch.communication.tcpip.SocketMessageVisitor; /** * @author Robert von Burg diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java b/li.strolch.utils/src/test/java/li/strolch/communication/TestConnectionObserver.java similarity index 87% rename from li.strolch.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java rename to li.strolch.utils/src/test/java/li/strolch/communication/TestConnectionObserver.java index 36db42fc5..818b1b3e3 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/communication/TestConnectionObserver.java +++ b/li.strolch.utils/src/test/java/li/strolch/communication/TestConnectionObserver.java @@ -13,13 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import java.text.MessageFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import li.strolch.communication.CommandKey; +import li.strolch.communication.ConnectionObserver; +import li.strolch.communication.IoMessage; + /** * @author Robert von Burg */ diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java b/li.strolch.utils/src/test/java/li/strolch/communication/TestIoMessage.java similarity index 90% rename from li.strolch.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java rename to li.strolch.utils/src/test/java/li/strolch/communication/TestIoMessage.java index c4d473d08..72b583bd0 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/communication/TestIoMessage.java +++ b/li.strolch.utils/src/test/java/li/strolch/communication/TestIoMessage.java @@ -13,10 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.communication; +package li.strolch.communication; import java.util.List; +import li.strolch.communication.CommandKey; +import li.strolch.communication.IoMessage; + /** * @author Robert von Burg */ diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/StringMatchModeTest.java similarity index 89% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/StringMatchModeTest.java index c387ac305..fb318d741 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/StringMatchModeTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/StringMatchModeTest.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils; +package li.strolch.utils; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; +import li.strolch.utils.StringMatchMode; + /** * @author Robert von Burg * @@ -27,7 +29,7 @@ import org.junit.Test; public class StringMatchModeTest { /** - * Test method for {@link ch.eitchnet.utils.StringMatchMode#isCaseSensitve()}. + * Test method for {@link li.strolch.utils.StringMatchMode#isCaseSensitve()}. */ @Test public void testIsCaseSensitve() { @@ -38,7 +40,7 @@ public class StringMatchModeTest { } /** - * Test method for {@link ch.eitchnet.utils.StringMatchMode#isEquals()}. + * Test method for {@link li.strolch.utils.StringMatchMode#isEquals()}. */ @Test public void testIsEquals() { @@ -49,7 +51,7 @@ public class StringMatchModeTest { } /** - * Test method for {@link ch.eitchnet.utils.StringMatchMode#matches(java.lang.String, java.lang.String)}. + * Test method for {@link li.strolch.utils.StringMatchMode#matches(java.lang.String, java.lang.String)}. */ @SuppressWarnings("nls") @Test diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/VersionTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/VersionTest.java similarity index 97% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/VersionTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/VersionTest.java index 5381a6f8f..e675f5ff0 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/VersionTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/VersionTest.java @@ -1,4 +1,4 @@ -package ch.eitchnet.utils; +package li.strolch.utils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -6,7 +6,8 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.Version; +import li.strolch.utils.helper.StringHelper; /** * Tests the {@link Version} class diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/collections/DateRangeTest.java similarity index 87% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/collections/DateRangeTest.java index 5ec0bbc75..9bc4ef5c1 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DateRangeTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/collections/DateRangeTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -26,7 +26,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import ch.eitchnet.utils.dbc.DBC.DbcException; +import li.strolch.utils.collections.DateRange; +import li.strolch.utils.dbc.DBC.DbcException; public class DateRangeTest { @@ -45,7 +46,7 @@ public class DateRangeTest { } /** - * Test method for {@link ch.eitchnet.utils.collections.DateRange#to(java.util.Date, boolean)}. + * Test method for {@link li.strolch.utils.collections.DateRange#to(java.util.Date, boolean)}. */ @Test public void testTo() { @@ -59,7 +60,7 @@ public class DateRangeTest { } /** - * Test method for {@link ch.eitchnet.utils.collections.DateRange#to(java.util.Date,boolean)}. + * Test method for {@link li.strolch.utils.collections.DateRange#to(java.util.Date,boolean)}. */ @Test public void testFromTo() { @@ -83,7 +84,7 @@ public class DateRangeTest { } /** - * Test method for {@link ch.eitchnet.utils.collections.DateRange#isDate()}. + * Test method for {@link li.strolch.utils.collections.DateRange#isDate()}. */ @Test public void testIsDate() { @@ -100,7 +101,7 @@ public class DateRangeTest { } /** - * Test method for {@link ch.eitchnet.utils.collections.DateRange#contains(java.util.Date)}. + * Test method for {@link li.strolch.utils.collections.DateRange#contains(java.util.Date)}. */ @Test public void testContains() { diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/collections/DefaultedHashMapTest.java similarity index 93% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/collections/DefaultedHashMapTest.java index 1048d10e1..01e8ff8ea 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/DefaultedHashMapTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/collections/DefaultedHashMapTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -24,6 +24,8 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; +import li.strolch.utils.collections.DefaultedHashMap; + /** * @author Robert von Burg */ diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/collections/PagingTest.java similarity index 96% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/collections/PagingTest.java index 12e4a7036..edf470704 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/collections/PagingTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/collections/PagingTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.collections; +package li.strolch.utils.collections; import static org.junit.Assert.assertEquals; @@ -22,6 +22,8 @@ import java.util.List; import org.junit.Test; +import li.strolch.utils.collections.Paging; + /** * @author Robert von Burg */ diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/dbc/DBCTest.java similarity index 93% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/dbc/DBCTest.java index 7c18a3ab2..6eaf1b192 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/dbc/DBCTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/dbc/DBCTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.dbc; +package li.strolch.utils.dbc; import java.io.File; import java.text.MessageFormat; @@ -22,7 +22,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import ch.eitchnet.utils.dbc.DBC.DbcException; +import li.strolch.utils.dbc.DBC; +import li.strolch.utils.dbc.DBC.DbcException; /** * The class DBCTest contains tests for the class {@link DBC}. @@ -74,8 +75,8 @@ public class DBCTest { // add additional test code here // An unexpected exception was thrown in user code while executing this test: - // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Values are not equal: - // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) + // li.strolch.utils.DBC.PRE.DBC$DbcException: Values are not equal: + // at li.strolch.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) } /** @@ -139,8 +140,8 @@ public class DBCTest { // add additional test code here // An unexpected exception was thrown in user code while executing this test: - // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Values are not equal: - // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) + // li.strolch.utils.DBC.PRE.DBC$DbcException: Values are not equal: + // at li.strolch.utils.DBC.PRE.DBC.PRE.assertEquals(DBC.PRE.java:39) } /** @@ -269,8 +270,8 @@ public class DBCTest { // add additional test code here // An unexpected exception was thrown in user code while executing this test: - // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Illegal situation as file () does not exist: - // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertExists(DBC.PRE.java:95) + // li.strolch.utils.DBC.PRE.DBC$DbcException: Illegal situation as file () does not exist: + // at li.strolch.utils.DBC.PRE.DBC.PRE.assertExists(DBC.PRE.java:95) } /** @@ -471,8 +472,8 @@ public class DBCTest { // add additional test code here // An unexpected exception was thrown in user code while executing this test: - // ch.eitchnet.utils.DBC.PRE.DBC$DbcException: Expected true, but was false: - // at ch.eitchnet.utils.DBC.PRE.DBC.PRE.assertTrue(DBC.PRE.java:47) + // li.strolch.utils.DBC.PRE.DBC$DbcException: Expected true, but was false: + // at li.strolch.utils.DBC.PRE.DBC.PRE.assertTrue(DBC.PRE.java:47) } /** diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/helper/AesCryptoHelperTest.java similarity index 95% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/helper/AesCryptoHelperTest.java index 31b069146..a3b01faf7 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/AesCryptoHelperTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/helper/AesCryptoHelperTest.java @@ -1,4 +1,4 @@ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -14,6 +14,11 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import li.strolch.utils.helper.AesCryptoHelper; +import li.strolch.utils.helper.ExceptionHelper; +import li.strolch.utils.helper.FileHelper; +import li.strolch.utils.helper.StringHelper; + public class AesCryptoHelperTest { private static final Logger logger = LoggerFactory.getLogger(AesCryptoHelperTest.class); diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/helper/BaseDecodingTest.java similarity index 90% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/helper/BaseDecodingTest.java index a77e5ee5c..d261037af 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseDecodingTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/helper/BaseDecodingTest.java @@ -13,26 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; -import static ch.eitchnet.utils.helper.BaseEncoding.fromBase16; -import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32; -import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32Dmedia; -import static ch.eitchnet.utils.helper.BaseEncoding.fromBase32Hex; -import static ch.eitchnet.utils.helper.BaseEncoding.fromBase64; -import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; +import static li.strolch.utils.helper.BaseEncoding.fromBase16; +import static li.strolch.utils.helper.BaseEncoding.fromBase32; +import static li.strolch.utils.helper.BaseEncoding.fromBase32Dmedia; +import static li.strolch.utils.helper.BaseEncoding.fromBase32Hex; +import static li.strolch.utils.helper.BaseEncoding.fromBase64; +import static li.strolch.utils.helper.BaseEncoding.toBase32Hex; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import li.strolch.utils.helper.PropertiesHelper; +import li.strolch.utils.helper.StringHelper; + /** * @author Robert von Burg */ @SuppressWarnings("nls") public class BaseDecodingTest { - public static final String PROP_RUN_PERF_TESTS = "ch.eitchnet.utils.test.runPerfTests"; //$NON-NLS-1$ + public static final String PROP_RUN_PERF_TESTS = "li.strolch.utils.test.runPerfTests"; //$NON-NLS-1$ private static final Logger logger = LoggerFactory.getLogger(BaseDecodingTest.class); public static boolean isSkipPerfTests() { diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/helper/BaseEncodingTest.java similarity index 90% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/helper/BaseEncodingTest.java index 4c2da50d7..00398110c 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/BaseEncodingTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/helper/BaseEncodingTest.java @@ -13,21 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; -import static ch.eitchnet.utils.helper.BaseDecodingTest.PROP_RUN_PERF_TESTS; -import static ch.eitchnet.utils.helper.BaseDecodingTest.isSkipPerfTests; -import static ch.eitchnet.utils.helper.BaseEncoding.toBase16; -import static ch.eitchnet.utils.helper.BaseEncoding.toBase32; -import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Dmedia; -import static ch.eitchnet.utils.helper.BaseEncoding.toBase32Hex; -import static ch.eitchnet.utils.helper.BaseEncoding.toBase64; +import static li.strolch.utils.helper.BaseDecodingTest.PROP_RUN_PERF_TESTS; +import static li.strolch.utils.helper.BaseDecodingTest.isSkipPerfTests; +import static li.strolch.utils.helper.BaseEncoding.toBase16; +import static li.strolch.utils.helper.BaseEncoding.toBase32; +import static li.strolch.utils.helper.BaseEncoding.toBase32Dmedia; +import static li.strolch.utils.helper.BaseEncoding.toBase32Hex; +import static li.strolch.utils.helper.BaseEncoding.toBase64; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import li.strolch.utils.helper.StringHelper; + /** * @author Robert von Burg */ diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/helper/ExceptionHelperTest.java similarity index 93% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/helper/ExceptionHelperTest.java index eba21340e..5cdfec029 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ExceptionHelperTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/helper/ExceptionHelperTest.java @@ -1,10 +1,12 @@ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; +import li.strolch.utils.helper.ExceptionHelper; + public class ExceptionHelperTest { @Test diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java b/li.strolch.utils/src/test/java/li/strolch/utils/helper/GenerateReverseBaseEncodingAlphabets.java similarity index 96% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java rename to li.strolch.utils/src/test/java/li/strolch/utils/helper/GenerateReverseBaseEncodingAlphabets.java index e013c5659..dc4ab50e4 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/GenerateReverseBaseEncodingAlphabets.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/helper/GenerateReverseBaseEncodingAlphabets.java @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import java.util.HashMap; import java.util.Map; +import li.strolch.utils.helper.BaseEncoding; + /** * Simple helper class to generate the reverse alphabets for {@link BaseEncoding} * diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/helper/ReplacePropertiesInTest.java similarity index 97% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/helper/ReplacePropertiesInTest.java index f9525899d..9d75b685f 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/ReplacePropertiesInTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/helper/ReplacePropertiesInTest.java @@ -1,4 +1,4 @@ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import static org.junit.Assert.assertEquals; @@ -6,6 +6,8 @@ import java.util.Properties; import org.junit.Test; +import li.strolch.utils.helper.StringHelper; + public class ReplacePropertiesInTest { @Test diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/helper/XmlSignHelperTest.java similarity index 98% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/helper/XmlSignHelperTest.java index 5964354ab..14f339c82 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/helper/XmlSignHelperTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/helper/XmlSignHelperTest.java @@ -1,4 +1,4 @@ -package ch.eitchnet.utils.helper; +package li.strolch.utils.helper; import static org.junit.Assert.assertEquals; @@ -18,6 +18,8 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; +import li.strolch.utils.helper.XmlDomSigner; + public class XmlSignHelperTest { private static XmlDomSigner helper; diff --git a/li.strolch.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java b/li.strolch.utils/src/test/java/li/strolch/utils/objectfilter/ObjectFilterTest.java similarity index 99% rename from li.strolch.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java rename to li.strolch.utils/src/test/java/li/strolch/utils/objectfilter/ObjectFilterTest.java index e1126d25b..33b81fe89 100644 --- a/li.strolch.utils/src/test/java/ch/eitchnet/utils/objectfilter/ObjectFilterTest.java +++ b/li.strolch.utils/src/test/java/li/strolch/utils/objectfilter/ObjectFilterTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.utils.objectfilter; +package li.strolch.utils.objectfilter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -23,6 +23,8 @@ import java.util.List; import org.junit.Test; +import li.strolch.utils.objectfilter.ObjectFilter; + /** * @author Robert von Burg */ diff --git a/li.strolch.utils/src/test/resources/SignedXmlFile.xml b/li.strolch.utils/src/test/resources/SignedXmlFile.xml index 63197c399..7055600a5 100644 --- a/li.strolch.utils/src/test/resources/SignedXmlFile.xml +++ b/li.strolch.utils/src/test/resources/SignedXmlFile.xml @@ -2,7 +2,7 @@ UmOjwAl8RNygtziXuS5fZfvDUidhPugv2EUKeUkH7CwMkLSB5TONC+AS8eEhfyZbl/4GYMd4Jcqx OQJgBRMNT6zfybFY+wfJDceFUqCCmyXFgcGBmtSjJqQivwH4B8k1ui49hO67ItCBcCo0aKpqoIxF UA2IZCDvmrdR/qCq/oA9ssjUzpC+yJMvwPtZ6LDdoWt+MDzDBkCKA6lKUqtU51rZ8GwoRLve8FXH -vCG/ZiqxKn4JwBQL2DiFuZVXhrrMvLpYyi4ZRA==CN=test,OU=ch.eitchnet.utils,O=ch.eitchnet,L=Solothurn,ST=Solothurn,C=CHMIIDizCCAnOgAwIBAgIEPPVdzDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJDSDESMBAGA1UE +vCG/ZiqxKn4JwBQL2DiFuZVXhrrMvLpYyi4ZRA==CN=test,OU=li.strolch.utils,O=li.strolch,L=Solothurn,ST=Solothurn,C=CHMIIDizCCAnOgAwIBAgIEPPVdzDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJDSDESMBAGA1UE CBMJU29sb3RodXJuMRIwEAYDVQQHEwlTb2xvdGh1cm4xFDASBgNVBAoTC2NoLmVpdGNobmV0MRow GAYDVQQLExFjaC5laXRjaG5ldC51dGlsczENMAsGA1UEAxMEdGVzdDAeFw0xNjAzMDMxMzEwMTRa Fw0xNjA2MDExMzEwMTRaMHYxCzAJBgNVBAYTAkNIMRIwEAYDVQQIEwlTb2xvdGh1cm4xEjAQBgNV diff --git a/li.strolch.utils/src/test/resources/SignedXmlFileWithNamespaces.xml b/li.strolch.utils/src/test/resources/SignedXmlFileWithNamespaces.xml index 012c1783b..456b9172a 100644 --- a/li.strolch.utils/src/test/resources/SignedXmlFileWithNamespaces.xml +++ b/li.strolch.utils/src/test/resources/SignedXmlFileWithNamespaces.xml @@ -2,7 +2,7 @@ Nv03OGJcYlksdZ5CCGlsioac+NY/z2QngtlDaFudKIHwj9yZ9zMdiKT/4kdwnUQP+p9tzYV9GeA9 gesLOielMdj382XoFQ/CIbrJevE4vpn9FSitbwHXV4kZ3/NxlBPYIgiM9yiTTT0NafFENTS38U+P k1tL32FcDfHytWN6Twl2ZbHrRYltba/ncxaqkauMA37r9v2f+HS+hXXNluLTazRzAxnhSaetjPOt -GFP/nkG0TcRbiZFTP3YwIHeo94v2d/fs6rKHiQ==CN=test,OU=ch.eitchnet.utils,O=ch.eitchnet,L=Solothurn,ST=Solothurn,C=CHMIIDizCCAnOgAwIBAgIEPPVdzDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJDSDESMBAGA1UE +GFP/nkG0TcRbiZFTP3YwIHeo94v2d/fs6rKHiQ==CN=test,OU=li.strolch.utils,O=li.strolch,L=Solothurn,ST=Solothurn,C=CHMIIDizCCAnOgAwIBAgIEPPVdzDANBgkqhkiG9w0BAQsFADB2MQswCQYDVQQGEwJDSDESMBAGA1UE CBMJU29sb3RodXJuMRIwEAYDVQQHEwlTb2xvdGh1cm4xFDASBgNVBAoTC2NoLmVpdGNobmV0MRow GAYDVQQLExFjaC5laXRjaG5ldC51dGlsczENMAsGA1UEAxMEdGVzdDAeFw0xNjAzMDMxMzEwMTRa Fw0xNjA2MDExMzEwMTRaMHYxCzAJBgNVBAYTAkNIMRIwEAYDVQQIEwlTb2xvdGh1cm4xEjAQBgNV diff --git a/li.strolch.utils/src/test/resources/log4j.xml b/li.strolch.utils/src/test/resources/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.utils/src/test/resources/log4j.xml +++ b/li.strolch.utils/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/li.strolch.xmlpers/README.md b/li.strolch.xmlpers/README.md index ec5c13c8c..051b06fc8 100644 --- a/li.strolch.xmlpers/README.md +++ b/li.strolch.xmlpers/README.md @@ -1,7 +1,7 @@ -ch.eitchnet.java.xmlpers +li.strolch.java.xmlpers ======================== -[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=ch.eitchnet.xmlpers)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.xmlpers/) +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=li.strolch.xmlpers)](http://jenkins.eitchnet.ch/view/li.strolch/job/li.strolch.xmlpers/) Generic Java XML persistence layer. Implemented to be light-weight and simple to use @@ -9,7 +9,7 @@ Dependencies ------------------------ XmlPers is built by Maven3 and has very few external dependencies. The current dependencies are: * the Java Runtime Environment 6 -* ch.eitchnet.utils +* li.strolch.utils * slf4j 1.7.2 * slf4j-log4j bindings (only during tests) * JUnit 4.10 (only during tests) @@ -32,7 +32,7 @@ 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 - * ch.eitchnet.utils is installed in your local Maven Repository + * li.strolch.utils is installed in your local Maven Repository * Clone repository and change path to root * Run maven: * mvn clean install diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/DomParser.java similarity index 95% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/DomParser.java index 3168364cd..1752cb209 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/DomParser.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import org.w3c.dom.Document; diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/FileDao.java similarity index 98% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/FileDao.java index 8a7e54af4..0f744af14 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/FileDao.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import java.io.File; import java.text.MessageFormat; @@ -21,8 +21,8 @@ import java.text.MessageFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.xmlpers.impl.PathBuilder; -import ch.eitchnet.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.objref.ObjectRef; public class FileDao { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/FileIo.java similarity index 96% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/FileIo.java index 9aaa97e45..81b15c357 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/FileIo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import java.io.File; import java.io.FileWriter; @@ -21,6 +21,10 @@ import java.io.IOException; import java.text.MessageFormat; import javanet.staxutils.IndentingXMLStreamWriter; +import li.strolch.utils.exceptions.XmlException; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.helper.XmlHelper; +import li.strolch.xmlpers.util.DomUtil; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; @@ -45,11 +49,6 @@ import org.w3c.dom.Document; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.utils.exceptions.XmlException; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.helper.XmlHelper; -import ch.eitchnet.xmlpers.util.DomUtil; - public class FileIo { public static final String DEFAULT_XML_VERSION = "1.0"; //$NON-NLS-1$ diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/IoMode.java similarity index 97% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/IoMode.java index 7e5c7ee52..e3b775da9 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/IoMode.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/IoOperation.java similarity index 95% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/IoOperation.java index ff4fd47ed..cbbb147e0 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/IoOperation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; public enum IoOperation { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/MetadataDao.java similarity index 97% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/MetadataDao.java index 7a114bb7a..49e0e79e1 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/MetadataDao.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import java.io.File; import java.text.MessageFormat; @@ -24,9 +24,9 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.xmlpers.impl.PathBuilder; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.util.FilenameUtility; +import li.strolch.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.util.FilenameUtility; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ModificationResult.java similarity index 97% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ModificationResult.java index 761513488..1b875a224 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ModificationResult.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import java.util.ArrayList; import java.util.List; diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ObjectDao.java similarity index 92% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ObjectDao.java index 493a749c8..03d9a3436 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ObjectDao.java @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; -import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsIdRef; -import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsNotIdRef; -import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsNotRootRef; -import static ch.eitchnet.xmlpers.util.AssertionUtil.assertNotNull; -import static ch.eitchnet.xmlpers.util.AssertionUtil.assertObjectRead; +import static li.strolch.xmlpers.util.AssertionUtil.assertIsIdRef; +import static li.strolch.xmlpers.util.AssertionUtil.assertIsNotIdRef; +import static li.strolch.xmlpers.util.AssertionUtil.assertIsNotRootRef; +import static li.strolch.xmlpers.util.AssertionUtil.assertNotNull; +import static li.strolch.xmlpers.util.AssertionUtil.assertObjectRead; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import ch.eitchnet.utils.objectfilter.ObjectFilter; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.SubTypeRef; -import ch.eitchnet.xmlpers.objref.TypeRef; +import li.strolch.utils.objectfilter.ObjectFilter; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.SubTypeRef; +import li.strolch.xmlpers.objref.TypeRef; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ParserFactory.java similarity index 95% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ParserFactory.java index 003b1236d..e6a02d551 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/ParserFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; public interface ParserFactory { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceConstants.java similarity index 91% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceConstants.java index 40fd39738..9f5d6224c 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceConstants.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; /** * @author Robert von Burg @@ -22,7 +22,7 @@ package ch.eitchnet.xmlpers.api; @SuppressWarnings("nls") public class PersistenceConstants { - private static final String PROP_PREFIX = "ch.eitchnet.xmlpers."; + private static final String PROP_PREFIX = "li.strolch.xmlpers."; public static final String PROP_VERBOSE = PROP_PREFIX + "verbose"; public static final String PROP_BASEPATH = PROP_PREFIX + "basePath"; public static final String PROP_DAO_FACTORY_CLASS = PROP_PREFIX + "daoFactoryClass"; diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContext.java similarity index 96% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContext.java index 0faa8688b..3963e52e3 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContext.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; -import ch.eitchnet.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.ObjectRef; public class PersistenceContext { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContextFactory.java similarity index 85% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContextFactory.java index 3feb45dd9..44a485ae9 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContextFactory.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.ObjectReferenceCache; public interface PersistenceContextFactory { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContextFactoryDelegator.java similarity index 98% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContextFactoryDelegator.java index 8be71335d..ae721d5f6 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceContextFactoryDelegator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import java.text.MessageFormat; import java.util.HashMap; diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceManager.java similarity index 96% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceManager.java index e19b71521..0a10cea56 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceManagerLoader.java similarity index 91% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceManagerLoader.java index ecbf0ba6d..ab3d50c70 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceManagerLoader.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import java.util.Properties; -import ch.eitchnet.xmlpers.impl.DefaultPersistenceManager; +import li.strolch.xmlpers.impl.DefaultPersistenceManager; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceRealm.java similarity index 91% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceRealm.java index 4cc3e82fa..bf54f27ec 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceRealm.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.objref.ObjectReferenceCache; public interface PersistenceRealm { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceTransaction.java similarity index 95% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceTransaction.java index 0f2efa7e5..3cd9ec0c2 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/PersistenceTransaction.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.objref.ObjectReferenceCache; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/SaxParser.java similarity index 96% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/SaxParser.java index 352b43f33..a51d2b715 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/SaxParser.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionCloseStrategy.java similarity index 96% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionCloseStrategy.java index 987a8ffbc..b35eb80fd 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionCloseStrategy.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; public enum TransactionCloseStrategy { COMMIT() { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionResult.java similarity index 98% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionResult.java index dbd72c324..bc5f2cae4 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionResult.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; public class TransactionResult { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionState.java similarity index 95% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionState.java index 42900e5a4..f0a62334e 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/TransactionState.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; public enum TransactionState { OPEN, // diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/XmlPersistenceException.java similarity index 96% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/XmlPersistenceException.java index 258118918..c57604208 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/api/XmlPersistenceException.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.api; +package li.strolch.xmlpers.api; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceManager.java similarity index 88% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceManager.java index 32eff87f7..bf57b6808 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.impl; +package li.strolch.xmlpers.impl; import java.io.File; import java.lang.reflect.Field; @@ -25,16 +25,16 @@ import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.PropertiesHelper; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; -import ch.eitchnet.xmlpers.api.PersistenceManager; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.objref.LockableObject; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.utils.helper.PropertiesHelper; +import li.strolch.utils.helper.StringHelper; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceContextFactoryDelegator; +import li.strolch.xmlpers.api.PersistenceManager; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.api.XmlPersistenceException; +import li.strolch.xmlpers.objref.LockableObject; +import li.strolch.xmlpers.objref.ObjectReferenceCache; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceRealm.java similarity index 88% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceRealm.java index 547ffc262..33046f9d7 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceRealm.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.impl; +package li.strolch.xmlpers.impl; -import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; -import ch.eitchnet.xmlpers.api.PersistenceManager; -import ch.eitchnet.xmlpers.api.PersistenceRealm; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.api.PersistenceContextFactoryDelegator; +import li.strolch.xmlpers.api.PersistenceManager; +import li.strolch.xmlpers.api.PersistenceRealm; +import li.strolch.xmlpers.objref.ObjectReferenceCache; public class DefaultPersistenceRealm implements PersistenceRealm { private final PersistenceManager persistenceManager; diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceTransaction.java similarity index 92% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceTransaction.java index 1590aedba..14160e6ab 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/DefaultPersistenceTransaction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.impl; +package li.strolch.xmlpers.impl; import java.text.MessageFormat; import java.util.Collections; @@ -26,21 +26,21 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.objectfilter.ObjectFilter; -import ch.eitchnet.xmlpers.api.FileDao; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.MetadataDao; -import ch.eitchnet.xmlpers.api.ModificationResult; -import ch.eitchnet.xmlpers.api.ObjectDao; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceRealm; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.api.TransactionCloseStrategy; -import ch.eitchnet.xmlpers.api.TransactionResult; -import ch.eitchnet.xmlpers.api.TransactionState; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import li.strolch.utils.helper.StringHelper; +import li.strolch.utils.objectfilter.ObjectFilter; +import li.strolch.xmlpers.api.FileDao; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.MetadataDao; +import li.strolch.xmlpers.api.ModificationResult; +import li.strolch.xmlpers.api.ObjectDao; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceRealm; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.api.TransactionCloseStrategy; +import li.strolch.xmlpers.api.TransactionResult; +import li.strolch.xmlpers.api.TransactionState; +import li.strolch.xmlpers.api.XmlPersistenceException; +import li.strolch.xmlpers.objref.ObjectReferenceCache; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/PathBuilder.java similarity index 94% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/PathBuilder.java index 8b0d582a3..1fb7ed25d 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/impl/PathBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.impl; +package li.strolch.xmlpers.impl; import java.io.File; import java.io.IOException; @@ -23,10 +23,10 @@ import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.PropertiesHelper; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import li.strolch.utils.helper.PropertiesHelper; +import li.strolch.utils.helper.StringHelper; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.XmlPersistenceException; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/IdOfSubTypeRef.java similarity index 92% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/IdOfSubTypeRef.java index bb4d206c1..c4a3c1031 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/IdOfSubTypeRef.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; import java.io.File; import java.text.MessageFormat; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.api.PersistenceContextFactoryDelegator; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.impl.PathBuilder; public class IdOfSubTypeRef extends ObjectRef { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/IdOfTypeRef.java similarity index 91% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/IdOfTypeRef.java index a110df9b1..906a29118 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/IdOfTypeRef.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; import java.io.File; import java.text.MessageFormat; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.api.PersistenceContextFactoryDelegator; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.impl.PathBuilder; public class IdOfTypeRef extends ObjectRef { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/LockableObject.java similarity index 93% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/LockableObject.java index 545de62d7..7734a0049 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/LockableObject.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; import java.text.MessageFormat; import java.util.concurrent.TimeUnit; @@ -22,8 +22,8 @@ import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import li.strolch.utils.helper.StringHelper; +import li.strolch.xmlpers.api.XmlPersistenceException; public class LockableObject { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/ObjectRef.java similarity index 89% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/ObjectRef.java index bc6810914..c03d5dbb3 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/ObjectRef.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; import java.io.File; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.impl.PathBuilder; public abstract class ObjectRef extends LockableObject { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/ObjectReferenceCache.java similarity index 97% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/ObjectReferenceCache.java index 67fd55562..a2156141d 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/ObjectReferenceCache.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; import java.util.HashMap; import java.util.Map; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; public class ObjectReferenceCache { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/RefNameCreator.java similarity index 96% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/RefNameCreator.java index d408c758d..5c1c87576 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/RefNameCreator.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; public class RefNameCreator { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/RootRef.java similarity index 93% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/RootRef.java index 3776544c9..03677a366 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/RootRef.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; import java.io.File; import java.text.MessageFormat; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.impl.PathBuilder; public class RootRef extends ObjectRef { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/SubTypeRef.java similarity index 94% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/SubTypeRef.java index ed792d5f5..a94c5a46d 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/SubTypeRef.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; import java.io.File; import java.text.MessageFormat; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.impl.PathBuilder; public class SubTypeRef extends ObjectRef { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/TypeRef.java similarity index 93% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/TypeRef.java index 738a2cff7..90e8f50e1 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/objref/TypeRef.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.objref; +package li.strolch.xmlpers.objref; import java.io.File; import java.text.MessageFormat; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.impl.PathBuilder; public class TypeRef extends ObjectRef { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/AssertionUtil.java similarity index 93% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/AssertionUtil.java index 6a3c9535a..5419da935 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/AssertionUtil.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.util; +package li.strolch.xmlpers.util; import java.text.MessageFormat; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.objref.ObjectRef; public class AssertionUtil { diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/DomUtil.java similarity index 93% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/DomUtil.java index fa2e9c9bf..fa74f83c1 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/DomUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.util; +package li.strolch.xmlpers.util; import java.text.MessageFormat; @@ -21,7 +21,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import li.strolch.xmlpers.api.XmlPersistenceException; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/FilenameUtility.java similarity index 89% rename from li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java rename to li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/FilenameUtility.java index bd67391b5..33cb41f44 100644 --- a/li.strolch.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java +++ b/li.strolch.xmlpers/src/main/java/li/strolch/xmlpers/util/FilenameUtility.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.util; +package li.strolch.xmlpers.util; import java.text.MessageFormat; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.api.XmlPersistenceException; +import li.strolch.xmlpers.impl.PathBuilder; public class FilenameUtility { diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/AbstractPersistenceTest.java similarity index 81% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/AbstractPersistenceTest.java index 312a9f19e..dcdaa4d90 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/AbstractPersistenceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test; +package li.strolch.xmlpers.test; import java.io.File; import java.util.Properties; @@ -21,16 +21,16 @@ import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.utils.helper.FileHelper; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceManager; -import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; -import ch.eitchnet.xmlpers.test.impl.BookContextFactory; -import ch.eitchnet.xmlpers.test.impl.MyModelContextFactory; -import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.Book; -import ch.eitchnet.xmlpers.test.model.MyModel; +import li.strolch.utils.helper.FileHelper; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceManager; +import li.strolch.xmlpers.api.PersistenceManagerLoader; +import li.strolch.xmlpers.test.impl.BookContextFactory; +import li.strolch.xmlpers.test.impl.MyModelContextFactory; +import li.strolch.xmlpers.test.impl.TestConstants; +import li.strolch.xmlpers.test.model.Book; +import li.strolch.xmlpers.test.model.MyModel; public abstract class AbstractPersistenceTest { diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/FileDaoTest.java similarity index 76% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/FileDaoTest.java index 31f19d4d1..c80114a63 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/FileDaoTest.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test; +package li.strolch.xmlpers.test; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static li.strolch.xmlpers.test.model.ModelBuilder.assertResource; +import static li.strolch.xmlpers.test.model.ModelBuilder.assertResourceUpdated; +import static li.strolch.xmlpers.test.model.ModelBuilder.createResource; +import static li.strolch.xmlpers.test.model.ModelBuilder.updateResource; import static org.junit.Assert.assertNull; import java.util.Properties; @@ -26,19 +26,19 @@ import java.util.Properties; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.xmlpers.api.FileDao; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; -import ch.eitchnet.xmlpers.api.PersistenceManager; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.impl.DefaultPersistenceRealm; -import ch.eitchnet.xmlpers.impl.DefaultPersistenceTransaction; -import ch.eitchnet.xmlpers.impl.PathBuilder; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; -import ch.eitchnet.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.api.FileDao; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.api.PersistenceContextFactoryDelegator; +import li.strolch.xmlpers.api.PersistenceManager; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.impl.DefaultPersistenceRealm; +import li.strolch.xmlpers.impl.DefaultPersistenceTransaction; +import li.strolch.xmlpers.impl.PathBuilder; +import li.strolch.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.test.model.MyModel; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/LockingTest.java similarity index 90% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/LockingTest.java index 906d61a4c..2b1e0cb1a 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/LockingTest.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test; +package li.strolch.xmlpers.test; -import static ch.eitchnet.xmlpers.test.impl.TestConstants.TYPE_RES; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static li.strolch.xmlpers.test.impl.TestConstants.TYPE_RES; +import static li.strolch.xmlpers.test.model.ModelBuilder.RES_TYPE; +import static li.strolch.xmlpers.test.model.ModelBuilder.createResource; +import static li.strolch.xmlpers.test.model.ModelBuilder.updateResource; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -30,12 +30,12 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.LockableObject; -import ch.eitchnet.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.LockableObject; +import li.strolch.xmlpers.test.model.MyModel; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/ObjectDaoBookTest.java similarity index 88% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/ObjectDaoBookTest.java index 840d95e63..c457d769c 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/ObjectDaoBookTest.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test; +package li.strolch.xmlpers.test; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.BOOK_ID; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBook; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBookUpdated; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createBook; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateBook; +import static li.strolch.xmlpers.test.model.ModelBuilder.BOOK_ID; +import static li.strolch.xmlpers.test.model.ModelBuilder.assertBook; +import static li.strolch.xmlpers.test.model.ModelBuilder.assertBookUpdated; +import static li.strolch.xmlpers.test.model.ModelBuilder.createBook; +import static li.strolch.xmlpers.test.model.ModelBuilder.updateBook; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -31,15 +31,15 @@ import java.util.Properties; import org.junit.Before; import org.junit.Test; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.ObjectDao; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.objref.IdOfTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.TypeRef; -import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.Book; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.ObjectDao; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.objref.IdOfTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.TypeRef; +import li.strolch.xmlpers.test.impl.TestConstants; +import li.strolch.xmlpers.test.model.Book; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/ObjectDaoResourceTest.java similarity index 90% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/ObjectDaoResourceTest.java index 72e40c3ae..5937b0e5e 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/ObjectDaoResourceTest.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test; +package li.strolch.xmlpers.test; -import static ch.eitchnet.xmlpers.test.impl.TestConstants.TYPE_RES; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static li.strolch.xmlpers.test.impl.TestConstants.TYPE_RES; +import static li.strolch.xmlpers.test.model.ModelBuilder.RES_ID; +import static li.strolch.xmlpers.test.model.ModelBuilder.RES_TYPE; +import static li.strolch.xmlpers.test.model.ModelBuilder.assertResource; +import static li.strolch.xmlpers.test.model.ModelBuilder.assertResourceUpdated; +import static li.strolch.xmlpers.test.model.ModelBuilder.createResource; +import static li.strolch.xmlpers.test.model.ModelBuilder.updateResource; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -36,17 +36,17 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.ObjectDao; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.api.XmlPersistenceException; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.SubTypeRef; -import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.ModelBuilder; -import ch.eitchnet.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.ObjectDao; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.api.XmlPersistenceException; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.SubTypeRef; +import li.strolch.xmlpers.test.impl.TestConstants; +import li.strolch.xmlpers.test.model.ModelBuilder; +import li.strolch.xmlpers.test.model.MyModel; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/RealmTest.java similarity index 91% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/RealmTest.java index d3f91e337..d3689d406 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/RealmTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test; +package li.strolch.xmlpers.test; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -24,13 +24,13 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.test.impl.TestConstants; -import ch.eitchnet.xmlpers.test.model.ModelBuilder; -import ch.eitchnet.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.test.impl.TestConstants; +import li.strolch.xmlpers.test.model.ModelBuilder; +import li.strolch.xmlpers.test.model.MyModel; @SuppressWarnings("nls") public class RealmTest extends AbstractPersistenceTest { diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/TransactionResultTest.java similarity index 87% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/TransactionResultTest.java index 8aa5e5a53..8d7c345dc 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/TransactionResultTest.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test; +package li.strolch.xmlpers.test; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; -import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static li.strolch.xmlpers.test.model.ModelBuilder.RES_ID; +import static li.strolch.xmlpers.test.model.ModelBuilder.createResource; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; @@ -31,14 +31,14 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import ch.eitchnet.xmlpers.api.IoMode; -import ch.eitchnet.xmlpers.api.ModificationResult; -import ch.eitchnet.xmlpers.api.ObjectDao; -import ch.eitchnet.xmlpers.api.PersistenceConstants; -import ch.eitchnet.xmlpers.api.PersistenceTransaction; -import ch.eitchnet.xmlpers.api.TransactionResult; -import ch.eitchnet.xmlpers.test.model.Book; -import ch.eitchnet.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.api.IoMode; +import li.strolch.xmlpers.api.ModificationResult; +import li.strolch.xmlpers.api.ObjectDao; +import li.strolch.xmlpers.api.PersistenceConstants; +import li.strolch.xmlpers.api.PersistenceTransaction; +import li.strolch.xmlpers.api.TransactionResult; +import li.strolch.xmlpers.test.model.Book; +import li.strolch.xmlpers.test.model.MyModel; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/XmlTestMain.java similarity index 97% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/XmlTestMain.java index 32af884e2..a68c9a8d8 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/XmlTestMain.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test; +package li.strolch.xmlpers.test; import java.io.File; import java.io.FileWriter; @@ -21,6 +21,11 @@ import java.util.ArrayList; import java.util.List; import javanet.staxutils.IndentingXMLStreamWriter; +import li.strolch.utils.exceptions.XmlException; +import li.strolch.utils.helper.XmlHelper; +import li.strolch.xmlpers.test.model.ModelBuilder; +import li.strolch.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.test.model.MyParameter; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -44,12 +49,6 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.utils.exceptions.XmlException; -import ch.eitchnet.utils.helper.XmlHelper; -import ch.eitchnet.xmlpers.test.model.ModelBuilder; -import ch.eitchnet.xmlpers.test.model.MyModel; -import ch.eitchnet.xmlpers.test.model.MyParameter; - /** * @author Robert von Burg * diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookContextFactory.java similarity index 78% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookContextFactory.java index 50563cb82..59b433bcb 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookContextFactory.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.objref.IdOfTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; -import ch.eitchnet.xmlpers.test.model.Book; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.objref.IdOfTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.test.model.Book; public class BookContextFactory implements PersistenceContextFactory { diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookDomParser.java similarity index 93% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookDomParser.java index a07e19be8..3e0fc1305 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookDomParser.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; import javax.xml.parsers.DocumentBuilder; import org.w3c.dom.Document; import org.w3c.dom.Element; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.test.model.Book; -import ch.eitchnet.xmlpers.util.DomUtil; +import li.strolch.xmlpers.api.DomParser; +import li.strolch.xmlpers.test.model.Book; +import li.strolch.xmlpers.util.DomUtil; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookParserFactory.java similarity index 81% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookParserFactory.java index 9de53c54e..de096413f 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookParserFactory.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.api.ParserFactory; -import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.test.model.Book; +import li.strolch.xmlpers.api.DomParser; +import li.strolch.xmlpers.api.ParserFactory; +import li.strolch.xmlpers.api.SaxParser; +import li.strolch.xmlpers.test.model.Book; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookSaxParser.java similarity index 94% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookSaxParser.java index 154df1afa..7eb820839 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/BookSaxParser.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -22,8 +22,8 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.test.model.Book; +import li.strolch.xmlpers.api.SaxParser; +import li.strolch.xmlpers.test.model.Book; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelContextFactory.java similarity index 78% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelContextFactory.java index 718813d46..860cfe5e6 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelContextFactory.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; -import ch.eitchnet.xmlpers.api.PersistenceContext; -import ch.eitchnet.xmlpers.api.PersistenceContextFactory; -import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; -import ch.eitchnet.xmlpers.objref.ObjectRef; -import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; -import ch.eitchnet.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.api.PersistenceContext; +import li.strolch.xmlpers.api.PersistenceContextFactory; +import li.strolch.xmlpers.objref.IdOfSubTypeRef; +import li.strolch.xmlpers.objref.ObjectRef; +import li.strolch.xmlpers.objref.ObjectReferenceCache; +import li.strolch.xmlpers.test.model.MyModel; public class MyModelContextFactory implements PersistenceContextFactory { diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelDomParser.java similarity index 93% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelDomParser.java index 30859879e..9fddf9fb0 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelDomParser.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; import javax.xml.parsers.DocumentBuilder; @@ -22,10 +22,10 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.test.model.MyModel; -import ch.eitchnet.xmlpers.test.model.MyParameter; -import ch.eitchnet.xmlpers.util.DomUtil; +import li.strolch.xmlpers.api.DomParser; +import li.strolch.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.test.model.MyParameter; +import li.strolch.xmlpers.util.DomUtil; public class MyModelDomParser implements DomParser { diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelParserFactory.java similarity index 80% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelParserFactory.java index 0760861fb..233473c31 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelParserFactory.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; -import ch.eitchnet.xmlpers.api.DomParser; -import ch.eitchnet.xmlpers.api.ParserFactory; -import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.api.DomParser; +import li.strolch.xmlpers.api.ParserFactory; +import li.strolch.xmlpers.api.SaxParser; +import li.strolch.xmlpers.test.model.MyModel; public class MyModelParserFactory implements ParserFactory { diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelSaxParser.java similarity index 93% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelSaxParser.java index 0cb2f0780..e4958d08e 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/MyModelSaxParser.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -22,9 +22,9 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; -import ch.eitchnet.xmlpers.api.SaxParser; -import ch.eitchnet.xmlpers.test.model.MyModel; -import ch.eitchnet.xmlpers.test.model.MyParameter; +import li.strolch.xmlpers.api.SaxParser; +import li.strolch.xmlpers.test.model.MyModel; +import li.strolch.xmlpers.test.model.MyParameter; class MyModelSaxParser extends DefaultHandler implements SaxParser { diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/TestConstants.java similarity index 95% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/TestConstants.java index cfa62b831..32d9ba515 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/impl/TestConstants.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.impl; +package li.strolch.xmlpers.test.impl; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/Book.java similarity index 98% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/Book.java index 536be946c..1b2ed7326 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/Book.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.model; +package li.strolch.xmlpers.test.model; /** * @author Robert von Burg diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/ModelBuilder.java similarity index 99% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/ModelBuilder.java index be4d9ff78..f30200e3d 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/ModelBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.model; +package li.strolch.xmlpers.test.model; import org.junit.Assert; diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/MyModel.java similarity index 98% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/MyModel.java index f8f90058a..7336ff11a 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/MyModel.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.model; +package li.strolch.xmlpers.test.model; import java.util.HashMap; import java.util.Map; diff --git a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/MyParameter.java similarity index 98% rename from li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java rename to li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/MyParameter.java index d4ca1f189..ff5a7f5f3 100644 --- a/li.strolch.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java +++ b/li.strolch.xmlpers/src/test/java/li/strolch/xmlpers/test/model/MyParameter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ch.eitchnet.xmlpers.test.model; +package li.strolch.xmlpers.test.model; public class MyParameter { diff --git a/li.strolch.xmlpers/src/test/resources/log4j.xml b/li.strolch.xmlpers/src/test/resources/log4j.xml index f223bc5cb..216c8d616 100644 --- a/li.strolch.xmlpers/src/test/resources/log4j.xml +++ b/li.strolch.xmlpers/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/strolch_minimal/src/main/resources/log4j.xml b/strolch_minimal/src/main/resources/log4j.xml index 1d2f3b080..c886d766d 100644 --- a/strolch_minimal/src/main/resources/log4j.xml +++ b/strolch_minimal/src/main/resources/log4j.xml @@ -17,7 +17,7 @@ - + diff --git a/strolch_minimal/src/runtime/config/PrivilegeConfig.xml b/strolch_minimal/src/runtime/config/PrivilegeConfig.xml index e264a8929..14dc76906 100644 --- a/strolch_minimal/src/runtime/config/PrivilegeConfig.xml +++ b/strolch_minimal/src/runtime/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,7 +24,7 @@ - + \ No newline at end of file diff --git a/strolch_minimal/src/runtime/config/PrivilegeModel.xml b/strolch_minimal/src/runtime/config/PrivilegeModel.xml index 8132bbc46..69329b7c9 100644 --- a/strolch_minimal/src/runtime/config/PrivilegeModel.xml +++ b/strolch_minimal/src/runtime/config/PrivilegeModel.xml @@ -25,7 +25,7 @@ - + li.strolch.agent.impl.StartRealms diff --git a/strolch_minimal_rest/src/main/java/li/strolch/minimal/rest/util/Result.java b/strolch_minimal_rest/src/main/java/li/strolch/minimal/rest/util/Result.java index de5072134..f43d9d718 100644 --- a/strolch_minimal_rest/src/main/java/li/strolch/minimal/rest/util/Result.java +++ b/strolch_minimal_rest/src/main/java/li/strolch/minimal/rest/util/Result.java @@ -15,7 +15,7 @@ */ package li.strolch.minimal.rest.util; -import ch.eitchnet.utils.helper.StringHelper; +import li.strolch.utils.helper.StringHelper; /** * @author Robert von Burg diff --git a/strolch_minimal_rest/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml b/strolch_minimal_rest/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml index e264a8929..14dc76906 100644 --- a/strolch_minimal_rest/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml +++ b/strolch_minimal_rest/src/main/webapp/WEB-INF/config/PrivilegeConfig.xml @@ -8,13 +8,13 @@ - + - + @@ -24,7 +24,7 @@ - + \ No newline at end of file diff --git a/strolch_minimal_rest/src/main/webapp/WEB-INF/config/PrivilegeModel.xml b/strolch_minimal_rest/src/main/webapp/WEB-INF/config/PrivilegeModel.xml index 8132bbc46..69329b7c9 100644 --- a/strolch_minimal_rest/src/main/webapp/WEB-INF/config/PrivilegeModel.xml +++ b/strolch_minimal_rest/src/main/webapp/WEB-INF/config/PrivilegeModel.xml @@ -25,7 +25,7 @@ - + li.strolch.agent.impl.StartRealms From b044d9a64b762d21c3602171b04be4f16e3d96ab Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 26 Jun 2016 11:40:35 +0200 Subject: [PATCH 452/457] [Major] Moved ch.eitchnet to li.strolch --- li.strolch.privilege/src/test/resources/log4j.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/li.strolch.privilege/src/test/resources/log4j.xml b/li.strolch.privilege/src/test/resources/log4j.xml index 0a2a73d06..7a0499275 100644 --- a/li.strolch.privilege/src/test/resources/log4j.xml +++ b/li.strolch.privilege/src/test/resources/log4j.xml @@ -17,7 +17,7 @@ - + From df7a7f620821b7019755d10346f474addf526626 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 26 Jun 2016 12:33:20 +0200 Subject: [PATCH 453/457] [Major] Set strolch version to 1.2.0-SNAPSHOT --- li.strolch.dev/createBundle.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/li.strolch.dev/createBundle.sh b/li.strolch.dev/createBundle.sh index f5925067c..dfd7476e5 100755 --- a/li.strolch.dev/createBundle.sh +++ b/li.strolch.dev/createBundle.sh @@ -1,7 +1,7 @@ #!/bin/bash projectName=strolch_bundle -projectVersion=1.1.0-SNAPSHOT +projectVersion=1.2.0-SNAPSHOT bundle_name="${projectName}-${projectVersion}" DIST_STROLCH="/var/www/eitch/strolch.li/dist/snapshot" DEPLOY_SERVER="hosting.eitchnet.ch" From 28f978a72b4aef3bda2409f8a2d4bd7b12778454 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 26 Jun 2016 12:34:48 +0200 Subject: [PATCH 454/457] [Project] Removed parent --- li.strolch.dev/projects_all.lst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/li.strolch.dev/projects_all.lst b/li.strolch.dev/projects_all.lst index 0cac22be3..049c79444 100644 --- a/li.strolch.dev/projects_all.lst +++ b/li.strolch.dev/projects_all.lst @@ -1,9 +1,7 @@ -ch.eitchnet.parent:develop -ch.eitchnet.privilege:develop -ch.eitchnet.utils:develop -ch.eitchnet.xmlpers:develop +li.strolch.utils:develop +li.strolch.privilege:develop +li.strolch.xmlpers:develop li.strolch.dev:develop -li.strolch.parent:develop li.strolch.bom:develop li.strolch.model:develop li.strolch.testbase:develop From 055ab442de7383f5131d8e1df1c95fcbd34f09a0 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 2 Jul 2016 18:42:57 +0200 Subject: [PATCH 455/457] Update README.md --- README.md | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/README.md b/README.md index 83e6aedc8..08fddf090 100644 --- a/README.md +++ b/README.md @@ -26,36 +26,4 @@ Find more to Strolch at our website: http://strolch.li Build status =================== -[![Build Status](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.parent/badge/icon)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.parent/) ch.eitchnet.parent - -[![Build Status](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.privilege/badge/icon)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.privilege/) ch.eitchnet.privilege - -[![Build Status](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.utils/badge/icon)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.utils/) ch.eitchnet.utils - -[![Build Status](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.xmlpers/badge/icon)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.xmlpers/) ch.eitchnet.xmlpers - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.model/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.model/) li.strolch.model - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.agent/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.agent/) li.strolch.agent - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.bom/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.bom/) li.strolch.bom - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.dev/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.dev/) li.strolch.dev - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.parent/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.parent/) li.strolch.parent - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.persistence.postgresql/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.persistence.postgresql/) li.strolch.persistence.postgresql - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.persistence.xml/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.persistence.xml/) li.strolch.persistence.xml - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.rest/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.rest/) li.strolch.rest - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.service/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.service/) li.strolch.service - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.testbase/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.testbase/) li.strolch.testbase - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.tutorialapp/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.tutorialapp/) li.strolch.tutorialapp - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.tutorialwebapp/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.tutorialwebapp/) li.strolch.tutorialwebapp - -[![Build Status](https://jenkins.eitchnet.ch/view/strolch/job/li.strolch.website/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/job/li.strolch.website/) li.strolch.website +[![Build Status](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/strolch/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/) strolch From 8daa3abd2b296452a817014dabe6f304b1b3f060 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 2 Jul 2016 19:35:14 +0200 Subject: [PATCH 456/457] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08fddf090..91c31b39b 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,4 @@ Find more to Strolch at our website: http://strolch.li Build status =================== -[![Build Status](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/strolch/badge/icon)](http://jenkins.eitchnet.ch/view/strolch/) strolch +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=li.strolch)](http://jenkins.eitchnet.ch/job/li.strolch/) From d20e63553b6a44a94d4aa26ea02a655a0995abd4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 2 Jul 2016 19:38:17 +0200 Subject: [PATCH 457/457] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91c31b39b..76ec8f990 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,4 @@ Find more to Strolch at our website: http://strolch.li Build status =================== -[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=li.strolch)](http://jenkins.eitchnet.ch/job/li.strolch/) +[![Build Status](https://jenkins.eitchnet.ch/job/li.strolch/badge/icon)](https://jenkins.eitchnet.ch/job/li.strolch/)