From 9a2c98cbe049154b2438316ebecf559d525bc7a4 Mon Sep 17 00:00:00 2001 From: jensp Date: Wed, 24 Jun 2015 15:43:14 +0000 Subject: [PATCH] CCM NG: Current status git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3498 8810af33-2d31-482b-a856-94f89814c4df --- ccm-core/pom.xml | 24 + .../com/arsdigita/kernel/KernelConfig.java | 203 +++ .../com/arsdigita/runtime/AbstractConfig.java | 143 ++ .../arsdigita/runtime/CCMResourceManager.java | 365 +++++ .../com/arsdigita/runtime/ConfigRegistry.java | 348 +++++ .../com/arsdigita/runtime/RegistryConfig.java | 140 ++ .../main/java/com/arsdigita/util/Assert.java | 716 +++++++++ .../main/java/com/arsdigita/util/Classes.java | 148 ++ .../arsdigita/util/ExceptionUnwrapper.java | 24 + .../java/com/arsdigita/util/Exceptions.java | 113 ++ .../arsdigita/util/JavaPropertyReader.java | 110 ++ .../arsdigita/util/JavaPropertyWriter.java | 119 ++ .../java/com/arsdigita/util/Lockable.java | 85 + .../java/com/arsdigita/util/LockableImpl.java | 63 + .../java/com/arsdigita/util/StringUtils.java | 1362 +++++++++++++++++ .../util/UncheckedWrapperException.java | 207 +++ .../util/parameter/AbstractParameter.java | 339 ++++ .../parameter/AbstractParameterContext.java | 307 ++++ .../util/parameter/BooleanParameter.java | 48 + .../util/parameter/CSVParameterReader.java | 266 ++++ .../util/parameter/ClassParameter.java | 74 + .../parameter/CompoundParameterReader.java | 80 + .../arsdigita/util/parameter/Converters.java | 95 ++ .../util/parameter/EmailParameter.java | 66 + .../util/parameter/EnumerationParameter.java | 85 + .../arsdigita/util/parameter/ErrorList.java | 156 ++ .../util/parameter/FileParameter.java | 67 + .../util/parameter/IntegerParameter.java | 53 + .../util/parameter/MapParameter.java | 115 ++ .../arsdigita/util/parameter/Parameter.java | 183 +++ .../util/parameter/ParameterContext.java | 105 ++ .../util/parameter/ParameterError.java | 116 ++ .../util/parameter/ParameterException.java | 74 + .../util/parameter/ParameterInfo.java | 64 + .../util/parameter/ParameterReader.java | 49 + .../util/parameter/ParameterWriter.java | 44 + .../util/parameter/ResourceParameter.java | 153 ++ .../util/parameter/SingletonParameter.java | 62 + .../parameter/SpecificClassParameter.java | 73 + .../util/parameter/StringArrayParameter.java | 109 ++ .../util/parameter/StringParameter.java | 53 + .../util/parameter/URLParameter.java | 54 + .../main/java/com/arsdigita/xml/Element.java | 609 ++++++++ .../java/com/arsdigita/xml/Formatter.java | 41 + .../src/main/java/com/arsdigita/xml/XML.java | 284 ++++ .../xml/formatters/DateFormatter.java | 58 + .../xml/formatters/DateFormatterConfig.java | 21 + .../DateFormatterConfig_parameter.properties | 4 + .../xml/formatters/DateTimeFormatter.java | 47 + .../xml/formatters/FullDateFormatter.java | 85 + .../xml/formatters/TimeFormatter.java | 47 + .../org/libreccm/core/CcmSessionContext.java | 14 +- .../main/java/org/libreccm/core/Group.java | 2 +- .../java/org/libreccm/core/Permission.java | 6 +- .../core/{Party.java => Subject.java} | 65 +- .../src/main/java/org/libreccm/core/User.java | 41 +- .../java/org/libreccm/core/UserManager.java | 113 ++ .../org/libreccm/core/UserRepository.java | 91 ++ .../core/authentication/LocalLoginModule.java | 138 ++ .../core/authentication/LoginManager.java | 117 ++ .../authentication/PasswordLoginModule.java | 213 +++ .../core/authentication/SubjectPrincipal.java | 72 + .../java/org/libreccm/messaging/Message.java | 8 +- .../org/libreccm/notification/Digest.java | 8 +- .../libreccm/notification/Notification.java | 8 +- .../org/libreccm/notification/QueueItem.java | 8 +- .../org/libreccm/search/lucene/Document.java | 14 +- .../kernel/KernelConfig_parameter.properties | 44 + .../libreccm/core/EqualsAndHashCodeTest.java | 2 +- .../java/org/libreccm/core/ToStringTest.java | 2 +- 70 files changed, 9154 insertions(+), 68 deletions(-) create mode 100644 ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java create mode 100644 ccm-core/src/main/java/com/arsdigita/runtime/AbstractConfig.java create mode 100644 ccm-core/src/main/java/com/arsdigita/runtime/CCMResourceManager.java create mode 100644 ccm-core/src/main/java/com/arsdigita/runtime/ConfigRegistry.java create mode 100644 ccm-core/src/main/java/com/arsdigita/runtime/RegistryConfig.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/Assert.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/Classes.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/ExceptionUnwrapper.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/Exceptions.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/JavaPropertyReader.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/JavaPropertyWriter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/Lockable.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/LockableImpl.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/StringUtils.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/UncheckedWrapperException.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/AbstractParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/AbstractParameterContext.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/BooleanParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/CSVParameterReader.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ClassParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/CompoundParameterReader.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/Converters.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/EmailParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/EnumerationParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ErrorList.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/FileParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/IntegerParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/MapParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/Parameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterContext.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterError.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterException.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterInfo.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterReader.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterWriter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/ResourceParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/SingletonParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/SpecificClassParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/StringArrayParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/StringParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/util/parameter/URLParameter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/xml/Element.java create mode 100644 ccm-core/src/main/java/com/arsdigita/xml/Formatter.java create mode 100644 ccm-core/src/main/java/com/arsdigita/xml/XML.java create mode 100755 ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatter.java create mode 100755 ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatterConfig.java create mode 100755 ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatterConfig_parameter.properties create mode 100755 ccm-core/src/main/java/com/arsdigita/xml/formatters/DateTimeFormatter.java create mode 100755 ccm-core/src/main/java/com/arsdigita/xml/formatters/FullDateFormatter.java create mode 100755 ccm-core/src/main/java/com/arsdigita/xml/formatters/TimeFormatter.java rename ccm-core/src/main/java/org/libreccm/core/{Party.java => Subject.java} (72%) create mode 100644 ccm-core/src/main/java/org/libreccm/core/UserManager.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/UserRepository.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/authentication/LocalLoginModule.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/authentication/LoginManager.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/authentication/PasswordLoginModule.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/authentication/SubjectPrincipal.java create mode 100755 ccm-core/src/main/resources/com/arsdigita/kernel/KernelConfig_parameter.properties diff --git a/ccm-core/pom.xml b/ccm-core/pom.xml index 0bae8bb5f..eb582216e 100644 --- a/ccm-core/pom.xml +++ b/ccm-core/pom.xml @@ -48,6 +48,30 @@ hibernate-validator + + + org.apache.logging.log4j + log4j-core + + + org.apache.logging.log4j + log4j-api + + + org.apache.logging.log4j + log4j-1.2-api + + + + commons-beanutils + commons-beanutils + + + + oro + oro + + junit junit diff --git a/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java b/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java new file mode 100644 index 000000000..342a8dbd0 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2002-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.kernel; + +import com.arsdigita.runtime.AbstractConfig; +import com.arsdigita.util.parameter.BooleanParameter; +import com.arsdigita.util.parameter.EnumerationParameter; +import com.arsdigita.util.parameter.Parameter; +import com.arsdigita.util.parameter.StringParameter; +import java.util.StringTokenizer; + +import org.apache.log4j.Logger; + +/** + * @author Justin Ross + * @see com.arsdigita.kernel.Kernel + * @version $Id$ + */ +public final class KernelConfig extends AbstractConfig { + + /** A logger instance. */ + private static final Logger s_log = Logger.getLogger(KernelConfig.class); + + /** Singelton config object. */ + private static KernelConfig s_conf; + + /** + * Gain a KernelConfig object. + * + * Singelton pattern, don't instantiate a KernelConfig object using the + * constructor directly! + * @return + */ + public static synchronized KernelConfig getConfig() { + if (s_conf == null) { + s_conf = new KernelConfig(); + s_conf.load(); + } + + return s_conf; + } + + /** TODO: should be renamed waf.kernel.debug" */ + private static Parameter m_debug = new BooleanParameter + ("waf.debug", Parameter.REQUIRED, Boolean.FALSE); + /** Whether WEB development support should be activated (true) or not. */ + // Handled in OLD initializer c.ad.webdevsupport.LegacyInitializer + private static Parameter m_webdevSupport = new BooleanParameter + ("waf.webdev_support", Parameter.REQUIRED, Boolean.FALSE); + private final Parameter m_permissions = new BooleanParameter + ("waf.kernel.data_permission_check_enabled", Parameter.REQUIRED, + Boolean.TRUE); + /** User Login by screen name or email address */ + private final EnumerationParameter m_identifier = new EnumerationParameter + ("waf.kernel.primary_user_identifier", Parameter.REQUIRED, + "email"); + /** + * */ + private final Parameter m_SSO = new BooleanParameter + ("waf.kernel.sso_login", Parameter.REQUIRED, Boolean.FALSE); + + /** + * */ + private final Parameter m_remember = new BooleanParameter + ("waf.kernel.remember_login", Parameter.REQUIRED, Boolean.TRUE); + + /** + * */ + private final Parameter m_secureLogin = new BooleanParameter + ("waf.kernel.secure_login", Parameter.REQUIRED, Boolean.FALSE); + + /** String containing the supported languages. + The first one is considered default. */ + private final Parameter m_supportedLanguages = new StringParameter + ("waf.kernel.supported_languages", Parameter.REQUIRED, + "en,de,fr,nl,it,pt,es"); + private final Parameter m_languageIndependentItems = new BooleanParameter + ("waf.kernel.language_independent_items", Parameter.REQUIRED, Boolean.FALSE); + private final Parameter m_languageIndependentCode = new StringParameter + ("waf.kernel.language_independent_code", Parameter.OPTIONAL, + "--"); + + /** + * Constructor + */ + public KernelConfig() { + + // Add recognised Login user identification to enumeration parameter + m_identifier.put("email", "email"); + m_identifier.put("screen_name", "screenName"); + + + register(m_debug); + register(m_webdevSupport); + register(m_permissions); + register(m_identifier); + register(m_SSO); + register(m_remember); + register(m_secureLogin); + register(m_supportedLanguages); + register(m_languageIndependentItems); + register(m_languageIndependentCode); + + loadInfo(); + } + + + public final boolean isDebugEnabled() { + return ((Boolean) get(m_debug)).booleanValue(); + } + + /** + * Return true, if WEB developer support should be activated. + */ + public final boolean isWebdevSupportActive() { + return ((Boolean) get(m_webdevSupport)).booleanValue(); + } + + public final boolean isDataPermissionCheckEnabled() { + return ((Boolean) get(m_permissions)).booleanValue(); + } + + public final String getPrimaryUserIdentifier() { + return (String) get(m_identifier); + } + + public final boolean emailIsPrimaryIdentifier() { + return "email".equals(get(m_identifier)); + } + + public final boolean screenNameIsPrimaryIdentifier() { + return !emailIsPrimaryIdentifier(); + } + + public final boolean isSSOenabled() { + return ((Boolean) get(m_SSO)).booleanValue(); + } + + // XXX Move this to WebConfig. + public final boolean isLoginRemembered() { + return ((Boolean) get(m_remember)).booleanValue(); + } + + public final boolean isSecureLoginRequired() { + return ((Boolean) get(m_secureLogin)).booleanValue(); + } + + /** + * Returns the defaultLanguage flag. + */ + public final String getDefaultLanguage() { + return ((String) get(m_supportedLanguages)).trim().substring(0, 2); + } + + /** + * Returns the supportedLanguages as String. + */ + public final String getSupportedLanguages() { + return (String) get(m_supportedLanguages); + } + + /** + * Returns the supportedLanguages as StringTokenizer. + */ + public final StringTokenizer getSupportedLanguagesTokenizer() { + return new StringTokenizer(this.getSupportedLanguages(), ",", false); + } + + /** + * Returns the languagesIndependentCode as String. + */ + public final String getLanguagesIndependentCode() { + return (String) get(m_languageIndependentCode); + } + + /** + * Return true, if language lang is part of supported langs + */ + public final boolean hasLanguage(String lang) { + return ((String) get(m_supportedLanguages)).contains(lang); + } + + public final boolean languageIndependentItems() { + return ((Boolean) get(m_languageIndependentItems)).booleanValue(); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/runtime/AbstractConfig.java b/ccm-core/src/main/java/com/arsdigita/runtime/AbstractConfig.java new file mode 100644 index 000000000..f83501cb2 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/runtime/AbstractConfig.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.runtime; + +import com.arsdigita.util.parameter.AbstractParameterContext; +import com.arsdigita.util.parameter.ErrorList; +import org.apache.log4j.Logger; + +/** + * AbstractConfig is a base class for groups of customizable + * configuration {@link com.arsdigita.util.parameter parameters}. A + * CCM Developer wishing to add a new group of configuration + * parameters to his application will extend this class and provide a + * public noargs constructer that registers his parameters with the + * superclass. For example: + * + *
+ * package com.arsdigita.exampleApp;
+ *
+ * public final class ExampleConfig extends AbstractConfig {
+ *
+ *     private Parameter m_string = new StringParameter
+ *         ("example.string", Parameter.OPTIONAL, "default");
+ *     private Parameter m_integer = new IntegerParameter
+ *         ("example.integer", Parameter.OPTIONAL, new Integer(0));
+ *     private Parameter m_boolean = new BooleanParameter
+ *         ("example.boolean", Parameter.OPTIONAL, Boolean.TRUE);
+ *
+ *     public ExampleConfig() {
+ *         register(m_string);
+ *         register(m_integer);
+ *         register(m_boolean);
+ *         loadInfo();
+ *     }
+ *
+ *     public String getString() {
+ *         return (String) get(m_string);
+ *     }
+ *
+ *     public int getInteger() {
+ *         return ((Integer) get(m_integer)).intValue();
+ *     }
+ *
+ *     public boolean getBoolean() {
+ *         return Boolean.TRUE.equals(get(m_boolean));
+ *     }
+ *
+ * }
+ * 
+ * + * When this pattern is followed, the resulting subclass of abstract + * config may be used by developers writing java code to access the + * values of customizable configuration parameters in a convenient and + * type safe manner. In addition, the very same class is also usable + * by the ccm configuration tools to allow customization and + * validation of the new parameters. + * + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public abstract class AbstractConfig extends AbstractParameterContext { + + private static final Logger s_log = Logger.getLogger + (AbstractConfig.class); + + /** + * Default constructor for subclasses. + */ + protected AbstractConfig() {} + + /** + * Loads this AbstractConfig object with values from the default + * configuration registry. Any errors encountered during + * unmarshaling and loading of configuration values are added to + * the errors ErrorList. This method should not be + * called from the constructor of a config object since the ccm + * configuration tools need to be able to construct empty config + * objects. + * + * @param errors The ErrorList used to record errors during + * unmarshaling and loading. + * + * @see ConfigRegistry + */ + public final void load(ErrorList errors) { + ConfigRegistry reg = new ConfigRegistry(); + reg.load(this, errors); + } + + /** + * Invokes the {@link #load(ErrorList)} method with a new and + * empty ErrorList for accumulating errors, and returns that + * ErrorList. This method can be used in combination with the + * {@link ErrorList#check()} method to load and assert that this + * configuration object is valid in one simple idiom. For example: + * + *
+     *     ExampleConfig conf = new ExampleConfig();
+     *     conf.load().check();
+     *     ...
+     * 
+ * + * @return Errors that may have been encountered during + * configuration loading. + * + * @see #load(ErrorList) + */ + public final ErrorList load() { + ErrorList errs = new ErrorList(); + load(errs); + return errs; + } + + /** + * @deprecated Use @{link #load()} instead. + */ + public final ErrorList load(final String resource) { + return load(); + } + + /** + * @deprecated Use @{link #load()} instead. + */ + public final ErrorList require(final String resource) { + return load(); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/runtime/CCMResourceManager.java b/ccm-core/src/main/java/com/arsdigita/runtime/CCMResourceManager.java new file mode 100644 index 000000000..068bfd553 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/runtime/CCMResourceManager.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2004 Red Hat Inc. All Rights Reserved. + * Copyright (C) 2009 Peter Boy (pb@zes.uni-bremen.de) All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.runtime; + +import com.arsdigita.util.UncheckedWrapperException; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.apache.log4j.Logger; + +/** + *

CCMResourceManager Runtime environment repository object, stores essential + * properties of the runtime environment and provides them on request.

+ * + *

Currently, is is limited to the

+ * + *

It provides public methods to make the properties available.

+ * + *

The singleton is initialised

+ * + *

Currently as a fall back mechanism the environmant Variable CCM_HOME is + * evaluated and used a last resort, if no initialisation has been done when + * a getter is first called.

+ * + *

It is essential for the proper working of CCM that CCMResourceManager is + * initialised before any operation starts, as it is the case with the Startup + * class of the runtime package, which is responsible for organising database + * access.

+ * + * + *

Subject to change!

+ * + * A similiar task is performed by com.arsdigita.util.ResourceManager + * + * @author Justin Ross <jross@redhat.com> + * rewritten by + * @author pboy <pboy@barkhof.uni-bremen.de> + * @version $Id$ + */ +public final class CCMResourceManager { + + private static final Logger s_log = Logger.getLogger(CCMResourceManager.class); + + private static CCMResourceManager s_ccm; + + /** + * Full Pathname of the application base directory + * (document root in apache terminology) + */ + private static File m_baseDir; + + + /** + * Location of the registry in the applications directory tree + * as offset from the base directory + */ + // currently not used, should be refactored as File object for the sake of + // operating system independency! + // public static final String registryPath = "/WEB-INF/conf/registry"; + // public static final File registryPath = null; // currently not used, work in progress + + /* ************ Section singleton handlers ***************** */ + + /** + * Sets the singleton configuration property for the runtime + * environment. + * + */ + public static final synchronized void setBaseDirectory(String baseDirName) { + if (s_ccm == null) { + s_ccm = new CCMResourceManager(); + s_ccm.storeBaseDir(baseDirName); + } + else { + // baseDir already set, silently discard + s_log.info("baseDir already set as " + m_baseDir + ". Discarded."); + } + } + + /** + * Returns the singleton configuration property for the runtime + * environment. + * + * @return The RuntimeConfig record; it cannot be null + */ + public static final synchronized File getBaseDirectory() { + if (s_ccm == null) { + // should never happen, setBaseDirectory has to be executed first + // we try to resolve the problem in fetchBaseDir by search for + // a runtime environment variable (the old way). + s_ccm = new CCMResourceManager(); + } + + return s_ccm.fetchBaseDir(); + } + + /* ************ Section singleton handlers END ************** */ + + + /* ************ Constructors Section ************** */ + + /** + * Following the singleton pattern: Private constructor to prevent other + * clients from instantiating the class (and the compiler from generating + * a default public constructor). + */ + private CCMResourceManager() { } + + /* ************ Constructors Section END ************** */ + + + /* ************ Public getter/setter Section *************** */ + + /** + * Retrieve the homeDir as URL. + * + * Note! API changed. @see getHomeDirectory() + * May be removed in the future! + * + * @return Directory location in the servers file system as URL object. + */ + public static final URL getHomeURL() { + try { + return CCMResourceManager.getHomeDirectory().toURL(); + } catch (MalformedURLException e) { + throw new UncheckedWrapperException(e); + } + } + + /** + * Retrieve the homeDir, which is the location of the servlet server's + * servlet container directory in the file system of the server machine, + * as File object. + * + * Note! API changed! + * + * Originally it is used to determine all file object locations of a + * CCM installation, during the installation step as well as + * while running the application inside a servlet container. The CCM installation + * of a servlet container used to use a non-standard layout. It is based upon a + * system wide environment variable CCM_HOME to determine the home directory. + * + * The dependency from a system wide environment variable prevents a servlet + * container to run multiple instances of CCM. In addition to it CCM will + * be migrated to be installable in a standard way to a standard container. + * Therefore all file locations will be given relative to the applications + * directory (the baseDirectory @see m_baseDir). + * + * + * Method getHomeDirectory() is preserved during the transition phase. + * It may be removed in the future! Or it may be moved to + * c.ad.packaging for assistence of the installation step only. + * + * MODIFIED: + * CCM_HOME is now interpreted as the path to the applications base + * directory (web application context). + * + * @return Directory location in the servers file system as File object. + */ + static final File getHomeDirectory() { + + String home = System.getProperty("ccm.home"); + + if (home == null) { + throw new IllegalStateException + ("The ccm.home system property is null or not defined"); + } + + // make a guess, wether it is old style (i.e. referring to the containers + // base directory and therefor does not contain the webapps part) or + // new style referring to the apps base directory (and therefor containing + // the webapps part) + if (home.indexOf("webapps") > 0 ){ + // should be new style + } + else { + // presumably old style, add path to standard context name + home += "/webapps/ROOT"; + } + + File file = new File(home); + +// No need to require that home exists (indeed, during install it will not). +// Should be created by invoking method if not. +// if (!file.exists()) { +// throw new IllegalStateException +// ("The file given in the ccm.home system property " + +// "does not exist"); +// } + + if (!file.isDirectory()) { + throw new IllegalStateException + ("The file: " + home + " given in the ccm.home system property" + + " is not a directory"); + } + + return file; + } + + /** + * Provide the configDirectory as URL. + * + * @see getConfigDirectory() for details. + * + * Note! API changed! + * + * @return Directory location in the servers file system as URL object. + */ + public static final URL getConfigURL() { + try { + return CCMResourceManager.getConfigDirectory().toURL(); + } catch (MalformedURLException e) { + throw new UncheckedWrapperException(e); + } + } + + /** + * Retrieve the configDir, which is the location of the configuration + * database root (registry) in the file system tree of the server machine, + * as File object. + + * + * @return Directory location in the servers file system as File object. + */ + public static final File getConfigDirectory() { + + // Keep this in sync with informational attribut @see registryPath ! + File confdir = new File(new File(new File(CCMResourceManager.getBaseDirectory(), + "WEB-INF"),"conf"), "registry"); + + if (!confdir.exists()) { + if (!confdir.mkdirs()) { + throw new IllegalStateException + ("Could not create configuration directory: " + confdir); + } + } + if (!confdir.isDirectory()) { + throw new IllegalStateException + ("Configuration directory value is not a directory: " + confdir); + } + + return confdir; + } + + /** + * getWorkDirectory retrieves and eventually creates an internal directory + * in the servlet container for temporary files (work files), where subsystems + * may create subdirectories for internal use (e.g. Lucene search enginge or + * the PublishToFile machinery). + * + * The containers work file directory could be used as well, but may be + * inappropriate in case of confidential data. + * + * @return Directory location in the servers file system as File object. + */ + public static final File getWorkDirectory() { + File file = new File(new File(CCMResourceManager.getBaseDirectory(), + "WEB-INF"),"work"); + if (!file.exists()) { + if (!file.mkdirs()) { + throw new IllegalStateException + ("Could not create work directory: " + file); + } + } + if (!file.isDirectory()) { + throw new IllegalStateException + ("Work directory value is not a directory: " + file); + } + return file; + } + + /* ************ Public getter/setter Section END *************** */ + + + /* ************ Private Worker Section *************** */ + + /** + * Stores the passed in String as File object. + * + * @param baseDirName String containing the path, must not be null + */ + private final void storeBaseDir(String baseDirName) { + + s_log.debug("storeBaseDir: BaseDir name is given as " + baseDirName ); + m_baseDir = new File(baseDirName); + + // eventually: check if dir exists, create it if not. + if (!m_baseDir.exists()) { + if (!m_baseDir.mkdirs()) { + throw new IllegalStateException + ("Could not create base directory: " + m_baseDir); + } + } + if (!m_baseDir.isDirectory()) { + throw new IllegalStateException + ("Base directory value is not a directory: " + m_baseDir); + } + + } + + /** + * Retrieves the stored BaseDir File object. + * + * @return Base directory location in the servers file system as File object. + */ + private final File fetchBaseDir() { + + if (m_baseDir == null) { + // should never happen, but we try to cope with it anyway by + // falling back to getHomeDirectory() and the system wide + // environment variable. + // During transition phase only! Must be removed when the new + // standard compliant installation method is fully in place + // MODIFIED + // HomeDirectory now specifies the applications context dir. + m_baseDir = CCMResourceManager.getHomeDirectory(); + + // eventually: check if dir exists, create it if not. + if (!m_baseDir.exists()) { + if (!m_baseDir.mkdirs()) { + throw new IllegalStateException + ("Could not create base directory: " + m_baseDir); + } + } + if (!m_baseDir.isDirectory()) { + throw new IllegalStateException + ("Base directory value is not a directory: " + m_baseDir); + } + } + + return m_baseDir; + + } + + /* ************ Private Worker Section END *************** */ + + +} diff --git a/ccm-core/src/main/java/com/arsdigita/runtime/ConfigRegistry.java b/ccm-core/src/main/java/com/arsdigita/runtime/ConfigRegistry.java new file mode 100644 index 000000000..ea1316340 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/runtime/ConfigRegistry.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.runtime; + +import com.arsdigita.util.Classes; +import com.arsdigita.util.JavaPropertyReader; +import com.arsdigita.util.UncheckedWrapperException; +import com.arsdigita.util.parameter.ErrorList; +import com.arsdigita.util.parameter.ParameterContext; +import com.arsdigita.util.parameter.ParameterReader; +import com.arsdigita.xml.XML; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.xml.sax.Attributes; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.log4j.Logger; + +/** + * The ConfigRegistry class maps between config classes (subclasses of + * {@link com.arsdigita.runtime.AbstractConfig}), and a location used + * for persisting the values in a config class. + * + * The ConfigRegistry also stores the set of configured packages for a + * particular CCMResourceManager instance. + * Additionally it stores a list of URLs for parent configurations that are + * used for defaulting values not present in the local configuration. + * This mapping is maintained and extended by CCMResourceManager developers through + * the use of an XML configuration file placed in the src tree for a + * particular package. If a particular package is configured, the + * ConfigRegistry class will look in the classpath for a registry + * configuration file named package-key.config, and parse the + * file according to the following specification: + * + *
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <registry>
+ *   ...
+ *   <config class="CLASSNAME"
+ *           storage="FILENAME"/>
+ *   ...
+ * </registry>
+ * 
+ * + * The mappings stored by this ConfigRegistry will then be extended to include + * the classes and storage locations specified in the configuration file. These + * mappings are then used by the ConfigRegistry instance to load config objects. + * + * @author Rafael H. Schloming <rhs@mit.edu> + * @version $Revision$ $Date$ + * @version $Id$ + **/ +public class ConfigRegistry { + + private static final Logger s_log = Logger.getLogger(ConfigRegistry.class); + /** + * Base url for registry location(s). + * (i.e. $CATALINA_HOME/webapps/$context/WEB-INF/conf/registry in a + * standard installation) + */ + private URL m_url; + private ClassLoader m_loader; + private List m_packages = new ArrayList(); + private List m_contexts = new ArrayList(); + private Map m_storage = new HashMap(); + private List m_loaders = new ArrayList(); + + /** + * Constructs a new config registry that will resolve all + * locations relative to url, and use + * loader when searching the classpath for registry + * configuration files. + * + * @param url The base url for registry locations. + * @param loader The ClassLoader to use for retrieving registry + * configuration files. + **/ + public ConfigRegistry(URL url, ClassLoader loader) { + m_url = url; + m_loader = loader; + addContext(RegistryConfig.class, "registry.properties"); + initialize(m_url, new ErrorList()); + } + + /** + * Convenience class which invokes {@link #ConfigRegistry(URL, ClassLoader)} + * defaulting the loader to the current context class loader. + * + * @see Thread#getContextClassLoader() + * + * @param url The base url for registry locations. + */ + public ConfigRegistry(URL url) { + this(url, Thread.currentThread().getContextClassLoader()); + } + + /** + * Convenience class which invokes {@link #ConfigRegistry(URL, ClassLoader)} + * defaulting the URL to + * new File(System.getProperty("ccm.conf")).toURL(). The value + * of the ccm.conf system property may or may not include a trailing slash. + * + * @param loader The ClassLoader to use when searching for + * registry configuration files. + */ + public ConfigRegistry(ClassLoader loader) { + this(CCMResourceManager.getConfigURL(), loader); + } + + /** + * Invokes {@link #ConfigRegistry(URL)} defaulting the URL to new + * File(System.getProperty("ccm.conf")).toURL(). The value of the + * ccm.conf system property may or may not include a trailing slash. + */ + public ConfigRegistry() { + this(CCMResourceManager.getConfigURL()); + } + + /** + * + * @param url Base url for registry location(s). + * @param errs Errorlist + */ + private void initialize(URL url, ErrorList errs) { + + ClassLoader ldr = new URLClassLoader(new URL[]{url}, null); + + RegistryConfig rc = new RegistryConfig(); + load(rc, errs, ldr); + + String[] packages = rc.getPackages(); + URL[] parents = rc.getParents(); + + for (int i = 0; i < packages.length; i++) { + if (!m_packages.contains(packages[i])) { + initialize(packages[i]); + } + } + + for (int i = parents.length - 1; i >= 0; i--) { + initialize(parents[i], errs); + } + + m_loaders.add(ldr); + } + + /** + * This method is not supported API. + */ + public final void initialize(String key) { + s_log.debug(String.format("Initalizing for key '%s'", key)); + if (m_packages.contains(key)) { + throw new IllegalArgumentException("already loaded: " + key); + } + + InputStream is = m_loader.getResourceAsStream(key + ".config"); + if (is != null) { + try { + XML.parse(is, new ConfigRegistryParser()); + m_packages.add(key); + } finally { + try { + is.close(); + } catch (IOException e) { + throw new UncheckedWrapperException(e); + } + } + } + } + + /** + * Returns the list of configured packages for this ConfigRegistry. + * + * @return A list of package keys represented as Strings. + **/ + public List getPackages() { + return m_packages; + } + + /** + * Returns a list of config classes for this ConfigRegistry. + * + * @return A list of Class objects. + **/ + public List getContexts() { + return m_contexts; + } + + /** + * Returns the relative location used to store values for the + * given config class. + * + * @param context a subclass of {@link + * com.arsdigita.runtime.AbstractConfig} + * + * @return the relative storage location for context + * + * @throws IllegalArgumentException if this ConfigRegistry does + * not contain a mapping for context + **/ + public String getStorage(Class context) { + if (!m_contexts.contains(context)) { + throw new IllegalArgumentException("no such context: " + context + + "; available contexts=" + + m_contexts + + "; context->storage map: " + + m_storage); + } + return (String) m_storage.get(context); + } + + private void addContext(Class context, String storage) { + s_log.debug(String.format("Adding context '%s', storage '%s'...", + context.getName(), storage)); + m_contexts.add(context); + m_storage.put(context, storage); + } + + /** + * Returns true if this ConfigRegistry contains a mapping for + * context + * + * @param context a subclass of {@link + * com.arsdigita.runtime.AbstractConfig} + * + * @return true if this ConfigRegistry contains a mapping for + * context + **/ + public boolean isConfigured(Class context) { + return m_contexts.contains(context); + } + + /** + * Loads the given config object from the correct location based + * on its class. Defaults all values based on the value of the + * waf.config.parents parameter. Any errors + * encountered during loading are reported in the given ErrorList. + * + * @param ctx the config object to load + * @param errs used to accumulate errors during loading + * + * @throws IllegalArgumentException if this ConfigRegistry does + * not contain a mapping for ctx.getClass() + **/ + public void load(ParameterContext ctx, ErrorList errs) { + for (Iterator it = m_loaders.iterator(); it.hasNext();) { + ClassLoader ldr = (ClassLoader) it.next(); + load(ctx, errs, ldr); + } + } + + /** + * Searches through this ConfigRegistry and its parents for the + * given resource. If it is not found it is also searched for in + * the classpath specified by the loader passed to this + * ConfigRegistry on construction. This may be used to load + * configuration information that is not stored in a config + * object. + * + * @param resource the path to the resource + * + * @return an input stream containing the contents of the resource + * or null if the resource is not found + */ + public InputStream load(String resource) { + for (int i = m_loaders.size() - 1; i >= 0; i--) { + ClassLoader ldr = (ClassLoader) m_loaders.get(i); + InputStream is = ldr.getResourceAsStream(resource); + if (is != null) { + return is; + } + } + + return m_loader.getResourceAsStream(resource); + } + + private void load(ParameterContext ctx, ErrorList errs, ClassLoader ldr) { + Properties props = getProperties(ldr, getStorage(ctx.getClass())); + ParameterReader reader = new JavaPropertyReader(props); + ctx.load(reader, errs); + } + + private Properties getProperties(ClassLoader ldr, String resource) { + Properties props = new Properties(); + InputStream is = ldr.getResourceAsStream(resource); + if (is != null) { + try { + props.load(is); + } catch (IOException e) { + throw new UncheckedWrapperException(e); + } finally { + try { + is.close(); + } catch (IOException e) { + throw new UncheckedWrapperException(e); + } + } + } + return props; + } + + private class ConfigRegistryParser extends DefaultHandler { + + @Override + public void startElement(String uri, String localName, String qn, + Attributes attrs) { + if (localName.equals("config")) { + String klass = attrs.getValue(uri, "class"); + String storage = attrs.getValue(uri, "storage"); + // XXX: Is there a better way to handle errors that + // includes line number information? + if ((klass == null) || (storage == null)) { + throw new IllegalArgumentException( + "class and storage attributes are required"); + } + + Class context = Classes.loadClass(klass); + addContext(context, storage); + } + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/runtime/RegistryConfig.java b/ccm-core/src/main/java/com/arsdigita/runtime/RegistryConfig.java new file mode 100644 index 000000000..2b6c14236 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/runtime/RegistryConfig.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.runtime; + +// import com.arsdigita.runtime.AbstractConfig; +import com.arsdigita.util.StringUtils; +import com.arsdigita.util.parameter.ErrorList; +import com.arsdigita.util.parameter.Parameter; +import com.arsdigita.util.parameter.ParameterError; +import com.arsdigita.util.parameter.StringParameter; +import java.net.MalformedURLException; +import java.net.URL; + + +/** + * A config class used by the registry itself. + * + * Contains the parameters: + * waf.config.packages: comma separated package-key list of installed packages + * waf.config.parents : + * + * @author Rafael H. Schloming <rhs@mit.edu> + * @version $Id$ + */ +public class RegistryConfig extends AbstractConfig { + + /** + * Helper method to unmarshal parameter values. + * @param str A String of comma separated values + * @return StringArray of the values + */ + private static String[] array(String str) { + if (str == null) { + return null; + } else { + return StringUtils.split(str, ','); + } + } + + /** + * List of installed packages. + * + * Provided as a comma separated package-key list of installed packages. + * The parameter overwrites the default marshal and unmarshal methods to + * allow the String parameter to hold a list of values. + * + * TODO: Replace the type String parameter by StringArray parameter which + * provides exactly the required functionality (doesn't it?). + */ + private Parameter m_packages = new StringParameter + ("waf.config.packages", Parameter.OPTIONAL, new String[0]) { + @Override + protected Object unmarshal(String value, ErrorList errs) { + return array(value); + } + + @Override + protected String marshal(Object obj) { + return StringUtils.join((String[]) obj, ','); + } + }; + + /** + * List of parameter values, purpose currently unkown. + * + * The parameter overwrites the default marshal and unmarshal methods to + * allow the String parameter to hold a list of values. + */ + private Parameter m_parents = new StringParameter + ("waf.config.parents", Parameter.OPTIONAL, new URL[0]) { + @Override + protected Object unmarshal(String value, ErrorList errs) { + String[] strs = array(value); + URL[] result = new URL[strs.length]; + for (int i = 0; i < result.length; i++) { + try { + result[i] = new URL(strs[i]); + } catch (MalformedURLException e) { + errs.add(new ParameterError(this, e)); + } + } + if (!errs.isEmpty()) { + return null; + } + return result; + } + + protected String marshal(Object obj) { + URL[] urls = (URL[]) obj; + String[] strs = new String[urls.length]; + for (int i = 0; i < strs.length; i++) { + strs[i] = urls[i].toExternalForm(); + } + return StringUtils.join(strs, ','); + } + }; + + /** + * Constructs a new and empty config object. + */ + public RegistryConfig() { + register(m_packages); + register(m_parents); + } + + /** + * Returns the value of the waf.config.packages parameter. + * + * @return the value of the waf.config.packages parameter + */ + public String[] getPackages() { + return (String[]) get(m_packages); + } + + /** + * Returns the value of the waf.config.parents parameter. + * + * @return the value of the waf.config.parents parameter + */ + public URL[] getParents() { + return (URL[]) get(m_parents); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/Assert.java b/ccm-core/src/main/java/com/arsdigita/util/Assert.java new file mode 100644 index 000000000..648ab6b32 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/Assert.java @@ -0,0 +1,716 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + +import org.apache.log4j.Logger; + +/** + * Utility functions for assertions. + * + *

The static methods in this class provide a standard way of + * asserting certain conditions.

+ * + *

Though it is not right now, this class should be final. + * Do not subclass it. In a future revision of this software, this + * class will be made final.

+ * + * @author David Lutterkort <dlutter@redhat.com> + * @author Uday Mathur + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class Assert { + + /** Class specific logger instance. */ + private static final Logger s_log = Logger.getLogger(Assert.class); + + private static final String DEFAULT_MESSAGE = "Assertion failure"; + + /** + * Configuration by a system wide / servlet container wide environment + * variable is evil and no longer supported API. + * Currently Assertion is set alway on. The non-deprecated methods of this + * class don't check for isEnabled anyway. So there is no effect. + * If configurability is really needed, configuration by config registry + * during system setup (eg. @link com.arsdigita.runtime.CCMResourceManager) + * has to be implemented + */ + private static boolean s_enabled = true; +// private static boolean s_enabled; +// +// static { +// final String enabled = System.getProperty +// (Assert.class.getName() + ".enabled"); +// +// if (enabled == null) { +// //s_enabled = false; +// //default value is true +// s_enabled = true; +// } else { +// // s_enabled = enabled.equals("true"); +// // Test: set to true anyway. +// // TODO: read from config registry, any dependency from a +// // environment variable is deprecated! +// s_enabled = true; +// } +// } + + /** + * Tells whether asserts are turned on. Use this to wrap code + * that should be optimized away if assertions are disabled. + * + * By default, assertions are disabled + */ + public static final boolean isEnabled() { + return s_enabled; + } + + static final void setEnabled(final boolean enabled) { + s_enabled = enabled; + } + + /** + * Throws an error. + * + * @param message A String describing the failure + * condition + * @throws AssertionError + */ + public static final void fail(final String message) { + error(message); + + throw new AssertionError(message); + } + + /** + * Throws an error. + * + * @throws AssertionError + */ + public static final void fail() { + error(DEFAULT_MESSAGE); + + throw new AssertionError(DEFAULT_MESSAGE); + } + + /** + * Asserts that an arbitrary condition is true and throws an + * error with message message if the condition is + * false. + * + * @param condition The condition asserted + * @param message An error message + * @throws AssertionError if the condition is false + */ + public static final void isTrue(final boolean condition, + final String message) { + if (!condition) { + error(message); + + throw new AssertionError(message); + } + } + + /** + * Asserts that an arbitrary condition is true and throws an + * error with message message if the condition is + * false. + * + * @param condition The condition asserted + * @param message An error message + * @throws AssertionError if the condition is false + * @deprecated use Assert.isTrue(condition, message) instead + * (we will follow the standard naming scheme) + */ + public static final void truth(final boolean condition, + final String message) { + Assert.isTrue(condition, message); + + + // if (!condition) { + // error(message); + // + // throw new AssertionError(message); + // } + } + + /** + * Asserts that an arbitrary condition is true and throws an + * error with message message if the condition is + * false. + * + * @param condition The condition asserted + * @throws AssertionError if the condition is false + */ + public static final void isTrue(final boolean condition) { + if (!condition) { + error(DEFAULT_MESSAGE); + + throw new AssertionError(DEFAULT_MESSAGE); + } + } + + /** + * Asserts that an arbitrary condition is true and throws an + * error with message message if the condition is + * false. + * + * @param condition The condition asserted + * @throws AssertionError if the condition is false + * @deprecated use Assert.isTrue(final boolean condition) instead + * (we will follow the standard naming scheme) + */ + public static final void truth(final boolean condition) { + Assert.isTrue(condition); + + // if (!condition) { + // error(DEFAULT_MESSAGE); + // + // throw new AssertionError(DEFAULT_MESSAGE); + // } + } + + /** + * Asserts that an arbitrary condition is false and throws an + * error if the condition is true. + * + * @param condition The condition asserted + * @param message An error message + * @throws AssertionError if the condition is false + */ + public static final void isFalse(final boolean condition, + final String message) { + if (condition) { + error(message); + + throw new AssertionError(message); + } + } + + /** + * Asserts that an arbitrary condition is false and throws an + * error if the condition is true. + * + * @param condition The condition asserted + * @throws AssertionError if the condition is false + */ + public static final void isFalse(final boolean condition) { + if (condition) { + error(DEFAULT_MESSAGE); + + throw new AssertionError(DEFAULT_MESSAGE); + } + } + + /** + * Asserts that an object is not null. + * + * @param object The object that must not be null + * @param clacc The Class of parameter + * object + * @throws AssertionError if the object is null + */ + public static void exists(final Object object, + final Class clacc) { + if (object == null) { + final String message = clacc.getName() + " is null"; + + error(message); + + throw new AssertionError(message); + } + } + + /** + * Asserts that an object is not null. + * + * @param object The Object that must not be null + * @param label A text to describe Object + * + * @throws AssertionError if the object is null + */ + public static void exists(final Object object, + final String label) { + if (object == null) { + final String message = + label != null && label.trim().length() > 0 + ? "Value of " + label + " is null." + : DEFAULT_MESSAGE ; + + error(message); + + throw new AssertionError(message); + } + } + + /** + * Asserts that an object is not null. + * + * @param object The object that must not be null + * @throws AssertionError if the object is null + */ + public static final void exists(final Object object) { + if (object == null) { + error(DEFAULT_MESSAGE); + + throw new AssertionError(DEFAULT_MESSAGE); + } + } + + /** + * Verifies that Lockable is locked and throws an + * error if it is not. + * + * @param lockable The object that must be locked + * @see com.arsdigita.util.Lockable + */ + public static final void isLocked(final Lockable lockable) { + if (lockable != null && !lockable.isLocked()) { + final String message = "Illegal access: " + lockable + + " is not locked"; + + error(message); + + throw new AssertionError(message); + } + } + + /** + * Verifies that lockable is not locked and + * throws an error if it is. + * + * @param lockable The object that must not be locked + * @see com.arsdigita.util.Lockable + */ + public static final void isUnlocked(final Lockable lockable) { + if (lockable != null && lockable.isLocked()) { + final String message = "Illegal access: " + lockable + " is locked."; + + error(message); + + throw new AssertionError(message); + } + } + + /** + * Verifies that two values are equal (according to their equals + * method, unless value1 is null, then according to + * ==). + * + * @param value1 The first value to be compared + * @param value2 The second + * @throws AssertionError if the arguments are unequal + */ + public static final void isEqual(final Object value1, + final Object value2) { + if (value1 == null) { + if (value1 != value2) { + final String message = value1 + " does not equal " + value2; + + error(message); + + throw new AssertionError(message); + } + } else { + if (!value1.equals(value2)) { + final String message = value1 + " does not equal " + value2; + + error(message); + + throw new AssertionError(message); + } + } + } + + /** + * Verifies that two values are equal (according to their equals + * method, unless value1 is null, then according to + * ==). + * + * @param value1 The first value to be compared + * @param value2 The second + * @throws AssertionError if the arguments are unequal + */ + public static final void isEqual(final Object value1, + final Object value2, + final String message) { + if (value1 == null) { + if (value1 != value2) { + error(message); + + throw new AssertionError(message); + } + } else { + if (!value1.equals(value2)) { + error(message); + + throw new AssertionError(message); + } + } + } + + /** + * Verifies that two values are not equal (according to their + * equals method, unless value1 is null, then + * according to ==). + * + * @param value1 The first value to be compared + * @param value2 The second + * @throws AssertionError if the arguments are isNotEqual + */ + public static final void isNotEqual(final Object value1, + final Object value2) { + if (value1 == null) { + if (value1 == value2) { + final String message = value1 + " equals " + value2; + + error(message); + + throw new AssertionError(message); + } + } else { + if (value1.equals(value2)) { + final String message = value1 + " equals " + value2; + + error(message); + + throw new AssertionError(message); + } + } + } + + // Utility methods + + private static void error(final String message) { + // Log the stack trace too, since we still have code that + // manages to hide exceptions. + s_log.error(message, new AssertionError(message)); + } + + /////////////////////////////////////////////////////////////////////////// + // // + // The methods below are all deprecated. // + // // + /////////////////////////////////////////////////////////////////////////// + + /** + * @deprecated in favor of {@link #isEnabled()} + * + * pboy Jan.09: not used by any package in trunk + */ + // public static final boolean ASSERT_ON = true; + + /** + * Indicates state of the ASSERT_ON flag. + * + * pboy Jan.09: not used by any package in trunk + * + * @deprecated Use {@link #isEnabled()} instead + */ + // public static final boolean isAssertOn() { + // return isEnabled(); + // } + +// /** +// * Tells whether asserts are turned on. Use this to wrap code +// * that should be optimized away if asserts are disabled. +// * +// * @deprecated Use {@link #isEnabled()} instead +// */ +// public static final boolean isAssertEnabled() { +// return isEnabled(); +// } + + /** + * Assert that an arbitrary condition is true, and throw an + * exception if the condition is false. + * + * @param cond condition to assert + * @throws java.lang.IllegalStateException condition was false + * @deprecated Use {@link #truth(boolean, String)} instead + */ +/* public static final void isTrue(boolean cond) { + isTrue(cond, ""); + } +*/ + /** + * Assert that an arbitrary condition is true, and throw an + * exception with message msg if the condition is + * false. + * + * @param cond condition to assert + * @param msg failure message + * @throws java.lang.IllegalStateException condition was false + * @deprecated Use {@link #truth(boolean,String)} instead + */ +/* public static final void assertTrue(boolean cond, String msg) { + if (!cond) { + error(msg); + + throw new IllegalStateException(msg); + } + } +*/ + /** + * Asserts that an arbitrary condition is true and throws an + * error with message message if the condition is + * false. + * + * @param condition The condition asserted + * @param message An error message + * @throws AssertionError if the condition is false + * @deprecated use Assert.isTrue(condition, message) instead + * (we will follow the standard naming scheme) + */ +/* public static final void truth(final boolean condition, + final String message) { + Assert.isTrue(condition, message); + + // if (!condition) { + // error(message); + // + // throw new AssertionError(message); + // } + } +*/ + + /** + * Asserts that an arbitrary condition is true and throws an + * error with message message if the condition is + * false. + * + * @param condition The condition asserted + * @throws AssertionError if the condition is false + * @deprecated use Assert.isTrue(final boolean condition) instead + * (we will follow the standard naming scheme) + */ +/* public static final void truth(final boolean condition) { + Assert.isTrue(condition); + + // if (!condition) { + // error(DEFAULT_MESSAGE); + // + // throw new AssertionError(DEFAULT_MESSAGE); + // } + } +*/ + + /** + * Asserts that an arbitrary condition is false and throws an + * error if the condition is true. + * + * @param condition The condition asserted + * @param message An error message + * @throws AssertionError if the condition is false + */ +/* public static final void falsity(final boolean condition, + final String message) { + if (condition) { + error(message); + + throw new AssertionError(message); + } + } +*/ + + /** + * Asserts that an arbitrary condition is false and throws an + * error if the condition is true. + * + * @param condition The condition asserted + * @throws AssertionError if the condition is false + */ +/* public static final void falsity(final boolean condition) { + if (condition) { + error(DEFAULT_MESSAGE); + + throw new AssertionError(DEFAULT_MESSAGE); + } + } +*/ + /** + * Verify that a parameter is not null and throw a runtime + * exception if so. + * + * @deprecated Use {@link #exists(Object)} instead + */ +/* public static final void assertNotNull(Object o) { + assertNotNull(o, ""); + } +*/ + /** + * Verify that a parameter is not null and throw a runtime + * exception if so. + * + * @deprecated Use {@link #exists(Object,String)} instead + */ +/* public static final void assertNotNull(Object o, String label) { + if (isEnabled()) { + isTrue(o != null, "Value of " + label + " is null."); + } + } +*/ +// /** +// * Verify that a string is not empty and throw a runtime exception +// * if so. A parameter is considered empty if it is null, or if it +// * does not contain any characters that are non-whitespace. +// * +// * @deprecated with no replacement +// */ +// public static final void assertNotEmpty(String s) { +// assertNotEmpty(s, ""); +// } + + /** + * Verify that a string is not empty and throw a runtime exception + * if so. A parameter is considered empty if it is null, or if it + * does not contain any characters that are non-whitespace. + * + * @deprecated with no replacement + */ + public static final void assertNotEmpty(String s, String label) { + if (isEnabled()) { + isTrue(s != null && s.trim().length() > 0, + "Value of " + label + " is empty."); + } + } + + /** + * Verify that two values are equal (according to their equals method, + * unless expected is null, then according to ==). + * + * @param expected Expected value. + * @param actual Actual value. + * @throws java.lang.IllegalStateException condition was false + * @deprecated Use {@link #isEqual(Object,Object)} instead + */ + public static final void assertEquals(Object expected, Object actual) { + assertEquals(expected, actual, "expected", "actual"); + } + + /** + * Verify that two values are equal (according to their equals method, + * unless expected is null, then according to ==). + * + * @param expected Expected value. + * @param actual Actual value. + * @param expectedLabel Label for first (generally expected) value. + * @param actualLabel Label for second (generally actual) value. + * @throws java.lang.IllegalStateException condition was false + */ + public static final void assertEquals(Object expected, Object actual, + String expectedLabel, + String actualLabel) { + if (isEnabled()) { + if (expected == null) { + isTrue(expected == actual, + "Values not equal, " + + expectedLabel + " '" + expected + "', " + + actualLabel + " '" + actual + "'"); + } else { + isTrue(expected.equals(actual), + "Values not equal, " + + expectedLabel + " '" + expected + "', " + + actualLabel + " '" + actual + "'"); + } + } + } + + /** + * Verify that two values are equal. + * + * @param expected Expected value. + * @param actual Actual value. + * @throws java.lang.IllegalStateException condition was false + * @deprecated Use {@link #truth(boolean, String)} instead + */ + public static final void assertEquals(int expected, int actual) { + assertEquals(expected, actual, "expected", "actual"); + } + + /** + * Verify that two values are equal. + * + * @param expected Expected value. + * @param actual Actual value. + * @param expectedLabel Label for first (generally expected) value. + * @param actualLabel Label for second (generally actual) value. + * @throws java.lang.IllegalStateException condition was false + * @deprecated Use {@link #truth(boolean, String)} instead + */ + public static final void assertEquals(int expected, int actual, + String expectedLabel, + String actualLabel) { + if (isEnabled()) { + isTrue(expected == actual, + "Values not equal, " + + expectedLabel + " '" + expected + "', " + + actualLabel + " '" + actual + "'"); + } + } + + /** + * Verify that the model is locked and throw a runtime exception + * if it is not locked. + * + * @deprecated Use {@link #isLocked(Lockable)} instead + */ +/* public static void assertLocked(Lockable l) { + if (isEnabled()) { + isTrue(l.isLocked(), + "Illegal access to an unlocked " + + l.getClass().getName()); + } + } +*/ + /** + * Verify that the model is locked and throw a runtime exception + * if it is locked. + * +// * @deprecated Use {@link #isUnlocked(Lockable)} instead + */ +/* public static void assertUnlocked(Lockable l) { + if (isEnabled()) { + isTrue(!l.isLocked(), + "Illegal access to a isLocked " + + l.getClass().getName()); + } + } +*/ + /** + * Verifies that lockable is not isLocked and + * throws an error if it is. + * + * @param lockable The object that must not be isLocked + * @see com.arsdigita.util.Lockable +// * @deprecated use isUnlocked(Lockable) instead + */ +/* public static final void unlocked(final Lockable lockable) { + if (lockable != null && lockable.isLocked()) { + final String message = "Illegal access: " + lockable + " is isLocked."; + + error(message); + + throw new AssertionError(message); + } + } +*/ + + +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/Classes.java b/ccm-core/src/main/java/com/arsdigita/util/Classes.java new file mode 100644 index 000000000..bd1230241 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/Classes.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; +import org.apache.log4j.Logger; + +import java.util.Arrays; + +/** + * A collection of static utility methods for dealing with Java + * classes. + * + * @author Justin Ross + * @version $Id$ + */ +public final class Classes { + + private static final Logger s_log = Logger.getLogger(Classes.class); + + /** + * Loads a class from its fully qualified string name. + * + * @param clacc A fully qualified String naming + * the class to be loaded + */ + public static final Class loadClass(final String clacc) { + Assert.exists(clacc, String.class); + + try { + return Class.forName(clacc); + } catch (ClassNotFoundException ex) { + throw new UncheckedWrapperException(ex); + } + } + + /** + * Constructs a new instance of a class using the given + * parameters. + * + * @param clacc The Class of which to make a new + * instance + * @param params A Class[] representing the arguments + * of the desired constructor + * @param values An Object[] of values to fill the + * parameters + */ + public static final Object newInstance(final Class clacc, + final Class[] params, + final Object[] values) { + if (Assert.isEnabled()) { + Assert.exists(clacc, Class.class); + Assert.exists(params, Class.class); + Assert.exists(values, Object.class); + Assert.isTrue(params.length == values.length); + } + + try { + final Constructor constructor = clacc.getConstructor(params); + + return constructor.newInstance(values); + } catch (NoSuchMethodException ex) { + throw new UncheckedWrapperException + (message(clacc, params, values), ex); + } catch (IllegalAccessException ex) { + throw new UncheckedWrapperException + (message(clacc, params, values), ex); + } catch (InvocationTargetException ex) { + throw new UncheckedWrapperException + (message(clacc, params, values), ex); + } catch (InstantiationException ex) { + throw new UncheckedWrapperException + (message(clacc, params, values), ex); + } + } + + private static String message(Class klass, Class[] params, + Object[] values) { + return "class = " + klass + + ", params = " + message(params) + + ", values = " + message(values); + } + + private static String message(Object[] array) { + if (array == null) { + return "" + null; + } else { + return Arrays.asList(array).toString(); + } + } + + /** + * Constructs a new instance of the class referred to by + * clacc. + * + * @param clacc The fully qualified String + * clacc of the object you wish to instantiate + * @param params A Class[] representing the arguments + * of the desired constructor + * @param values An Object[] of values to fill the + * parameters + */ + public static final Object newInstance(final String clacc, + final Class[] params, + final Object[] values) { + return newInstance(loadClass(clacc), params, values); + } + + /** + * Creates a new instance of clacc using its no-args + * constructor. If the class has no such constructor, it throws a + * runtime exception. + * + * @param clacc The class of which to create a new instance + */ + public static final Object newInstance(final Class clacc) { + return newInstance(clacc, new Class[0], new Object[0]); + } + + /** + * Creates a new instance of the class represented by + * clacc using its no-args constructor. If the class + * has no such constructor, it throws a runtime exception. + * + * @param clacc The fully-qualified String name of + * the class + */ + public static final Object newInstance(final String clacc) { + return newInstance(loadClass(clacc)); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/ExceptionUnwrapper.java b/ccm-core/src/main/java/com/arsdigita/util/ExceptionUnwrapper.java new file mode 100644 index 000000000..8171bed18 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/ExceptionUnwrapper.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + + +public interface ExceptionUnwrapper { + Throwable unwrap(Throwable t); +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/Exceptions.java b/ccm-core/src/main/java/com/arsdigita/util/Exceptions.java new file mode 100644 index 000000000..c0b3e23f1 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/Exceptions.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; + +import org.apache.log4j.Logger; + +public class Exceptions { + + private static Logger s_log = Logger.getLogger(Exceptions.class); + + private static Map s_unwrappers = new HashMap(); + + public static Throwable[] unwrap(Throwable t) { + Assert.exists(t, Throwable.class); + + List exceptions = new ArrayList(); + + exceptions.add(t); + + if (s_log.isDebugEnabled()) { + s_log.debug("Trying to unwrap " + t.getClass()); + } + + Throwable current = t; + + for (;;) { + Throwable inner = null; + ExceptionUnwrapper unwrapper = findUnwrapper(current.getClass()); + + if (unwrapper != null) { + inner = unwrapper.unwrap(current); + } + + if (inner == null) { + Assert.exists(current, Throwable.class); + if (s_log.isDebugEnabled()) { + s_log.debug("Returning exception " + current.getClass()); + } + return (Throwable[])exceptions.toArray( + new Throwable[exceptions.size()]); + } + + if (s_log.isDebugEnabled()) { + s_log.debug("Inner exception is " + inner.getClass()); + } + + exceptions.add(inner); + + current = inner; + } + + // Unreachable + //throw new RuntimeException("this cannot happen"); + } + + + public static void registerUnwrapper(Class exception, + ExceptionUnwrapper unwrapper) { + s_unwrappers.put(exception, unwrapper); + } + + public static void unregisterUnwrapper(Class exception) { + s_unwrappers.remove(exception); + } + + public static ExceptionUnwrapper getUnwrapper(Class exception) { + return (ExceptionUnwrapper)s_unwrappers.get(exception); + } + + public static ExceptionUnwrapper findUnwrapper(Class exception) { + if (s_log.isDebugEnabled()) { + s_log.debug("Finding unwrapper for " + exception.getName()); + } + + Class current = exception; + ExceptionUnwrapper unwrapper = null; + while (unwrapper == null && + current != null) { + if (s_log.isDebugEnabled()) { + s_log.debug("Trying class " + current.getName()); + } + unwrapper = (ExceptionUnwrapper)s_unwrappers.get(current); + current = current.getSuperclass(); + } + + if (s_log.isDebugEnabled()) { + s_log.debug("Got unwrapper " + + (unwrapper != null ? unwrapper.getClass() : null)); + } + return unwrapper; + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/JavaPropertyReader.java b/ccm-core/src/main/java/com/arsdigita/util/JavaPropertyReader.java new file mode 100644 index 000000000..a29379612 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/JavaPropertyReader.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + +import com.arsdigita.util.parameter.ErrorList; +import com.arsdigita.util.parameter.Parameter; +import com.arsdigita.util.parameter.ParameterReader; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import org.apache.log4j.Logger; + +/** + * An implementation of ParameterReader that uses + * standard Java properties to retrieve values. + * + * Subject to change. + * + * @see com.arsdigita.util.parameter.ParameterReader + * @see JavaPropertyWriter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class JavaPropertyReader implements ParameterReader { + + private static final Logger s_log = Logger.getLogger + (JavaPropertyReader.class); + + private final Properties m_props; + + /** + * Constructs a parameter reader that uses props. + * + * @param props The Properties object that stores + * property values; it cannot be null + */ + public JavaPropertyReader(final Properties props) { + Assert.exists(props, Properties.class); + + m_props = props; + } + + /** + * Loads the internal Properties object using + * in. + * + * @param in The InputStream that has the source + * properties; it cannot be null + */ + public final void load(final InputStream in) { + Assert.exists(in, InputStream.class); + + try { + m_props.load(in); + } catch (IOException ioe) { + throw new UncheckedWrapperException(ioe); + } + } + + /** + * Reads a String value back for a + * param. + * + * @param param The Parameter whose value is + * requested; it cannot be null + * @param errors An ErrorList to trap any errors + * encountered when reading; it cannot be null + * @return The String value for param; + * it can be null + */ + @Override + public final String read(final Parameter param, final ErrorList errors) { + if (s_log.isDebugEnabled()) { + s_log.debug("Reading " + param + " from " + m_props); + } + + if (Assert.isEnabled()) { + Assert.exists(param, Parameter.class); + Assert.exists(errors, ErrorList.class); + } + + return m_props.getProperty(param.getName()); + } + + /** + * Returns a String representation of this object. + * + * @return super.toString() + "," + properties.size() + */ + @Override + public String toString() { + return super.toString() + "," + m_props.size(); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/JavaPropertyWriter.java b/ccm-core/src/main/java/com/arsdigita/util/JavaPropertyWriter.java new file mode 100644 index 000000000..94f116048 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/JavaPropertyWriter.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + +import com.arsdigita.util.parameter.Parameter; +import com.arsdigita.util.parameter.ParameterWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Properties; +import org.apache.log4j.Logger; + +/** + * Subject to change. + * + * An implementation of ParameterWriter that uses + * standard Java properties to store values. + * + * @see com.arsdigita.util.parameter.ParameterWriter + * @see JavaPropertyReader + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class JavaPropertyWriter implements ParameterWriter { + + private static final Logger s_log = Logger.getLogger + (JavaPropertyWriter.class); + + private static final String s_header = + " Generated by " + JavaPropertyWriter.class.getName(); + + private final Properties m_props; + + /** + * Constructs a parameter writer that uses props. + * + * @param props The Properties object that stores + * property values; it cannot be null + */ + public JavaPropertyWriter(final Properties props) { + Assert.exists(props, Properties.class); + + m_props = props; + } + + /** + * Tells the internal property object to store its values to + * out. + * + * @param out The OutputStream to send the saved + * parameters to; it cannot be null + */ + public final void store(final OutputStream out) { + if (s_log.isDebugEnabled()) { + s_log.debug("Storing " + this); + } + + Assert.exists(out, OutputStream.class); + + try { + m_props.store(out, s_header); + } catch (IOException ioe) { + throw new UncheckedWrapperException(ioe); + } + } + + /** + * Writes a String value back for a + * param. + * + * @param param The Parameter whose value is + * to be written; it cannot be null + * @param value The String value to write out; it can + * be null + */ + public final void write(final Parameter param, final String value) { + if (s_log.isDebugEnabled()) { + s_log.debug("Writing " + param + " with value " + value); + } + + if (Assert.isEnabled()) { + Assert.exists(param, Parameter.class); + } + + // XXX: Properties objects blow up when you try to put null + // values in them. This null check fixes it for now, but it + // doesn't let us explicitly write out a null value if that's + // what we actually want to store. I.e. our property store + // doesn't know the difference between a parameter being + // unspecified and a parameter being explicitly set to null. + if (value != null) { + m_props.setProperty(param.getName(), value); + } + } + + /** + * Returns a String representation of this object. + * + * @return super.toString() + "," + properties.size() + */ + public String toString() { + return super.toString() + "," + m_props.size(); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/Lockable.java b/ccm-core/src/main/java/com/arsdigita/util/Lockable.java new file mode 100644 index 000000000..45bcec656 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/Lockable.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + +/** + * A common interface for all lockable parts of ACS. The locking mechanism makes + * it possible to safely split all data structures that are used inside a web + * server into ones that are constant across all requests and those that may + * change during a request. + * + *

+ * The distinction betwen static and request-specific data helps in optimizing + * the amount of memory that needs to be allocated for each request. Data + * structures that are static can be allocated and initialized ahead of time, + * e.g., in the init method of a servlet. The initialized data + * structures are then locked to make them immutable. This mechanism + * ensures that static structures that are supposed to be shared by many + * requests, often even concurrently, do not change and are not "polluted" by + * request-specific data. + * + *

+ * There is no automatic mechanism that makes an object immutable by itself. The + * right checks and operations need to be implemented by each class implementing + * Lockable. + * + *

+ * Bebop parameters are a good example of how one logical structure is split + * into two classes: the class {@link + * com.arsdigita.bebop.parameters.ParameterModel} is Lockable and + * only contains the description of the parameter in an HTTP request that is + * static and does not change on a per-request basis, such as the name of the + * parameter and the (Java) type that the parameter value should be converted + * to. The class {@link + * com.arsdigita.bebop.parameters.ParameterData} contains all the + * request-specific information for a parameter, such as the value of the + * parameter. + * + *

+ * Any class that implements Lockable is expected to be fully + * modifiable until its {@link #lock} method is called. From that point on, it + * is read-only and should throw exceptions whenever an attempt is made to + * modify it. + * + * @author David Lutterkort + * @version $Id$ + */ +public interface Lockable { + + /** + * Lock an object. Locked objects are to be considered immutable. Any + * attempt to modify them, e.g., through a setXXX method should + * lead to an exception. + * + *

+ * Most lockable Bebop classes throw an {@link + * java.lang.IllegalStateException} if an attempt is made to modify a locked + * instance. + */ + void lock(); + + /** + * Return whether an object is locked and thus immutable, or can still be + * modified. + * + * @return + */ + boolean isLocked(); + +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/LockableImpl.java b/ccm-core/src/main/java/com/arsdigita/util/LockableImpl.java new file mode 100644 index 000000000..0e9d5faf2 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/LockableImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + +/** + * A generic implementation of the Lockable interface. + * + * @see Lockable + * + * @author Michael Bryzek + * @version $Id$ + * + */ +public class LockableImpl implements Lockable { + + private boolean m_locked = false; + + /** + * Lock an object. Locked objetcs are to be considered immutable. Any + * attempt to modify them, e.g., through a setXXX method should + * lead to an exception. + * + * @see Lockable#lock() + * + */ + // must not be final so cms.ui.Grid.GridModelBuilder can override it. + @Override + public void lock() { + m_locked = true; + } + + /** + * Return whether an object is locked and thus immutable, or can still be + * modified. + * + * @return + * + * @see Lockable#isLocked() + * + */ + // must not be final so cms.ui.PropertySheet.PSTMBAdapter can override it. + @Override + public boolean isLocked() { + return m_locked; + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/StringUtils.java b/ccm-core/src/main/java/com/arsdigita/util/StringUtils.java new file mode 100644 index 000000000..db4354d5e --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/StringUtils.java @@ -0,0 +1,1362 @@ +/* + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package com.arsdigita.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import org.apache.oro.text.perl.Perl5Util; +import org.apache.oro.text.regex.MalformedPatternException; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.PatternMatcher; +import org.apache.oro.text.regex.PatternMatcherInput; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.apache.oro.text.regex.Substitution; +import org.apache.oro.text.regex.Util; + +import org.apache.log4j.Logger; + +/** + * A (static) class of generally-useful string utilities. + * + * @author Bill Schneider + */ +public class StringUtils { + + private static final Logger s_log = Logger.getLogger(StringUtils.class); + + private static Perl5Util s_re = new Perl5Util(); + + public static final String NEW_LINE = System.getProperty("line.separator"); + + + private StringUtils() { + // can't instantiate me! + } + + + /** + * Tests if a string is empty. + * @param s A string to test + * @return true if s is null or empty; + * otherwise false + */ + public static boolean emptyString(String s) { + boolean expr = (s == null || s.trim().length() == 0); + return expr; + } + + /** + * Tests if a string is empty. + * @param o A string to test + * @return true if o is null or empty; + * otherwise false + */ + public static boolean emptyString(Object o) { + boolean expr = + (o == null || (o instanceof String && ((String)o).length() ==0)); + return expr; + } + + /** + * If the String is null, returns an empty string. Otherwise, + * returns the string unaltered + */ + public static String nullToEmptyString(String s) { + return (s == null) ? "" : s; + } + + /** + * Escapes some "special" characters in HTML text (ampersand, angle + * brackets, quote). + * @param s The plain-text string to quote + * @return The string with special characters escpaed. + */ + public static String quoteHtml(String s) { + if (s != null) { + StringBuffer result = new StringBuffer(s.length() + 10); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + switch (ch) { + case '&': + result.append("&"); + break; + case '"': + result.append("""); + break; + case '<': + result.append("<"); + break; + case '>': + result.append(">"); + break; + default: + result.append(ch); + } + } + return result.toString(); + } else { + return ""; + } + } + + /** + * Takes a plaintext string, and returns an HTML string that, when + * rendered by a web browser, will appear as the original input string + * + * @param s The input plaintext string + * @return A HTML string with blank lines coverted to

<p>
+ * and ampersands/angle brackets escaped. + */ + public static String textToHtml(String s) { + s = quoteHtml(s); + s = s_re.substitute("s/\r\n\r\n/

/g", s); + s = s_re.substitute("s/\n\n/

/g", s); + s = s_re.substitute("s/\r\r/

/g", s); + s = s_re.substitute("s/\r\n/
/g", s); + s = s_re.substitute("s/\n/
/g", s); + s = s_re.substitute("s/\r/
/g", s); + return s; + } + + /** + * Removes tags and substitutes P tags with newlines. For much + * more extensive conversion of HTML fragments to plain text + * equivalents, see {@link HtmlToText}. + */ + public static String htmlToText(String s) { + if (s != null) { + // first take out new-lines + s = s_re.substitute("s/\n//g", s); + s = s_re.substitute("s/\r//g", s); + s = s_re.substitute("s/<[Pp]>/\n\n/g", s); + s = s_re.substitute("s/
/\n/ig", s); + // take out other tags + s = s_re.substitute("s/<([^>]*)>/ /g", s); + return s; + } else { + return ""; + } + } + + + /** + * Converts plain text with simple inline markup + * into HTML. The following constructs are recognised: + * + *

+ */ + public static String smartTextToHtml(String s) { + ArrayList blocks = new ArrayList(); + s_re.split(blocks, "/\\r?\\n(\\r?\\n)+/", s); + + StringBuffer html = new StringBuffer(""); + Iterator i = blocks.iterator(); + while (i.hasNext()) { + String block = (String)i.next(); + if (s_re.match("/^\\s*(___+|---+)\\s*$/", block)) { + html.append("
"); + } else if (s_re.match("/^\\*\\s/", block)) { + html.append(smartTextList("/^\\*\\s+/m", "ul", block)); + } else if (s_re.match("/^\\+\\s/", block)) { + html.append(smartTextList("/^\\+\\s+/m", "ol", block)); + } else if (s_re.match("/\\w/", block)) { + html.append("
\n" + smartTextInline(block) + "\n
"); + } + html.append("\n"); + } + return html.toString(); + } + + private static String smartTextList(String match, + String type, + String s) { + ArrayList blocks = new ArrayList(); + s_re.split(blocks, match, s); + + StringBuffer list = new StringBuffer("<" + type + ">\n"); + Iterator i = blocks.iterator(); + while (i.hasNext()) { + String block = (String)i.next(); + + if ("".equals(block)) { + continue; + } + + list.append("
  • \n"); + list.append(smartTextInline(block)); + list.append("
  • \n"); + } + list.append(""); + + return list.toString(); + } + + private static Map s_entities = new HashMap(); + static { + s_log.debug("Static initalizer starting..."); + s_entities.put("fraction12", "½"); + s_entities.put("fraction14", "¼"); + s_entities.put("fraction34", "¾"); + s_entities.put("copyright", "©"); + s_entities.put("registered", "®"); + s_entities.put("trademark", "TM"); + s_log.debug("Static initalizer finished."); + } + + private static String smartTextInline(String s) { + HashMap links = new HashMap(); + if (s_log.isDebugEnabled()) { + s_log.debug("Input {" + s + "}"); + } + + // We're going to use the octal characters \u0001 and \u0002 for + // escaping stuff, so we'd better make sure there aren't any + // in the text. + s = s_re.substitute("s/\u0001|\u0002|\u0003//g", s); + + // We transform a few common symbols + // We don't substitute them straight in because the + // substituted text might interfere with stuff that + // follows... + s = s_re.substitute("s|\\b1/4\\b|\u0003fraction14\u0003|gx", s); + s = s_re.substitute("s|\\b1/2\\b|\u0003fraction12\u0003|gx", s); + s = s_re.substitute("s|\\b3/4\\b|\u0003fraction34\u0003|gx", s); + s = s_re.substitute("s|\\(C\\)|\u0003copyright\u0003|gx", s); + s = s_re.substitute("s|\\(R\\)|\u0003registered\u0003|gx", s); + s = s_re.substitute("s|\\(TM\\)|\u0003trademark\u0003|gx", s); + if (s_log.isDebugEnabled()) { + s_log.debug("After entities {" + s + "}"); + } + + // We've got to protect the url of titled links before we go further, + // however we can't actually generate the link yet because + // that interferes with the monospace stuff below.... + s = s_re.substitute("s|@@|\u0001|gx", s); + s = smartTextReplace(new TitledLinkSubstitution(links), + "@([^\\(@]+)\\(([^\\)]+)\\)", s); + + // We protect hyperlinks so that the '/' or '@' doesn't get + // mistaken for a block of italics / link + s = smartTextReplace(new UntitledLinkSubstitution(links), + "([a-z]+:\\/\\/[^\\s,\\(\\)><]*)", s); + s = smartTextReplace(new UntitledLinkSubstitution(links), + "(mailto:[^\\s,\\(\\)><]*)", s); + if (s_log.isDebugEnabled()) { + s_log.debug("After links {" + s + "}"); + } + + + // Next lets process italics /italic/ + // NB. this must be first, otherwise closing tags + // interfere with the pattern matching + s = s_re.substitute("s|//|\u0001|gx", s); + //s = s_re.substitute("s|(?$1|gx", s); + s = s_re.substitute("s|(\\W)/([^/]+)/(?!\\w)|$1$2|gx", s); + s = s_re.substitute("s|\u0001|/|gx", s); + + // Lets process bold text *bold* + s = s_re.substitute("s|\\*\\*|\u0001|gx", s); + //s = s_re.substitute("s|(?$1|gx", s); + s = s_re.substitute("s|(\\W)\\*([^\\*]+)\\*(?!\\w)|$1$2|gx", s); + s = s_re.substitute("s|\u0001|*|gx", s); + + // Now we're onto the monospace stuff =monospace= + s = s_re.substitute("s|==|\u0001|gx", s); + //s = s_re.substitute("s|(?$1|gx", s); + s = s_re.substitute("s|(\\W)=([^=]+)=(?!\\w)|$1$2|gx", s); + s = s_re.substitute("s|\u0001|=|gx", s); + + if (s_log.isDebugEnabled()) { + s_log.debug("After styles {" + s + "}"); + } + + + // Links are next on the list @text(url) + s = s_re.substitute("s|@@|\u0001|gx", s); + s = s_re.substitute("s|@([^\\(@]+)\\(([^\\)]+)\\)|$1|gx", s); + s = s_re.substitute("s|\u0001|@|gx", s); + if (s_log.isDebugEnabled()) { + s_log.debug("After links pass two {" + s + "}"); + } + + // Finally we can unobscure the hyperlinks + s = smartTextReplace(new UnobscureSubstitution(links), + "\u0002([^\u0002]+)\u0002", s); + s = s_re.substitute("s|\u0001|@|gx", s); + if (s_log.isDebugEnabled()) { + s_log.debug("After links pass three {" + s + "}"); + } + + // And those entities + s = smartTextReplace(new EntitySubstitution(), + "\u0003([^\u0003]+)\u0003", s); + if (s_log.isDebugEnabled()) { + s_log.debug("After entities (complete) {" + s + "}"); + } + + return s; + } + + /** + * + * @param subst + * @param pattern + * @param s + * @return + */ + private static String smartTextReplace(Substitution subst, + String pattern, + String s) { + Perl5Matcher matcher = new Perl5Matcher(); + Perl5Compiler compiler = new Perl5Compiler(); + StringBuffer result = new StringBuffer(); + PatternMatcherInput input = new PatternMatcherInput(s); + + try { + Util.substitute(result, + matcher, + compiler.compile(pattern), + subst, + input, + Util.SUBSTITUTE_ALL); + } catch (MalformedPatternException e) { + throw new UncheckedWrapperException("cannot perform substitution", e); + } + return result.toString(); + } + + /** + * + */ + private static class TitledLinkSubstitution implements Substitution { + private Map m_hash; + + public TitledLinkSubstitution(Map hash) { + m_hash = hash; + } + + public void appendSubstitution(StringBuffer appendBuffer, + MatchResult match, + int substitutionCount, + PatternMatcherInput originalInput, + PatternMatcher matcher, + Pattern pattern) { + String title = match.group(1); + String link = match.group(2); + s_log.debug("Link: " + link); + + Integer i = new Integer(m_hash.size()); + s_log.debug("Key: " + i); + m_hash.put(i, link); + String dst = "@" + title + "(\u0002" + i.toString() + "\u0002)"; + appendBuffer.append(dst); + s_log.debug("Encoded Link: " + dst); + } + } + + /** + * + */ + private static class UntitledLinkSubstitution implements Substitution { + private Map m_hash; + + public UntitledLinkSubstitution(Map hash) { + m_hash = hash; + } + + public void appendSubstitution(StringBuffer appendBuffer, + MatchResult match, + int substitutionCount, + PatternMatcherInput originalInput, + PatternMatcher matcher, + Pattern pattern) { + String link = match.group(1); + s_log.debug("Link: " + link); + + Integer i = new Integer(m_hash.size()); + s_log.debug("Key: " + i); + m_hash.put(i, link); + String dst = "@\u0002" + i.toString() + "\u0002(\u0002" + + i.toString() + "\u0002)"; + appendBuffer.append(dst); + s_log.debug("Encoded Link: " + dst); + } + } + + /** + * + */ + private static class UnobscureSubstitution implements Substitution { + private Map m_hash; + + public UnobscureSubstitution(Map hash) { + m_hash = hash; + } + + public void appendSubstitution(StringBuffer appendBuffer, + MatchResult match, + int substitutionCount, + PatternMatcherInput originalInput, + PatternMatcher matcher, + Pattern pattern) { + String s = match.group(1); + s_log.debug("Key: " + s); + + Integer i = new Integer(s); + appendBuffer.append((String)m_hash.get(i)); + s_log.debug("Link: " + m_hash.get(i)); + } + } + + /** + * + */ + private static class EntitySubstitution implements Substitution { + public void appendSubstitution(StringBuffer appendBuffer, + MatchResult match, + int substitutionCount, + PatternMatcherInput originalInput, + PatternMatcher matcher, + Pattern pattern) { + String s = match.group(1); + s_log.debug("Key: " + s); + + appendBuffer.append((String)s_entities.get(s)); + s_log.debug("Entity: " + s_entities.get(s)); + } + } + + + /** + * Convert a string of items separated by a separator + * character to an (string)array of the items. sep is the separator + * character. Example: Input - s == "cat,house,dog" sep==',' + * Output - {"cat", "house", "dog"} + * @param s string contains items separated by a separator character. + * @param sep separator character. + * @return Array of items. + */ + public static String [] split(String s, char sep) { + ArrayList al = new ArrayList(); + int start_pos, end_pos; + start_pos = 0; + while (start_pos < s.length()) { + end_pos = s.indexOf(sep, start_pos); + if (end_pos == -1) { + end_pos = s.length(); + } + String found_item = s.substring(start_pos, end_pos); + al.add(found_item); + start_pos = end_pos + 1; + } + if (s.length() > 0 && s.charAt(s.length()-1) == sep) { + al.add(""); // In case last character is separator + } + String [] returned_array = new String[al.size()]; + al.toArray(returned_array); + return returned_array; + } + + /** + *

    Given a string, split it into substrings matching a regular + * expression that you supply. Parts of the original string which + * don't match the regular expression also appear as substrings. The + * upshot of this is that the final substrings can be concatenated + * to get the original string.

    + * + *

    As an example, let's say the original string is:

    + * + *
    +     * s = "/packages/foo/xsl/::vhost::/foo_::locale::.xsl";
    +     * 
    + * + *

    We call the function like this:

    + * + *
    +     * output = splitUp (s, "/::\\w+::/");
    +     * 
    + * + *

    The result (output) will be the following list:

    + * + *
    +     * ("/packages/foo/xsl/", "::vhost::", "/foo_", "::locale::", ".xsl")
    +     * 
    + * + *

    Notice the important property that concatenating all these + * strings together will restore the original string.

    + * + *

    Here is another useful example. To split up HTML into elements + * and content, do:

    + * + *
    +     * output = splitUp (html, "/<.*?>/");
    +     * 
    + * + *

    You will end up with something like this:

    + * + *
    +     * ("The following text will be ", "", "bold", "", ".")
    +     * 
    + * + * @param s The original string to split. + * @param re The regular expression in the format required by + * {@link org.apache.oro.text.perl.Perl5Util#match(String, String)}. + * @return List of substrings. + * + * @author Richard W.M. Jones + * + *

    This is equivalent to the Perl "global match in array context", + * specifically: @a = /(RE)|(.+)/g;

    + * + */ + public static List splitUp (String s, String re) + { + Perl5Util p5 = new Perl5Util (); + ArrayList list = new ArrayList (); + + while (s != null && s.length() > 0) { + // Find the next match. + if (p5.match (re, s)) { + MatchResult result = p5.getMatch (); + + // String up to the start of the match. + if (result.beginOffset (0) > 0) + list.add (s.substring (0, result.beginOffset (0))); + + // Matching part. + list.add (result.toString ()); + + // Update s to be the remainder of the string. + s = s.substring (result.endOffset (0)); + } + else { + // Finished. + list.add (s); + + s = null; + } + } + + return list; + } + + /** + * Converts an array of Strings into a single String separated by + * a given character. + * Example Input: {"cat", "house", "dog"}, ',' + * Output - "cat,house,dog" + * + * @param strings The string array too join. + * @param joinChar The character to join the array members together. + * + * @pre strings != null + * + * @return Joined String + */ + public static String join(String[] strings, char joinChar) { + StringBuffer result = new StringBuffer(); + final int lastIdx = strings.length - 1; + for (int idx = 0; idx < strings.length; idx++) { + result.append(strings[idx]); + if (idx < lastIdx) { + result.append(joinChar); + } + } + + return result.toString(); + } + + /** + * Converts an array of Strings into a single String separated by + * a given string. + * Example Input: {"cat", "house", "dog"}, ", " + * Output - "cat, house, dog" + * + * @param strings The string array too join. + * @param joinStr The string to join the array members together. + * + * @pre strings != null + * + * @return Joined String + */ + public static String join(String[] strings, String joinStr) { + StringBuffer result = new StringBuffer(); + final int lastIdx = strings.length - 1; + for (int idx = 0; idx < strings.length; idx++) { + result.append(strings[idx]); + if (idx < lastIdx) { + result.append(joinStr); + } + } + + return result.toString(); + } + + /** + * Extract a parameter value from a packed list of parameter values. + * Example: + * input: key="age", sep=',', + * plist="cost=23,age=27,name=Thom" + * output = "27". + * This is a simple implementation that is meant for controlled use in which + * the key and values are known to be safe. + * Specifically, the equals character must be used to indicate + * parameter assignments. There is no escape character. Thus the + * parameter names and values cannot contain the equals character or the + * separator character. + * + * @param key the key indicating which parameter value to extract. + * @param plist packed list of key=value assignments. The character '=' + * must be used to indicate the assignment. + * @param sep separator character. + * @return the value corresponding to the key, or null if the key is not + * present. If the key appears in the list more than once, + * the first value is returned. + */ + public static String getParameter(String key, String plist, char sep) { + int key_end; + int key_start = 0; + String found_value; + while (key_start < plist.length()) { + key_start = plist.indexOf(key, key_start); + if (key_start == -1) { + return null; // Did not find key + } + key_end = key_start + key.length(); + if (plist.charAt(key_end) == '=' && + (key_start == 0 || plist.charAt(key_start - 1) == sep)) { + // Found isolated parameter value, this is the match + int value_end = plist.indexOf(sep, key_end); + if (value_end == -1) { + // did not find another separator, return value + found_value = plist.substring(key_end + 1); + } else { + // found another separator, return value + found_value = plist.substring(key_end + 1, value_end); + } + return found_value; + } else { + key_start++; // did not find. Advance past current position + } + } + return null; + } + + /** + * Strip extra white space from a string. This replaces any white space + * character or consecutive white space characters with a single space. + * It is useful when comparing strings that should be equal except for + * possible differences in white space. Example: input = "I \ndo\tsee". + * Output = "I do see". + * @param s string that may contain extra white space + * @return string the same as the input, but with extra white space + * removed and replaced by a single space. + */ + static public String stripWhiteSpace(String s) { + StringBuffer to = new StringBuffer(); + boolean inSpace = true; + boolean isSpace; + char c; + for (int i=0; i ") + .append(toString(e.getValue())); + } + to.append(NEW_LINE).append("}"); + } + } + } + String result = to.toString(); + return result; + } + + /** + * Strips all new-line characters from the input string. + * @param str a string to strip + * @return the input string with all new-line characters + * removed. + * @post result.indexOf('\r') == 0 + * @post result.indexOf('\n') == 0 + */ + public static String stripNewLines(String str) { + int len = str.length(); + StringBuffer sb = new StringBuffer(len); + for (int i = 0; i < len; i++) { + char ch = str.charAt(i); + if (ch != '\r' && ch != '\n') { + sb.append(ch); + } + } + return sb.toString(); + } + + /** + *

    Add a possible newline for proper wrapping.

    + * + *

    Checks the given String to see if it ends with whitspace. If so, it + * assumes this whitespace is intentional formatting and returns a reference + * to the original string. If not, a new String object is + * created containing the original plus a platform-dependent newline + * character obtained from {@link System#getProperty(String) + * System.getProperty("line.separator")}.

    + */ + public static String addNewline(String s) { + int n = s.length()-1; + if (n == -1) { + return s; + } else if (Character.isWhitespace(s.charAt(n))) { + return s; + } else { + return s.concat(NEW_LINE); + } + } + + + /** + * This takes the passed in string and truncates it. + * It cuts the string off at the length specified and then + * goes back to the most recent space and truncates any + * word that may have been cut off. It also takes the + * string and converts it to plain text so that no HTML + * will be shown. + */ + public static String truncateString(String s, int length) { + return truncateString(s, length, true); + } + + + /** + * This takes the passed in string and truncates it. + * It cuts the string off at the length specified and then + * goes back to the most recent space and truncates any + * word that may have been cut off. The htmlToText dictates + * whehter or not the string should be converted from HTML to + * text before being truncated + * + * @param s The string to be truncated + * @param length The length which to truncate the string + * @param removeHTML Whether or not to convert the HTML to text + */ + public static String truncateString(String s, int length, + boolean removeHTML) { + if (s == null) { + return ""; + } + + String string = s; + if (removeHTML) { + string = htmlToText(string); + } + + if (string.length() <= length) { + return string; + } + + return string.substring(0, string.lastIndexOf(" ", length)); + } + + + /** + * "join" a List of Strings into a single string, with each string + * separated by a defined separator string. + * + * @param elements the strings to join together + * @param sep the separator string + * @return the strings joined together + */ + public static String join(List elements, String sep) { + StringBuffer sb = new StringBuffer(); + boolean first = true; + Iterator iter = elements.iterator(); + + while (iter.hasNext()) { + String element = (String)iter.next(); + + if (!first) { + sb.append(sep); + } else { + first = false; + } + + sb.append(element); + } + + return sb.toString(); + } + + /** + * Removes whitespace from the beginning of a string. If the + * string consists of nothing but whitespace characters, an empty + * string is returned. + */ + public final static String trimleft(String s) { + for (int i = 0; i < s.length(); i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return s.substring(i); + } + } + return ""; + } + + /** + * Returns a String containing the specified repeat count of a + * given pattern String. + * + * @param pattern the pattern String + * @param repeatCount the number of time to repeat it + */ + public static String repeat(String pattern, int repeatCount) { + StringBuffer sb = new StringBuffer(repeatCount * pattern.length()); + for (int i = 0; i < repeatCount; i++) { + sb.append(pattern); + } + return sb.toString(); + } + + /** + * Returns a String containing the specified repeat count of a + * given pattern character. + * + * @param pattern the pattern character + * @param repeatCount the number of time to repeat it + */ + public static String repeat(char pattern, int repeatCount) { + return repeat(String.valueOf(pattern), repeatCount); + } + + /** + * Wrap a string to be no wider than 80 characters. This is just + * a convenience method for calling the more general method with a + * default string width. + * + * @param input the String to wrap + * + * @since 5.1.2 + */ + public static String wrap(String input) { + return wrap(input,80); + } + + /** + * Wrap a string to be no wider than a specified number of + * characters by inserting line breaks. If the input is null or + * the empty string, a string consisting of only the newline + * character will be returned. Otherwise the input string will be + * wrapped to the specified line length. In all cases the last + * character of the return value will be a single newline. + * + *

    Notes: + * + *

      + *
    1. line breaks in the input string are preserved + *
    2. wrapping is "soft" in that lines in the output string may + * be longer than maxLength if they consist of contiguous + * non-whitespace characters. + *
    + * + * @param input the String to wrap + * @param maxLength the maximum number of characters between line + * breaks + * + * @since 5.1.2 + */ + public static String wrap(String input, int maxLength) { + + final char SPACE = ' '; + final char ENDL = '\n'; + + // Make sure that we start with a string terminated by a + // newline character. Some of the index calculations below + // depend on this. + + if (emptyString(input)) { + return String.valueOf(ENDL); + } else { + input = input.trim() + String.valueOf(ENDL); + } + + StringBuffer output = new StringBuffer(); + + int startOfLine = 0; + + while (startOfLine < input.length()) { + + String line = input.substring + (startOfLine, Math.min(input.length(), + startOfLine + maxLength)); + + if (line.equals("")) { + break; + } + + int firstNewLine = line.indexOf(ENDL); + if (firstNewLine != -1) { + + // there is a newline + output.append + (input.substring(startOfLine, + startOfLine + firstNewLine)); + output.append(ENDL); + startOfLine += firstNewLine + 1; + continue; + } + + if (startOfLine + maxLength > input.length()) { + + // we're on the last line and it is < maxLength so + // just return it + + output.append(line); + break; + } + + int lastSpace = line.lastIndexOf(SPACE); + if (lastSpace == -1) { + + // no space found! Try the first space in the whole + // rest of the string + + int nextSpace = input.indexOf + (SPACE, startOfLine); + int nextNewLine = input.indexOf + (ENDL, startOfLine); + + if (nextSpace == -1) { + lastSpace = nextNewLine; + } else { + lastSpace = Math.min + (nextSpace,nextNewLine); + } + + if (lastSpace == -1) { + // didn't find any more whitespace, append the + // whole thing as a line + output.append(input.substring(startOfLine)); + break; + } + + // code below will add this to the start of the line + + lastSpace -= startOfLine; + } + + // append up to the last space + + output.append(input.substring(startOfLine, + startOfLine + lastSpace)); + output.append(ENDL); + + startOfLine += lastSpace + 1; + } + + return output.toString(); + } + + /** + * Returns true if the String is AlphaNumeric. Obviously, this is not at all + * globalized and should only be used with English text. + * + * @param value String to check + * @return true if value is alphanumeric, false otherwise. + */ + public static boolean isAlphaNumeric(String value) { + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || + ('0' <= c && c <= '9'))) { + return false; + } + } + + return true; + } + + /** + * This method performs interpolation on multiple variables. + * The keys in the hash table correspond directly to the placeholders + * in the string. The values in the hash table can either be + * plain strings, or an instance of the PlaceholderValueGenerator + * interface + * + * Variable placeholders are indicated in text by surrounding + * a key word with a pair of colons. The keys in the hash + * table correspond to the names + * + * eg. "::forename:: has the email address ::email::" + * + * @see java.text.MessageFormat + * + * @param text the text to interpolate + * @param vars a hash table containing key -> value mappings + */ + public static String interpolate(String text, Map vars) { + HashSubstitution subst = new HashSubstitution(vars); + Perl5Matcher matcher = new Perl5Matcher(); + Perl5Compiler compiler = new Perl5Compiler(); + StringBuffer result = new StringBuffer(); + PatternMatcherInput input = new PatternMatcherInput(text); + + try { + Util.substitute(result, + matcher, + compiler.compile("(::(?:\\w+(?:[.-]+\\w+)*)::)"), + subst, + input, + Util.SUBSTITUTE_ALL); + } catch (MalformedPatternException e) { + throw new UncheckedWrapperException("cannot perform substitution", e); + } + return result.toString(); + } + + + /** + * THis method performs a single variable substitution + * on a string. The placeholder takes the form of + * ::key:: within the sample text. + * + * @see java.text.MessageFormat + * + * @param text the text to process for substitutions + * @param key the name of the placeholder + * @param value the value to insert upon encountering a placeholder + */ + public static String interpolate(String text, String key, String value) { + String pattern = "s/::" + key + "::/" + value + "/"; + + return s_re.substitute(pattern, text); + } + + + + /** + * Finds all occurrences of find in str and + * replaces them with them with replace. + * + * @pre find != null + * @pre replace != null + */ + public static String replace(final String str, + final String find, + final String replace) { + + Assert.exists(find, String.class); + Assert.exists(replace, String.class); + + if ( str == null ) return null; + + int cur = str.indexOf(find); + if ( cur < 0 ) return str; + + final int findLength = find.length(); + // If replace is longer than find, assume the result is going to be + // slightly longer than the original string. + final int bufferLength = + replace.length() > findLength ? (int) (str.length() * 1.1) : str.length(); + StringBuffer sb = new StringBuffer(bufferLength); + int last = 0; + + if ( cur == 0 ) { + sb.append(replace); + cur = str.indexOf(find, cur+findLength); + last = findLength; + } + + while ( cur > 0 ) { + sb.append(str.substring(last, cur)); + sb.append(replace); + last = cur + findLength; + cur = str.indexOf(find, cur+findLength); + } + if ( last < str.length()-1) { + sb.append(str.substring(last)); + } + + return sb.toString(); + } + + + /** + * An interface allowing the value for a placeholder to be + * dynamically generated. + */ + public interface PlaceholderValueGenerator { + /** + * Returns the value corresponding to the supplied key + * placeholder. + * + * @param key the key being substituted + */ + public String generate(String key); + } + + + + /** + * + */ + private static class HashSubstitution implements Substitution { + private Map m_hash; + + public HashSubstitution(Map hash) { + m_hash = hash; + } + + public void appendSubstitution(StringBuffer appendBuffer, + MatchResult match, + int substitutionCount, + PatternMatcherInput originalInput, + PatternMatcher matcher, + Pattern pattern) { + String placeholder = match.toString(); + String key = placeholder.substring(2, placeholder.length()-2); + + Object value = (m_hash.containsKey(key) ? + m_hash.get(key) : + placeholder); + + if( s_log.isDebugEnabled() ) { + Object hashValue = m_hash.get( key ); + + s_log.debug( "Placeholder: " + placeholder ); + s_log.debug( "Key: " + key ); + if( null != value ) { + s_log.debug( "Value (" + value.getClass().getName() + + "): " + value.toString() ); + } + if( null != hashValue ) { + s_log.debug( "Hash Value (" + + hashValue.getClass().getName() + "): " + + hashValue.toString() ); + } + } + + value = (m_hash.containsKey(key) ? m_hash.get(key) : ""); + + String val; + if( value instanceof PlaceholderValueGenerator ) { + PlaceholderValueGenerator gen = (PlaceholderValueGenerator)value; + val = gen.generate(key); + } else if( value.getClass().isArray() ) { + Object[] values = (Object[]) value; + + StringBuffer buf = new StringBuffer(); + for( int i = 0; i < values.length; i++ ) { + buf.append( values[i].toString() ); + if( (values.length - 1) != i ) { + buf.append( ", " ); + } + } + + val = buf.toString(); + } else { + val = value.toString(); + } + + appendBuffer.append(val); + } + } + + /** + * @throws NullPointerException if throwable is null + */ + public static String getStackTrace(Throwable throwable) { + if (throwable==null) { throw new NullPointerException("throwable"); } + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + + /** + * Returns a list of lines where each line represents one level + * in the stack trace captured by throwable. + * + *

    For a stack trace like this:

    + * + *
    +     * java.lang.Throwable
    +     *         at Main.level3(Main.java:19)
    +     *         at Main.level2(Main.java:15)
    +     *         at Main.level1(Main.java:11)
    +     *         at Main.main(Main.java:7)
    +     * 
    + * + *

    the returned list looks like this:

    + * + *
    +     * ["java.lang.Throwable",
    +     *  "Main.level3(Main.java:20)",
    +     *  "Main.level2(Main.java:15)",
    +     *  "Main.level1(Main.java:11)",
    +     *  "Main.main(Main.java:7)"]
    +     * 
    + * + * @see #getStackTrace(Throwable) + * @throws NullPointerException if throwable is null + */ + public static List getStackList(Throwable throwable) { + StringTokenizer tkn = new StringTokenizer + (getStackTrace(throwable), System.getProperty("line.separator")); + List list = new LinkedList(); + while ( tkn.hasMoreTokens() ) { + String token = tkn.nextToken().trim(); + if ( "".equals(token) ) { continue; } + if ( token.startsWith("at ") ) { + list.add(token.substring(3)); + } else { + list.add(token); + } + } + + return list; + } + + /** + * Convert a name into a URL form, the java equivalent of + * "manipulate-input.js" + * + * For example, "Business promotions!" will be converted to + * "business-promotions". + * + * @param name + * the to be converted into a URL. + * @return the converted name, possibly unchanged and null if the input is null. + */ + public static String urlize(String name) { + if (name == null) { + return null; + } + StringBuffer urlizedName = new StringBuffer(name.length()); + + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + + if (Character.isLetter(ch)) { + urlizedName.append(Character.toLowerCase(ch)); + } + else if (Character.isDigit(ch) || ch == '_' || ch == '-') { + urlizedName.append(ch); + } + else if (ch == ' ' || ch == '&' || ch == '/') { + urlizedName.append('-'); + } + } + return urlizedName.toString(); + } +} + + diff --git a/ccm-core/src/main/java/com/arsdigita/util/UncheckedWrapperException.java b/ccm-core/src/main/java/com/arsdigita/util/UncheckedWrapperException.java new file mode 100644 index 000000000..4c5253aba --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/UncheckedWrapperException.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util; + +import org.apache.log4j.Logger; + +/** + * A wrapper exception that can be used to rethrow another + * exception. + * + * TODO: This should become a skeleton when/if we switch to Java 1.4. + * http://java.sun.com/j2se/1.4/docs/guide/lang/chained-exceptions.html + * + * The basic exception methods are overridden with methods that + * combine this wrapper and its root cause, so it can be + * treated just like any normal exception in actual use. + * + * Note that it is not necessary to provide a string along + * with a root cause; in particular, the following usage: + * new UncheckedWrapperException(e); is more correct than + * new UncheckedWrapperException(e.getMessage(), e); + * + * @author David Eison + * @version $Id$ + */ +public class UncheckedWrapperException extends RuntimeException { + + private static final Logger logger = Logger.getLogger( + UncheckedWrapperException.class); + + static { + logger.debug("Static initalizer starting..."); + Exceptions.registerUnwrapper( + UncheckedWrapperException.class, + new ExceptionUnwrapper() { + + public Throwable unwrap(Throwable t) { + UncheckedWrapperException ex = + (UncheckedWrapperException) t; + return ex.getRootCause(); + } + }); + logger.debug("Static initalizer finished."); + } + Throwable m_rootCause; + + /** + * Constructor which only takes a msg, which will cause this + * UncheckedWrapperException to behave like a normal RuntimeException. + * While it doesn't seem to make a lot of sense to have a wrapper + * exception that doesn't wrap anything, this is needed so that it + * can be used as a direct replacement for RuntimeException. + */ + public UncheckedWrapperException(String msg) { + this(msg, null); + } + + /** + * Constructor which takes a root cause + * that this exception will be wrapping. + */ + public UncheckedWrapperException(Throwable rootCause) { + this(null, rootCause); + } + + /** + * Constructor which takes a message string and a root cause + * that this exception will be wrapping. The message string + * should be something different than rootCause.getMessage() + * would normally provide. + */ + public UncheckedWrapperException(String s, Throwable rootCause) { + super(s); + this.m_rootCause = rootCause; + } + + /** + * Throws an UncheckedWrapperException, and ensurs that it is logged at ERROR priority. + * + * @param source Class having the error. For Log4J reporting + * @param msg Error message + * @param rootCause The root cause exception + * + * @throws UncheckedWrapperException + */ + public static void throwLoggedException(Class source, String msg, + Throwable rootCause) throws + UncheckedWrapperException { + Logger log = Logger.getLogger(source); + log.error(msg, rootCause); + + throw new UncheckedWrapperException(msg, rootCause); + } + + /** + * Indicates if this exception has a root cause. + */ + public boolean hasRootCause() { + return m_rootCause != null; + } + + /** + * Gets the root cause of this exception. + */ + public Throwable getRootCause() { + return m_rootCause; + } + + // All further methods override normal throwable behavior to + // combine information w/ the root cause. + /** + * Get a string representing this exception and the root cause. + */ + @Override + public String toString() { + return toString(this.getClass()); + } + + /** + * Get a string representing this exception and the root cause. + * + * Functions like normal toString, except that the name of the + * provided class will be used instead of the name of the + * unchecked wrapper exception. Useful when another exception + * class is using an unchecked wrapper exception to delegate + * to. + */ + public String toString(Class delegatingClass) { + // default toString calls getMessage, so we don't want to rely on it + // here. + StringBuffer b = new StringBuffer(delegatingClass.getName()); + String superMsg = super.getMessage(); + if (superMsg != null) { + b.append(": ").append(superMsg); + } + if (m_rootCause != null) { + b.append(" (root cause: ").append(m_rootCause.toString()); + b.append(")"); + } + return b.toString(); + } + + /** + * This exception's message and the root cause's. + */ + @Override + public String getMessage() { + if (m_rootCause != null) { + return super.getMessage() + " (root cause: " + m_rootCause. + getMessage() + ")"; + } else { + return super.getMessage(); + } + } + + /** + * Stack trace for the root cause. + */ + @Override + public void printStackTrace() { + super.printStackTrace(); + if (m_rootCause != null) { + System.err.print("Root cause: "); + m_rootCause.printStackTrace(); + } + } + + /** + * Stack trace for the root cause. + */ + @Override + public void printStackTrace(java.io.PrintStream s) { + super.printStackTrace(s); + if (m_rootCause != null) { + s.println("Root cause: "); + m_rootCause.printStackTrace(s); + } + } + + /** + * Stack trace for the root cause. + */ + @Override + public void printStackTrace(java.io.PrintWriter s) { + super.printStackTrace(s); + if (m_rootCause != null) { + s.println("Root cause: "); + m_rootCause.printStackTrace(s); + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/AbstractParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/AbstractParameter.java new file mode 100644 index 000000000..3fcaf5573 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/AbstractParameter.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Assert; +import org.apache.commons.beanutils.ConversionException; + +/** + * A base implementation of the Parameter interface. + * + * It offers subclasses use of the Apache BeanUtils framework, should + * they opt to use it. + * + * Methods of the form doXXX are extension points for subclasses. + * The isRequired() and getDefaultValue() + * methods may also be overriden. + * + * Subject to change. + * + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public abstract class AbstractParameter implements Parameter { + + private final String m_name; + private final Class m_type; + private final int m_multiplicity; + private final Object m_default; + private ParameterInfo m_info; + + /** + * Constructs a new parameter with the default value + * defaalt and using the beanutils converter + * registered for type. + * + * @param name The name of the parameter; it cannot be null + * @param multiplicity The multiplicity type of the parameter + * @param defaalt The default value to use if the value is unset + * or is null + * @param type The Class whose beanutils converter + * will be used to unmarshal literal values + */ + protected AbstractParameter(final String name, + final int multiplicity, + final Object defaalt, + final Class type) { + if (Assert.isEnabled()) { + Assert.exists(name, String.class); + } + + m_name = name; + m_type = type; + m_multiplicity = multiplicity; + m_default = defaalt; + } + + /** + * Constructs a new parameter with the default value + * defaalt. + * + * @param name The name of the parameter; it cannot be null + * @param multiplicity The multiplicity type of the parameter + * @param defaalt The default value to use if the value is unset + * or is null + */ + protected AbstractParameter(final String name, + final int multiplicity, + final Object defaalt) { + // XXX Get rid of this constructor? + this(name, multiplicity, defaalt, null); + } + + /** + * Constructs a new parameter using the beanutils converter for + * type type. By default, the parameter is required + * and has no default. + * + * @param name The name of the parameter; it cannot be null + * @param type The Class whose beanutils converter + * will be used to unmarshal literal values + */ + protected AbstractParameter(final String name, + final Class type) { + this(name, Parameter.REQUIRED, null, type); + } + + /** + * Parameter users may override this method to make the multiplicity of + * the parameter dependent on the multiplicity of related parameters. + * + * @see Parameter#isRequired() + */ + public boolean isRequired() { + return m_multiplicity == Parameter.REQUIRED; + } + + /** + * @see Parameter#getName() + */ + public final String getName() { + return m_name; + } + + /** + * Parameter users may override this method to achieve dynamic + * defaulting. + * + * @see Parameter#getDefaultValue() + */ + public Object getDefaultValue() { + return m_default; + } + + /** + * @see Parameter#getInfo() + */ + public final ParameterInfo getInfo() { + return m_info; + } + + /** + * @see Parameter#setInfo(ParameterInfo) + */ + public final void setInfo(final ParameterInfo info) { + m_info = info; + } + + // + // Lifecycle events + // + + /** + * Gets the parameter value as a Java object. + * + * The value will have a specific runtime type and so may be + * appropriately cast. + * + * Reading typically follows the following procedure: + * + *
      + *
    • Read the literal string value associated with the + * parameter from reader
    • + * + *
    • Convert the literal string value into an approprite Java + * object
    • + *
    + * + * If at any point in the process an error is encountered, it is + * added to errors. Callers of this method will + * typically construct an ErrorList in which to + * collect errors. + * Actually calls {@link #doRead(ParameterReader,ErrorList)} (as an + * extension point for subclasses). + * + * @param reader The ParameterReader from which to + * recover a string literal value; it cannot be null + * @param errors The ErrorList in which to collect + * any errors encountered; it cannot be null + * @return The Java object value of the parameter + * + */ + public final Object read(final ParameterReader reader, + final ErrorList errors) { + if (Assert.isEnabled()) { + Assert.exists(reader, ParameterReader.class); + Assert.exists(errors, ErrorList.class); + } + + return doRead(reader, errors); + } + + /** + * Extension point to read the value of the parameter from reader. + * + * It unmarshals the value, and returns it. If any errors are encountered, + * they are added to errors. + * + * If the literal string value from reader is not null, + * this method delegates to {@link #unmarshal(String,ErrorList)}. + * + * This implementation is suited to a parameter with a singular + * scalar value. Subclasses that are compound parameters should + * override this method to delegate to child parameters. + * + * @param reader The ParameterReader that will supply + * the literal stored value for this parameter; it cannot be null + * @param errors The ErrorList that will trap any + * errors encountered; it cannot be null + */ + protected Object doRead(final ParameterReader reader, + final ErrorList errors) { + final String string = reader.read(this, errors); + + if (string == null) { + return null; + } else { + return unmarshal(string, errors); + } + } + + /** + * Converts a literal String value, + * value, to a Java object, which is returned. + * + * @param value The String value to convert from; it + * cannot be null + * @param errors An ErrorList that holds any errors + * encountered during unmarshaling; it cannot be null + */ + protected Object unmarshal(final String value, final ErrorList errors) { + if (Assert.isEnabled()) { + Assert.exists(value, String.class); + Assert.exists(errors, String.class); + } + + try { + return Converters.convert(m_type, value); + } catch (ConversionException ce) { + errors.add(new ParameterError(this, ce)); + return null; + } + } + + /** + * Calls {@link #doValidate(Object,ErrorList)} if + * value is not null. Otherwise, if the value is + * required and null, an error is added to + * errors and doValidate is not called. + * + * @see Parameter#validate(Object,ErrorList) + */ + public final void validate(final Object value, final ErrorList errors) { + Assert.exists(errors, ErrorList.class); + + if (value == null) { + // If the value is null, validation stops here. + + if (isRequired()) { + final ParameterError error = new ParameterError + (this, "The value must not be null"); + errors.add(error); + } + } else { + // Always do further validation for non-null values. + + doValidate(value, errors); + } + } + + /** + * Validates value, placing any validation errors in + * errors. This particular implementation does + * nothing. Subclasses are expected to add specific validation + * behaviors. + * + * @param value The value to validate; it cannot be null + * @param errors The ErrorList that traps validation + * errors; it cannot be null + */ + protected void doValidate(final Object value, final ErrorList errors) { + if (Assert.isEnabled()) { + Assert.exists(value, Object.class); + Assert.exists(errors, ErrorList.class); + } + + // Nothing + } + + /** + * Calls {@link #doWrite(ParameterWriter,Object)}. + * + * @see Parameter#write(ParameterWriter,Object) + */ + public final void write(final ParameterWriter writer, final Object value) { + Assert.exists(writer); + + // XXX what to do about nulls here? + + doWrite(writer, value); + } + + /** + * Marshals and writes value to writer. + * + * This implementation is suited to a parameter with a singular + * scalar value. Subclasses that are compound parameters should + * override this method to delegate to child parameters. + * + * @param writer The ParameterWriter we write to; it + * cannot be null + * @param value The value to write; it may be null + */ + protected void doWrite(final ParameterWriter writer, final Object value) { + writer.write(this, marshal(value)); + } + + /** + * Converts value to a representative + * String, which is returned. + * + * @param value The value to marshal; it may be null + * @return The String literal representation of + * value; it may be null + */ + protected String marshal(final Object value) { + if (value == null) { + return null; + } else { + return value.toString(); + } + } + + /** + * Returns a String representation of this object. + * + * @return super.toString() + "," + getName() + "," + + * isRequired() + */ + public String toString() { + return super.toString() + "," + getName() + "," + isRequired(); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/AbstractParameterContext.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/AbstractParameterContext.java new file mode 100644 index 000000000..7714d68b3 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/AbstractParameterContext.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Assert; +import com.arsdigita.util.UncheckedWrapperException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.apache.log4j.Logger; + +/** + * A base implementation of the ParameterContext + * interface. + * + * Subject to change. + * + * @see com.arsdigita.util.parameter.ParameterContext + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public abstract class AbstractParameterContext implements ParameterContext { + + private static final Logger s_log = Logger.getLogger + (AbstractParameterContext.class); + + private final MapParameter m_param; + private final HashMap m_map; + private final Properties m_info; + + /** + * Constructs a parameter context. + */ + public AbstractParameterContext() { + m_param = new MapParameter("root"); + m_map = new HashMap(); + m_info = new Properties(); + } + + /** + * Registers param to the context. + * + * @param param The Parameter being registered; it + * cannot be null + */ + public final void register(final Parameter param) { + if (s_log.isDebugEnabled()) { + s_log.debug("Registering " + param + " on " + this); + } + + if (Assert.isEnabled()) { + Assert.exists(param, Parameter.class); + Assert.isTrue(!m_param.contains(param), + param + " is already registered"); + } + + m_param.add(param); + } + + /** + * @see ParameterContext#getParameters() + */ + public final Parameter[] getParameters() { + final ArrayList list = new ArrayList(); + final Iterator params = m_param.iterator(); + + while (params.hasNext()) { + list.add(params.next()); + } + + return (Parameter[]) list.toArray(new Parameter[list.size()]); + } + + /** + * Gets the unmarshaled value of param. + * + * If the loaded value is null, param.getDefaultValue() + * is returned. + * + * @param param The named Parameter whose value to + * retrieve; it cannot be null + * @return The unmarshaled Java object value of param + */ + public Object get(final Parameter param) { + return get(param, param.getDefaultValue()); + } + + /** + * Gets the unmarshaled value of param, returning + * dephalt if param's value is null. + * + * @param param The Parameter whose value to + * retrieve; it cannot be null + * @param dephalt The fallback default value; it may be null + * @return The unmarshaled Java object value of param + * or dephalt if the former is null + */ + public Object get(final Parameter param, final Object dephault) { + if (Assert.isEnabled()) { + Assert.exists(param, Parameter.class); + Assert.isTrue(m_param.contains(param), + param + " has not been registered"); + } + + // XXX check for is loaded? + + final Object value = m_map.get(param); + + if (value == null) { + return dephault; + } else { + return value; + } + } + + /** + * @see ParameterContext#get(Parameter,Object) + */ + public void set(final Parameter param, final Object value) { + if (s_log.isDebugEnabled()) { + s_log.debug("Setting " + param + " to " + value); + } + + Assert.exists(param, Parameter.class); + + m_map.put(param, value); + } + + /** + * Reads and unmarshals all values associated with the registered + * parameters from reader. Any errors are returned. + * + * @param reader The ParameterReader from which to + * fetch the values; it cannot be null + * @return An ErrorList containing any errors + * encountered while loading; it cannot be null + */ + public final ErrorList load(final ParameterReader reader) { + final ErrorList errors = new ErrorList(); + + load(reader, errors); + + return errors; + } + + /** + * Reads and unmarshals all values associated with the registered + * parameters from reader. If any errors are + * encountered, they are added to errors. + * + * @param reader The ParameterReader from which to + * fetch the values; it cannot be null + * @param errors The ErrorList that captures any + * errors while loading; it cannot be null + */ + public final void load(final ParameterReader reader, + final ErrorList errors) { + if (Assert.isEnabled()) { + Assert.exists(reader, ParameterReader.class); + Assert.exists(errors, ErrorList.class); + } + + m_map.putAll((Map) m_param.read(reader, errors)); + } + + /** + * Validates all values associated with the registered parameters. + * Any errors encountered are returned. + * + * @return An ErrorList containing validation errors; + * it cannot be null + */ + public final ErrorList validate() { + final ErrorList errors = new ErrorList(); + + m_param.validate(m_map, errors); + + return errors; + } + + /** + * @see ParameterContext#validate(ErrorList) + */ + public final void validate(final ErrorList errors) { + Assert.exists(errors, ErrorList.class); + + m_param.validate(m_map, errors); + } + + /** + * @see ParameterContext#save(ParameterWriter) + */ + public final void save(ParameterWriter writer) { + m_param.write(writer, m_map); + } + + /** + * Loads source data for ParameterInfo objects from + * the file YourClass_parameter.properties next to + * YourClass.class. + * + * YourClass_parameter.properties: + * + *
    +     * yourforum.notification_enabled.title=Flag to enable forum notifications
    +     * yourforum.notification_enabled.purpose=Enables or disables forum notifications
    +     * yourforum.notification_enabled.example=true
    +     * yourforum.notifiaction_enabled.format=true|false
    +     * 
    + * + * @see ParameterInfo + */ + protected final void loadInfo() { + final InputStream in = findInfo(getClass()); + + try { + m_info.load(in); + } catch (IOException ioe) { + throw new UncheckedWrapperException(ioe); + } + + final Iterator params = m_param.iterator(); + + while (params.hasNext()) { + final Parameter param = (Parameter) params.next(); + + param.setInfo(new Info(param)); + } + } + + // + // Private classes and methods + // + + private class Info implements ParameterInfo { + private final String m_name; + + Info(final Parameter param) { + m_name = param.getName(); + } + + public final String getTitle() { + return m_info.getProperty(m_name + ".title"); + } + + public final String getPurpose() { + return m_info.getProperty(m_name + ".purpose"); + } + + public final String getExample() { + return m_info.getProperty(m_name + ".example"); + } + + public final String getFormat() { + return m_info.getProperty(m_name + ".format"); + } + } + + private static InputStream findInfo(final Class klass) { + final List files = new LinkedList(); + InputStream in = findInfo(klass, files); + if ( in == null ) { + throw new IllegalStateException + ("Could not find any of the following files: " + files); + } + return in; + } + + private static InputStream findInfo(final Class klass, final List files) { + if (klass == null) { return null; } + final String name = + klass.getName().replace('.', '/') + "_parameter.properties"; + files.add(name); + if ( klass.getClassLoader() == null ) { + return null; + } + final InputStream in = klass.getClassLoader().getResourceAsStream(name); + + if (in == null) { + return findInfo(klass.getSuperclass(), files); + } else { + return in; + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/BooleanParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/BooleanParameter.java new file mode 100644 index 000000000..2365982cf --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/BooleanParameter.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import org.apache.commons.beanutils.converters.BooleanConverter; + +/** + * Subject to change. + * + * A parameter representing a Java Boolean. + * + * @see java.lang.Boolean + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class BooleanParameter extends AbstractParameter { + + static { + Converters.set(Boolean.class, new BooleanConverter()); + } + + public BooleanParameter(final String name) { + super(name, Boolean.class); + } + + public BooleanParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt, Boolean.class); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/CSVParameterReader.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/CSVParameterReader.java new file mode 100644 index 000000000..7070edf58 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/CSVParameterReader.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.UncheckedWrapperException; +// import com.arsdigita.util.parameter.ErrorList; +// import com.arsdigita.util.parameter.Parameter; +// import com.arsdigita.util.parameter.ParameterLoader; +// import com.arsdigita.util.parameter.ParameterValue; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Processes an input stream (a set of lines, each containing a comma separated + * list of parameter values) and .... + * + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public final class CSVParameterReader implements ParameterReader { + + private final LineNumberReader m_reader; + private final Parameter[] m_params; + private final HashMap m_line; + + /** + * Constructor + * + * @param reader: input stream to read values + * @param params: array of parameter objects to store procecced values + */ + public CSVParameterReader(final Reader reader, final Parameter[] params) { + m_reader = new LineNumberReader(reader); // input stream + m_params = params; // array of parameters + m_line = new HashMap(params.length); // + } + + /** + * read + * + * + * + * @param param + * @param errors + * @return + */ + public final String read(final Parameter param, final ErrorList errors) { + return (String) m_line.get(param); + } +/* + * May 2009: Obviously a reminiscence from previous versions of code. This class + * is currently used by coreloader only and it does not use the load method + * and it works with load commented out. + * + * Code should be removed after extensive testing. + * + public final ParameterValue load(final Parameter param) { + final ParameterValue value = new ParameterValue(); + + // XXX this won't work correctly with compound parameters + value.setObject(param.read(this, value.getErrors())); + + value.getErrors().check(); + + return value; + } +*/ + /** + * Just a public visible entry point into internalNext, used to process + * an exception if thrown. + * + * @return: boolean true if any values could be processed. + */ + public final boolean next() { + try { + return internalNext(); + } catch (IOException ioe) { + throw new UncheckedWrapperException(ioe); + } + } + + /** + * Internally used worker method which processes the next() method. + * + * Reads in a line from input stream and asks parseLine to process it. The + * resulting array of strings (each containing a value) + * @return + * @throws java.io.IOException + */ + private boolean internalNext() throws IOException { + final String line = m_reader.readLine(); + + if (line == null) { + return false; + } else { + final String[] elems = parseLine(line); + + // m_params: array of parameters to store the comma separated values + // used to determine the max. number of values which can be processed. + for (int i = 0; i < m_params.length; i++) { + if (i < elems.length) { + // If for the given index into the array of parametes a + // corresponding element in the array of strings exist, + // store it in a hash map (a hash map per line) + m_line.put(m_params[i], elems[i]); + } else { + m_line.put(m_params[i], null); + } + } + + return true; + } + } + + private static final char ESCAPE = '\\'; + private static final char QUOTE = '"'; + private static final char SEPARATOR = ','; + + /** + * Internal used helper method of method parseLine. + * + * @param c + * @return + */ + private char escape(char c) { + switch (c) { + case 'n': + return '\n'; + case 't': + return '\t'; + case 'r': + return '\r'; + default: + return c; + } + } + + /** + * Takes a string and analyses it as a list of comma separated values. + * + * Internally used to store each value found in a new string and add it + * to an array of strings. + * + * @param line: string containing a comma separated list of values + * @return : array of strings, each containing a value of the list + */ + private String[] parseLine(final String line) { + int length = line.length(); + + // Check here if the last character is an escape character so + // that we don't need to check in the main loop. + if (line.charAt(length - 1) == ESCAPE) { + throw new IllegalArgumentException + (m_reader.getLineNumber() + + ": last character is an escape character\n" + line); + } + + // The set of parsed fields. + List result = new ArrayList(); + + // The characters between seperators. + StringBuffer buf = new StringBuffer(length); + // Marks the begining of the field relative to buf, + // -1 indicates the beginning of buf. + int begin = -1; + // Marks the end of the field relative to buf. + int end = 0; + + // Indicates whether or not we're in a quoted string. + boolean quote = false; + + for (int i = 0; i < length; i++) { + char c = line.charAt(i); + if (quote) { + switch (c) { + case QUOTE: + quote = false; + break; + case ESCAPE: + buf.append(escape(line.charAt(++i))); + break; + default: + buf.append(c); + break; + } + + end = buf.length(); + } else { + switch (c) { + case SEPARATOR: + result.add(field(buf, begin, end)); + buf = new StringBuffer(length); + begin = -1; + end = 0; + break; + case ESCAPE: + if (begin < 0) { begin = buf.length(); } + buf.append(escape(line.charAt(++i))); + end = buf.length(); + break; + case QUOTE: + if (begin < 0) { begin = buf.length(); } + quote = true; + end = buf.length(); + break; + default: + if (begin < 0 && + !Character.isWhitespace(c)) { + begin = buf.length(); + } + buf.append(c); + if (!Character.isWhitespace(c)) { end = buf.length(); } + break; + } + } + } + + if (quote) { + throw new IllegalArgumentException + (m_reader.getLineNumber() + ": unterminated string\n" + line); + } else { + result.add(field(buf, begin, end)); + } + + String[] fields = new String[result.size()]; + result.toArray(fields); + return fields; + } + + /** + * internal helper method for method parseLine + * + * @param field + * @param begin + * @param end + * @return + */ + private String field(StringBuffer field, int begin, int end) { + if (begin < 0) { + return field.substring(0, end); + } else { + return field.substring(begin, end); + } + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ClassParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ClassParameter.java new file mode 100644 index 000000000..1e067a98c --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ClassParameter.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import org.apache.commons.beanutils.converters.ClassConverter; +import org.apache.log4j.Logger; + +/** + * A parameter representing a Java Class. + * + * Subject to change. + * + * @see java.lang.Class + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class ClassParameter extends AbstractParameter { + + private static final Logger logger = Logger.getLogger(ClassParameter.class); + + static { + logger.debug("Static initalizer starting..."); + Converters.set(Class.class, new ClassConverter()); + logger.debug("Static initalizer finished."); + } + + public ClassParameter(final String name) { + super(name, Class.class); + } + + public ClassParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt, Class.class); + } + + // value != null + protected Object unmarshal(String value, ErrorList errors) { + Class theClass = null; + try { + theClass = Class.forName(value); + } catch (ClassNotFoundException e) { + errors.add(new ParameterError(this, "No such class: " + value)); + } + + return theClass; + } + + protected String marshal(Object value) { + Class theClass = ((Class) value); + if (theClass == null) { + return null; + } else { + return theClass.getName(); + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/CompoundParameterReader.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/CompoundParameterReader.java new file mode 100644 index 000000000..f5789dea6 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/CompoundParameterReader.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Assert; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.apache.log4j.Logger; + +/** + * Aggregates a set of ParameterReaders so they may be + * treated as one. + * + * Subject to change. + * + * @see ParameterReader + * @author Rafael H. Schloming <rhs@mit.edu> + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class CompoundParameterReader implements ParameterReader { + + private static final Logger s_log = Logger.getLogger + (CompoundParameterReader.class); + + private final List m_readers; + + /** + * Constructs a new compound parameter reader. + */ + public CompoundParameterReader() { + m_readers = new ArrayList(); + } + + /** + * Adds reader to the set of component readers. + * + * @param reader The ParameterReader being added; it + * cannot be null + */ + public void add(final ParameterReader reader) { + Assert.exists(reader, ParameterReader.class); + + m_readers.add(reader); + } + + /** + * @see ParameterReader#read(Parameter,ErrorList) + */ + public String read(final Parameter param, final ErrorList errors) { + for (final Iterator it = m_readers.iterator(); it.hasNext(); ) { + final ParameterReader reader = (ParameterReader) it.next(); + + final String result = reader.read(param, errors); + + if (result != null) { + return result; + } + } + + return null; + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/Converters.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/Converters.java new file mode 100644 index 000000000..0f8c4ba45 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/Converters.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Assert; + +import java.util.Collections; +import java.util.Map; +import java.util.HashMap; + +import org.apache.commons.beanutils.Converter; + +/** + * Subject to change. + * + * Collects together BeanUtils converters for use by the base + * Parameters. + * + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class Converters { + + private static Map s_converters = Collections.synchronizedMap + (new HashMap()); + + /** + * Gets the Converter registered for + * clacc. This method will fail if no converter is + * found. + * + * @param clacc The Class of the parameter value; it + * cannot be null + * @return A Converter instance; it cannot be null + */ + public static final Converter get(final Class clacc) { + Assert.exists(clacc, Class.class); + + final Converter converter = (Converter) s_converters.get(clacc); + + Assert.exists(converter, Converter.class); + + return converter; + } + + /** + * Registers converter for clacc. + * + * @param clacc The Class of the parameter value; it + * cannot be null + * @param converter The Converter to register to + * clacc; it cannot be null + */ + public static final void set(final Class clacc, final Converter converter) { + if (Assert.isEnabled()) { + Assert.exists(clacc, Class.class); + Assert.exists(converter, Converter.class); + } + + s_converters.put(clacc, converter); + } + + /** + * Converts value using the converter registered for + * clacc. + * + * @param clacc The Class of the parameter value; it + * cannot be null + * @param value The String-encoded value of the + * parameter; it may be null + * @return The Java object conversion for value; it + * may be null + */ + public static final Object convert(final Class clacc, final String value) { + Assert.exists(clacc, Class.class); + + return get(clacc).convert(clacc, value); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/EmailParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/EmailParameter.java new file mode 100644 index 000000000..eaa3720d1 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/EmailParameter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import org.apache.oro.text.perl.Perl5Util; + +/** + * Subject to change. + * + * A parameter representing an InternetAddress. + * + * @see javax.mail.internet.InternetAddress + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class EmailParameter extends StringParameter { + + private static final Perl5Util s_perl = new Perl5Util(); + private static final String s_regex = + "/^[^@<>\"\t ]+@[^@<>\".\t]+([.][^@<>\".\n ]+)+$/"; + + public EmailParameter(final String name) { + super(name); + } + + protected Object unmarshal(final String value, final ErrorList errors) { + try { + return new InternetAddress(value); + } catch (AddressException ae) { + errors.add(new ParameterError(this, ae)); + return null; + } + } + + protected void doValidate(final Object value, final ErrorList errors) { + super.doValidate(value, errors); + + final InternetAddress email = (InternetAddress) value; + + if (!s_perl.match(s_regex, email.toString())) { + final ParameterError error = new ParameterError + (this, "The value is not a valid email address"); + + errors.add(error); + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/EnumerationParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/EnumerationParameter.java new file mode 100644 index 000000000..1483a8e0b --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/EnumerationParameter.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import java.util.HashMap; +import org.apache.log4j.Logger; + +/** + * Subject to change. + * + * A parameter that maps keys to values and, given a key, marshals or + * unmarshals to the corresponding value. + * + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class EnumerationParameter extends AbstractParameter { + + private static final Logger s_log = Logger.getLogger + (EnumerationParameter.class); + + private final HashMap m_entries; + private final HashMap m_reverse; + + public EnumerationParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt); + + m_entries = new HashMap(); + m_reverse = new HashMap(); + } + + public EnumerationParameter(final String name) { + this(name, Parameter.REQUIRED, null); + } + + public final void put(final String name, final Object value) { + if (m_entries.containsKey(name)) { + throw new IllegalArgumentException + ("name already has a value: " + name); + } + if (m_reverse.containsKey(value)) { + throw new IllegalArgumentException + ("value already has a name: " + value); + } + m_entries.put(name, value); + m_reverse.put(value, name); + } + + protected Object unmarshal(final String value, final ErrorList errors) { + if (m_entries.containsKey(value)) { + return m_entries.get(value); + } else { + final ParameterError error = new ParameterError + (this, "The value must be one of " + m_entries.keySet()); + + errors.add(error); + + return null; + } + } + + protected String marshal(Object value) { + return (String) m_reverse.get(value); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ErrorList.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ErrorList.java new file mode 100644 index 000000000..923366674 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ErrorList.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Assert; +import com.arsdigita.util.UncheckedWrapperException; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Iterator; +import org.apache.log4j.Logger; + +/** + * Subject to change. + * + * A collection to store ParameterErrors that are + * encountered during parameter reading or validation. This + * collection is used in the lifecycle methods of + * Parameter. It is ordinarily returned to the + * parameter-using code so that it can handle errors. + * + * @see ParameterError + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public final class ErrorList { + + private static final Logger s_log = Logger.getLogger(ErrorList.class); + + private final ArrayList m_params; + + // XXX temporarily package access + final ArrayList m_errors; + + /** + * Constructs a new error list. + */ + public ErrorList() { + m_params = new ArrayList(); + m_errors = new ArrayList(); + } + + /** + * Adds error to the error list. + * + * @param error A ParameterError representing a read + * or validation error; it cannot be null + */ + public final void add(final ParameterError error) { + Assert.exists(error, ParameterError.class); + + final Parameter param = error.getParameter(); + + synchronized (m_params) { + if (!m_params.contains(param)) { + m_params.add(param); + } + } + + m_errors.add(error); + } + + /** + * Gets an iterator over the currently stored errors. + * + * @see ParameterError + * @return An Iterator of + * ParameterErrors; it cannot be null + */ + public final Iterator iterator() { + return m_errors.iterator(); + } + + /** + * Tells whether the error collection is empty or not. + * + * @return true if the collection is empty, otherwise + * false + */ + public final boolean isEmpty() { + return m_errors.isEmpty(); + } + + /** + * Throws a ParameterException containing the error + * list. This method is for use when the client code wants the + * program to fail via an exception if there are errors. + * + * @throws ParameterException if the error list is not empty + */ + public final void check() throws ParameterException { + if (!isEmpty()) { + final StringWriter writer = new StringWriter(); + report(writer); + s_log.error(writer.toString()); + + throw new ParameterException + ("Errors encountered while reading parameters", this); + } + } + + /** + * Prints parameter errors to out with formatting + * appropriate to the console. + * + * @param out The Writer to print the errors to + */ + public final void report(final Writer out) { + try { + Assert.exists(out, PrintWriter.class); + + final Iterator params = m_params.iterator(); + + while (params.hasNext()) { + final Parameter param = (Parameter) params.next(); + + out.write("Parameter " + param.getName() + " has the " + + "following errors:\n"); + + final Iterator errors = m_errors.iterator(); + + while (errors.hasNext()) { + final ParameterError error = + (ParameterError) errors.next(); + + if (error.getParameter().equals(param)) { + out.write("\t" + error.getMessage() + "\n"); + } + } + } + + out.flush(); + } catch (IOException ioe) { + throw new UncheckedWrapperException(ioe); + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/FileParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/FileParameter.java new file mode 100644 index 000000000..979e2addc --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/FileParameter.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import java.io.File; + +import org.apache.log4j.Logger; + +/** + * A Parameter representing a File + * + * @see Parameter + * @see java.io.File + * @author bche + */ +public class FileParameter extends AbstractParameter { + private static final Logger s_log = Logger.getLogger(FileParameter.class); + + public FileParameter(final String name) { + super(name, File.class); + } + + public Object unmarshal(final String value, final ErrorList errors) { + final String sPath = value; + File file = new File(sPath); + if (file.exists()) { + return file; + } else { + return null; + } + } + + public String marshal(final Object value) { + final File file = (File) value; + if (file == null) { + return null; + } else { + return file.getAbsolutePath(); + } + } + + public void doValidate(final Object value, final ErrorList errors) { + final File file = (File) value; + if (!file.exists()) { + errors.add( + new ParameterError( + this, + "File " + file.getAbsolutePath() + " does not exist")); + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/IntegerParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/IntegerParameter.java new file mode 100644 index 000000000..46a11ff07 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/IntegerParameter.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import org.apache.commons.beanutils.converters.IntegerConverter; +import org.apache.log4j.Logger; + +/** + * A parameter representing a Java Integer. + * + * Subject to change. + * + * @see java.lang.Integer + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class IntegerParameter extends AbstractParameter { + + private final static Logger logger = Logger.getLogger(IntegerParameter.class); + + static { + logger.debug("Static initalizer starting..."); + Converters.set(Integer.class, new IntegerConverter()); + logger.debug("Static initalizer finished."); + } + + public IntegerParameter(final String name) { + super(name, Integer.class); + } + + public IntegerParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt, Integer.class); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/MapParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/MapParameter.java new file mode 100644 index 000000000..6972cd59b --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/MapParameter.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Assert; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +/** + * A parameter that manages a collection of Parameter to + * Object value mappings. + * + * Subject to change. + * + * @see java.util.Map + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class MapParameter extends AbstractParameter { + + private final ArrayList m_params; + + public MapParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt, String.class); + + m_params = new ArrayList(); + } + + public MapParameter(final String name) { + super(name, String.class); + + m_params = new ArrayList(); + } + + public final void add(final Parameter param) { + Assert.exists(param, Parameter.class); + + m_params.add(param); + } + + public final boolean contains(final Parameter param) { + Assert.exists(param, Parameter.class); + + return m_params.contains(param); + } + + public final Iterator iterator() { + return m_params.iterator(); + } + + protected Object doRead(final ParameterReader reader, + final ErrorList errors) { + final HashMap map = new HashMap(); + final Iterator params = m_params.iterator(); + + while (params.hasNext()) { + final Parameter param = (Parameter) params.next(); + final Object value = param.read(reader, errors); + + if (value != null) { + map.put(param, value); + } + } + + return map; + } + + protected void doValidate(final Object value, final ErrorList errors) { + final HashMap map = (HashMap) value; + final Iterator params = m_params.iterator(); + + while (params.hasNext()) { + final Parameter param = (Parameter) params.next(); + + if (map.containsKey(param)) { + param.validate(map.get(param), errors); + } else { + param.validate(param.getDefaultValue(), errors); + } + } + } + + protected void doWrite(final ParameterWriter writer, final Object value) { + final HashMap map = (HashMap) value; + final Iterator params = m_params.iterator(); + + while (params.hasNext()) { + final Parameter param = (Parameter) params.next(); + + if (map.containsKey(param)) { + param.write(writer, map.get(param)); + } + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/Parameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/Parameter.java new file mode 100644 index 000000000..e94f5a4d6 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/Parameter.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +/** + * Describes a named property that can read, write, and validate its + * own value. + * + * Subject to change. + * + * See the documentation on {@link #read}, {@link #write}, and {@link #validate} + * for details. + + * They have the following features as well: + * + *
      + *
    • Multiplicity. A parameter can be nullable (0..x) or required + * (1..x) and singular (x..1) or multiple (x..n). The current + * parameter implementation only models nullablel vs. required + * parameters.
    • + * + *
    • Defaulting. A parameter can have a value to fall back on if + * none is set.
    • + * + *
    • Optional metadata. Optional extra "info" can be associated + * with a parameter.
    • + *
    + * + * The read and validate phases of a parameter collect errors into a + * list so that calling code can control error handling. This is in + * lieu of throwing exceptions that are not useful in creating + * error-recovery UIs. + * + * In contrast, the write phase of a parameter is expected to complete + * successfully or fail outright. + * + * Parameters are stateless "messages". They do not store their own + * values. Instead, a {@link com.arsdigita.util.parameter.ParameterContext} + * manages a set of parameters and keeps their values. + * + * + * Here's what it typically looks like to use a parameter: + * + *
    + * Properties props = System.getProperties(); + * ParameterReader reader = JavaPropertyReader(props); + * ParameterWriter writer = JavaPropertyWriter(props); + * ErrorList errors = new ErrorList(); + * + * Object value = param.read(reader, errors); + * errors.check(); // If errors is not empty, fails + * + * param.validate(value, errors); + * errors.check(); // If errors is not empty, fails + * + * // We now have a valid unmarshaled value, so code of actual use can + * // go here. + * + * param.write(writer, value); + *
    + * + * @see com.arsdigita.util.parameter.AbstractParameter + * @see com.arsdigita.util.parameter.ParameterContext + * @author Rafael H. Schloming <rhs@mit.edu> + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public interface Parameter { + + /** + * Flag to indicate the parameter value is nullable. + */ + public static final int OPTIONAL = 0; + + /** + * Flag to indicate the parameter value cannot be null. + */ + public static final int REQUIRED = 1; + + /** + * Tells wether the parameter is nullable or not. + * + * @return true if the parameter cannot be null; false if it can + * be null + */ + boolean isRequired(); + + /** + * Gets the name of the parameter. + * + * @return The String parameter name; it cannot be + * null + */ + String getName(); + + /** + * Gets the default value of the parameter. Implementations may + * choose to substitute this value for null. + * + * @return The fallback value; it may be null + */ + Object getDefaultValue(); + + /** + * Gets metadata associated with the parameter if it is available. + * + * @return The ParameterInfo object; it may be null + */ + ParameterInfo getInfo(); + + /** + * Sets the optional parameter metadata to info. + * + * @param info The ParameterInfo to associate; it may + * be null + */ + void setInfo(ParameterInfo info); + + /** + * Gets the parameter value as a Java object. The value will have + * a specific runtime type and so may be appropriately cast. + * + * Reading typically follows the following procedure: + * + *
      + *
    • Read the literal string value associated with the + * parameter from reader
    • + * + *
    • Convert the literal string value into an approprite Java + * object
    • + *
    + * + * If at any point in the process an error is encountered, it is + * added to errors. Callers of this method will + * typically construct an ErrorList in which to + * collect errors. + * + * @param reader The ParameterReader from which to + * recover a string literal value; it cannot be null + * @param errors The ErrorList in which to collect + * any errors encountered; it cannot be null + * @return The Java object value of the parameter + */ + Object read(ParameterReader reader, ErrorList errors); + + /** + * Validates the parameter value, value. Any + * validation errors encountered are added to errors. + * + * @param value The value to validate; this is typically the value + * returned by {@link #read}; it may be null + * @param errors The ErrorList in which to collect + * any errors encountered; it cannot be null + */ + void validate(Object value, ErrorList errors); + + /** + * Writes the parameter value as a string literal. The parameter + * marshals the object value to a string and sends it + * to writer. + * + * @param writer The ParameterWriter that will take + * the marshaled value and store it; it cannot be null + * @param value The Java object value of the parameter + */ + void write(ParameterWriter writer, Object value); +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterContext.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterContext.java new file mode 100644 index 000000000..9fdf4b020 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterContext.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +/** + * A container of parameters. + * + * A parameter context binds together a set of parameters and keeps their values. + * + * + * Subject to change. + * + * @see com.arsdigita.util.parameter.Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public interface ParameterContext { + + /** + * Returns all the parameters registered on the parameter context. + * + * @return A Parameter[] of all the parameters; it + * cannot be null + */ + Parameter[] getParameters(); + + /** + * Gets the unmarshaled value of param. If the + * loaded value is null, param.getDefaultValue() is + * returned. + * + * @param param The named Parameter whose value to + * retrieve; it cannot be null + * @return The unmarshaled Java object value of param + */ + Object get(Parameter param); + + /** + * Gets the unmarshaled value of param, returning + * dephalt if param's value is null. + * + * @param param The Parameter whose value to + * retrieve; it cannot be null + * @param dephalt The fallback default value; it may be null + * @return The unmarshaled Java object value of param + * or dephalt if the former is null + */ + Object get(Parameter param, Object dephalt); + + /** + * Sets the value of param to value. + * + * @param param The Parameter whose value to set; it + * cannot be null + * @param value The new value of param; it may be + * null + */ + void set(Parameter param, Object value); + + /** + * Reads and unmarshals all values associated with the registered + * parameters from reader. If any errors are + * encountered, they are added to errors. + * + * @param reader The ParameterReader from which to + * fetch the values; it cannot be null + * @param errors The ErrorList that captures any + * errors while loading; it cannot be null + */ + void load(ParameterReader reader, ErrorList errors); + + /** + * Marshals and writes all values associated with the registered + * parameters to writer. + * + * @param writer The ParameterWriter to which values + * are written; it cannot be null + */ + void save(ParameterWriter writer); + + /** + * Validates all values associated with the registered parameters. + * Any errors encountered are added to errors. + * + * @param errors The ErrorList that captures + * validation errors; it cannot be null + */ + void validate(ErrorList errors); +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterError.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterError.java new file mode 100644 index 000000000..bc01d6225 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterError.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Assert; + +/** + * Subject to change. + * + * Information about an error for a parameter. Parameter implementors + * will add ParameterErrors to the passed in + * ErrorList when their parameters encounter error + * conditions. + * + * @see ErrorList + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public final class ParameterError { + + private final Parameter m_param; + private final String m_message; + private Throwable m_throwable; + + /** + * Constructs a parameter error for param. + * + * @param param The Parameter whose value is in + * error; it cannot be null + * @param message A String description of the error + */ + public ParameterError(final Parameter param, + final String message) { + if (Assert.isEnabled()) { + Assert.exists(param, Parameter.class); + Assert.exists(message, String.class); + } + + m_param = param; + m_message = message; + } + + /** + * Constructs a parameter error for param, drawing + * its error message from throwable. + * + * @param param The Parameter whose value is in + * error; it cannot be null + * @param throwable The Throwable for the error; it + * cannot be null + */ + public ParameterError(final Parameter param, + final Throwable throwable) { + this(param, throwable.getMessage()); + + m_throwable = throwable; + } + + /** + * Gets the parameter associated with this error. + * + * @return The Parameter in error; it cannot be null + */ + public final Parameter getParameter() { + return m_param; + } + + /** + * Gets the message associated with this error. + * + * @return The String message for the error; it + * cannot be null + */ + public final String getMessage() { + // XXX this actually can be null, so need to prevent that + return m_message; + } + + /** + * Gets the throwable, if present, that corresponds to the error. + * + * @return The Throwable of this error; it may be + * null + */ + public final Throwable getThrowable() { + return m_throwable; + } + + /** + * Returns a string representation of the error suitable for + * debugging. + * + * @return super.toString() + "," + param.getName() + */ + @Override + public String toString() { + return super.toString() + "," + m_param.getName(); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterException.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterException.java new file mode 100644 index 000000000..651939a2e --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterException.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Assert; +import java.util.List; +import org.apache.log4j.Logger; + +/** + * Subject to change. + * + * An exception to indicate invalid parameter states. This exception should only + * be used when the client code of a parameter opts in to using exceptions + * rather than handling parameter errors itself. See + * {@link com.arsdigita.util.parameter.ErrorList#check()}. + * + * @see com.arsdigita.util.parameter.ErrorList + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public final class ParameterException extends RuntimeException { + + private static final Logger s_log = Logger.getLogger( + ParameterException.class); + private static final long serialVersionUID = 1726920836531266365L; + + private final ErrorList m_errors; + + /** + * Constructs a new parameter exception with the content + * message. + * + * @param message A String describing what's wrong; it cannot + * be null + * @param errors The ErrorList containing the errors that + * prompted this exception; it cannot be null + */ + public ParameterException(final String message, final ErrorList errors) { + super(message); + + if (Assert.isEnabled()) { + Assert.exists(message, String.class); + Assert.exists(errors, List.class); + } + + m_errors = errors; + } + + /** + * Gets the set of errors associated with the exception. + * + * @return The ErrorList of errors; it cannot be null + */ + public final ErrorList getErrors() { + return m_errors; + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterInfo.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterInfo.java new file mode 100644 index 000000000..3c6fd0087 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterInfo.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +/** + * Subject to change. + * + * Metadata for a parameter that is of use for building documentation + * or user interfaces for parameters. The fields are not required and + * thus the methods of this class may return null. + * + * @see Parameter#setInfo(ParameterInfo) + * @see Parameter#getInfo() + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public interface ParameterInfo { + + /** + * Gets the pretty name of the parameter. + * + * @return The String title of the parameter; it may + * be null + */ + String getTitle(); + + /** + * Gets the parameter's reason for being. + * + * @return The String purpose of the parameter; it + * may be null + */ + String getPurpose(); + + /** + * Gets an example value for the parameter. + * + * @return A String example value; it may be null + */ + String getExample(); + + /** + * Gets a format description. + * + * @return A format String; it may be null + */ + String getFormat(); +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterReader.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterReader.java new file mode 100644 index 000000000..4bf435512 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterReader.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +/** + * Reads an encoded string value for a parameter from storage. Any + * errors encountered while reading are added to an error list. + * This class is counterpart to ParameterWriter. + * + * Subject to change. + * + * @see Parameter#write(ParameterWriter, Object) + * @see ErrorList + * @see ParameterWriter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public interface ParameterReader { + + /** + * Reads an encoded String value for + * param from storage. If there are errors, they are + * added to errors. + * + * @param param The Parameter being read; it cannot + * be null + * @param errors The ErrorList that will collect any + * errors; it cannot be null + * @return The marshaled String value for + * param; it may be null + */ + String read(Parameter param, ErrorList errors); +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterWriter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterWriter.java new file mode 100644 index 000000000..6794bfb49 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ParameterWriter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +/** + * Subject to change. + * + * Writes encoded parameter values to storage. Implementors define + * the exact nature of the storage. + * + * @see Parameter#write(ParameterWriter,Object) + * @see ParameterReader + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public interface ParameterWriter { + + /** + * Writes the marshaled value for parameter + * param to storage. + * + * @param param The Parameter that is being written; + * it cannot be null + * @param value The encoded String value to store for + * param; it may be null + */ + void write(Parameter param, String value); +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/ResourceParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/ResourceParameter.java new file mode 100644 index 000000000..ebc166975 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/ResourceParameter.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.URL; +import java.util.Iterator; + +import org.apache.log4j.Logger; + +import com.arsdigita.util.UncheckedWrapperException; + +/** + * A parameter representing a JEE Resource (input stream). + * + * This takes in a path and makes sure that the resource exists either + * as a File or an actual resource. If it does, it returns the + * InputStream for the given Resource. If it does not, and if it is + * required, it logs an error. Otherwise, it returns null. + * + * Development note / CHANGELOG + * Had been deprecated for a while in favour of an URLParameter and a + * application specific resource: protocol extension (c.ad.util.protocol.resource). + * As of version 6.5 reverted to ResourceParameter to avoid non-standard extensions. + * + * @author Justin Ross <jross@redhat.com> + * @author Brett <bprucha@users.sourceforge net> + * @author PBoy <pboy@users.sourceforge net> + * @version $Id$ + */ +public class ResourceParameter extends AbstractParameter { + + private static final Logger s_log = Logger.getLogger(ResourceParameter.class); + + private Object m_default = null; + + public ResourceParameter(final String name) { + super(name, InputStream.class); + } + + public ResourceParameter(final String name, + final int multiplicity, + final Object defaultValue) { + + super(name, multiplicity, defaultValue, InputStream.class); + m_default = defaultValue; + } + + /** + * Get default value and return it as InputStream. + * + * Developers note: + * This makes the trick to use Parameter.java interface rsp AbstractParameter + * for other types of parameter as String. If you don't overwrite this + * method, you will always get a casting error, because the parameter + * returns a string instead of the intended object! + * + * @return default value as InputStream + */ + public Object getDefaultValue() { + + if(m_default instanceof String) { + ErrorList errors = new ErrorList(); + InputStream stream = (InputStream)unmarshal((String)m_default, errors); + + if(!errors.isEmpty()) { + String strErrors = ""; + for(Iterator i = errors.iterator(); i.hasNext(); ) { + ParameterError pe = (ParameterError)i.next(); + strErrors += pe.getMessage() + "\r\n"; + } + throw new UncheckedWrapperException(strErrors); + } + + return stream; + } else + return m_default; + } + + /** + * Unmarshals the encoded string value of the parameter to get the intended + * object type. It tries first to find a file of the specified name in the + * file system. If not successful it uses the classloader to find the file + * in the class path / jar files. + * + * @param value + * @param errors + * @return parameter value as an InputStream + */ + @Override + protected Object unmarshal(String value, final ErrorList errors) { + + // NOTE: + // This implementation will never find the file in the file system. + // The name has to be specified relativ to document root. So we must + // precede value with the context path, e.g. using + // c.ad.runtime.CCMResourceManager as soon as it's implementation is + // fixed / stable (when all modifications of the runtime environment + // are done). + File file = new File(value); + + if (!file.exists()) { + // it is not a standard file so lets try to see if it + // is a resource + if (value.startsWith("/")) { + value = value.substring(1); + } + + ClassLoader cload = Thread.currentThread().getContextClassLoader(); + URL url = cload.getResource(value); + InputStream stream = cload.getResourceAsStream(value); + if (stream == null && isRequired()) { + s_log.error(value + " is not a valid file and is required"); + + final ParameterError error = new ParameterError + (this, "Resource not found"); + errors.add(error); + } + return stream; + } else { + try { + return new FileInputStream(file); + } catch (FileNotFoundException ioe) { + // we know the file exists so this should not + // be an issue + s_log.error(value + " is not a valid file and is required", ioe); + + errors.add(new ParameterError(this, ioe)); + + return null; + } + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/SingletonParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/SingletonParameter.java new file mode 100644 index 000000000..eb6d00d2d --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/SingletonParameter.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import com.arsdigita.util.Classes; +import com.arsdigita.util.UncheckedWrapperException; + +/** + * A parameter representing an instance of a Java class. + * + * Subject to change. + * + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class SingletonParameter extends ClassParameter { + + public SingletonParameter(final String name) { + super(name); + } + + public SingletonParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt); + } + + protected String marshal(Object value) { + return super.marshal(value.getClass()); + } + + protected Object unmarshal(final String value, final ErrorList errors) { + final Class clacc = (Class) super.unmarshal(value, errors); + if(clacc == null) { + return null; + } + + try { + return Classes.newInstance(clacc); + } catch (UncheckedWrapperException uwe) { + errors.add(new ParameterError(this, uwe.getRootCause())); + return null; + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/SpecificClassParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/SpecificClassParameter.java new file mode 100644 index 000000000..b26a220e0 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/SpecificClassParameter.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +/** + * A parameter representing a Java Class which is checked to be + * an implementation of a required class / interface. + * + * Subject to change. + * + * @see java.lang.Class + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class SpecificClassParameter extends ClassParameter { + + + private Class m_requiredClass; + + /** + * Constructor + * @param name + * @param multiplicity + * @param defaultObj + * @param requiredClass + */ + public SpecificClassParameter(final String name, + final int multiplicity, + final Object defaultObj, + final Class requiredClass) { + super(name, multiplicity, defaultObj); + m_requiredClass = requiredClass; + } + + /** + * Unmarshals a string representation of the parameter. + * + * @param value string representation of class, must be value != null + * @param errors + * @return + */ + @Override + protected Object unmarshal(String value, ErrorList errors) { + Class theClass = (Class) super.unmarshal(value,errors); + if (theClass != null) { + if (!m_requiredClass.isAssignableFrom(theClass)) { + errors.add(new ParameterError(this, "class " + value + + " must implement : " + + m_requiredClass.getName())); + } + } + + return theClass; + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/StringArrayParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/StringArrayParameter.java new file mode 100644 index 000000000..ec397cc62 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/StringArrayParameter.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +// import com.arsdigita.util.parameter.StringParameter; +// import com.arsdigita.util.parameter.ErrorList; +import com.arsdigita.util.StringUtils; + +/** + * StringArrayParameter + * + * Usage Example: + *
    + * private static parameter exampleName ;
    + * exampleName = new StringArrayParameter(
    + *                   "com.arsdigita.package.example_name",
    + *                   Parameter.REQUIRED,
    + *                   new String[] {"String Example 01","String Example 02"}
    + *                                       );
    + * 
    + * + * @version $Id$ + */ +public class StringArrayParameter extends StringParameter { + + /** + * + * @param name: String literal + * @param multiplicity Indicator wether required (1) or not (0) (nullable) + * @param defaalt default value + */ + public StringArrayParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt); + + } + + /** + * Converts a String[] object into a literal representation. + * + * @param value + * @return + */ + @Override + protected String marshal(final Object value) { + if (value == null) { + return null; + } else { + return StringUtils.join((String[])value, ','); + } + } + + /** + * + * @param literal + * @param errors + * @return + */ + @Override + protected Object unmarshal(final String literal, + final ErrorList errors) { + final String[] literals = StringUtils.split(literal, ','); + final String[] strings = new String[literals.length]; + + for (int i = 0; i < literals.length; i++) { + final String elem = literals[i]; + + strings[i] = (String) super.unmarshal(elem, errors); + + if (!errors.isEmpty()) { + break; + } + } + return strings; + } + + @Override + protected void doValidate(final Object value, + final ErrorList errors) { + if (value != null) { + final String[] strings = (String[]) value; + + for (int i = 0; i < strings.length; i++) { + super.doValidate(strings[i], errors); + + if (!errors.isEmpty()) { + break; + } + } + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/StringParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/StringParameter.java new file mode 100644 index 000000000..49c7f3f12 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/StringParameter.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import org.apache.commons.beanutils.converters.StringConverter; +import org.apache.log4j.Logger; + +/** + * A parameter representing a Java String. + * + * Subject to change. + * + * @see java.lang.String + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class StringParameter extends AbstractParameter { + + private static final Logger logger = Logger.getLogger(StringParameter.class); + + static { + logger.debug("Static initalizer starting..."); + Converters.set(String.class, new StringConverter()); + logger.debug("Static initalizer finished."); + } + + public StringParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt, String.class); + } + + public StringParameter(final String name) { + super(name, String.class); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/util/parameter/URLParameter.java b/ccm-core/src/main/java/com/arsdigita/util/parameter/URLParameter.java new file mode 100644 index 000000000..5b23eab93 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/util/parameter/URLParameter.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.util.parameter; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Subject to change. + * + * A parameter representing a Java URL. + * + * @see java.net.URL + * @see Parameter + * @author Justin Ross <jross@redhat.com> + * @version $Id$ + */ +public class URLParameter extends StringParameter { + + public URLParameter(final String name) { + super(name); + } + + public URLParameter(final String name, + final int multiplicity, + final Object defaalt) { + super(name, multiplicity, defaalt); + } + + protected Object unmarshal(final String value, final ErrorList errors) { + try { + return new URL(value); + } catch (MalformedURLException mue) { + errors.add(new ParameterError(this, mue)); + return null; + } + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/xml/Element.java b/ccm-core/src/main/java/com/arsdigita/xml/Element.java new file mode 100644 index 000000000..540bbd8fa --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/xml/Element.java @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.xml; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.log4j.Logger; +import com.arsdigita.util.Assert; +import com.arsdigita.util.UncheckedWrapperException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Attr; + +/** + * A wrapper class that implements some functionality of + * org.jdom.Element using org.w3c.dom.Element. + * + * @author Patrick McNeill + * @since ACS 4.5a + * @version $Revision$ $Date$ + * @version $Id$ + */ +public class Element { + + private static final Logger s_log = Logger.getLogger(Element.class.getName()); + protected org.w3c.dom.Element m_element; + /* DOM element that is being wrapped */ + /** + * owner document + */ + private org.w3c.dom.Document m_doc; + + private static ThreadLocal s_localDocument = new ThreadLocal() { + @Override + public Object initialValue() { + try { + DocumentBuilderFactory builder = + DocumentBuilderFactory.newInstance(); + builder.setNamespaceAware(true); + return builder.newDocumentBuilder().newDocument(); + } catch (ParserConfigurationException e) { + s_log.error(e); + throw new UncheckedWrapperException( + "INTERNAL: Could not create thread local DOM document.", + e); + } + } + + }; + + private static org.w3c.dom.Document getDocument() { + return (org.w3c.dom.Document) s_localDocument.get(); + } + +// public org.w3c.dom.Document getOwnerDocument() { +// if (null == m_doc) { +// m_doc = (org.w3c.dom.Document) s_localDocument.get(); +// } +// +// return m_doc; +// } +// public void importElement(final Element element) { +// element.m_element = (org.w3c.dom.Element) this.m_element +// .getOwnerDocument().importNode(element.m_element, +// true); +// } + + public void syncDocs() { + if (m_doc == null) { + m_doc = (org.w3c.dom.Document) s_localDocument.get(); + } + + if (!m_element.getOwnerDocument().equals(m_doc)) { + m_element = (org.w3c.dom.Element) m_doc.importNode(m_element, true); + } + } + + /** + * Protected constructor to set up factories, etc. Does not actually + * create a new element. Used if we are programatically setting the + * m_element field later. + */ + protected Element() { + } + + /** + * Creates a new element with the given name and no assigned namespace. + * + * @param name the name of the element + */ + public Element(String name) { + this(); + Assert.exists(name, String.class); + + m_element = getDocument().createElement(name); + } + + /** + * Creates a new element with the given name, and assigns it to the + * namespace defined at uri. The namespace prefix is + * automatically determined. + * + * @param name the name of the element + * @param uri the URI for the namespace definition + */ + public Element(String name, String uri) { + Assert.exists(name, String.class); + Assert.exists(uri, String.class); + + m_element = getDocument().createElementNS(uri, name); + } + + /** + * Creates a new element and adds it as a child to this + * element. elt.newChildElement("newElt") is + * equivalent to + *
    +     * Element newElt = new Element("newElt");
    +     * elt.addChild(newElt);
    +     * 
    + * + * @param name the name of the element + * @return the created child element. + * @pre m_element != null + */ + public Element newChildElement(String name) { + Assert.exists(name, String.class); + + if (m_doc == null) { + m_doc = this.m_element.getOwnerDocument(); + } + + Element result = new Element(); + result.m_element = m_doc.createElement(name); + this.m_element.appendChild(result.m_element); + return result; + } + + /** + * Creates a new element. Adds it as a child to this element + * element and assigns it to the namespace defined at uri. + * elt.newChildElement("newElt", namespace) is + * equivalent to + *
    +     * Element newElt = new Element("newElt", namespace);
    +     * elt.addChild(newElt);
    +     * 
    + * + * @param name the name of the Element + * @param uri the URI for the namespace definition + * @return the created child element. + * @pre m_element != null + */ + public Element newChildElement(String name, String uri) { + Assert.exists(name, String.class); + Assert.exists(uri, String.class); + + if (m_doc == null) { + m_doc = this.m_element.getOwnerDocument(); + } + + Element result = new Element(); + result.m_element = m_doc.createElementNS(uri, name); + this.m_element.appendChild(result.m_element); + return result; + } + + /** + * Copies the passed in element and all of its children to a new + * Element. + * + * @param copyFrom + * @return + */ + public Element newChildElement(Element copyFrom) { + Assert.exists(copyFrom, Element.class); + + if (m_doc == null) { + m_doc = this.m_element.getOwnerDocument(); + } + + Element copyTo = new Element(); + copyTo.m_element = m_doc.createElementNS(copyFrom.m_element.getNamespaceURI(), copyFrom.getName()); + this.m_element.appendChild(copyTo.m_element); + newChildElementHelper(copyFrom, copyTo); + return copyTo; + } + + /** + * Copies the passed in element and all of its children to a new + * Element using the passed-in name. + * + * @param name + * @param copyFrom + * @return + */ + public Element newChildElement(String name, Element copyFrom) { + if (m_doc == null) { + m_doc = this.m_element.getOwnerDocument(); + } + + Element copyTo = new Element(); + copyTo.m_element = m_doc.createElement(name); + this.m_element.appendChild(copyTo.m_element); + newChildElementHelper(copyFrom, copyTo); + return copyTo; + } + + /** + * Copies the passed in element and all of its children to a new + * Element using the passed-in name. + * + * @param name + * @param uri + * @param copyFrom + * @return + */ + public Element newChildElement(String name, String uri, Element copyFrom) { + if (m_doc == null) { + m_doc = this.m_element.getOwnerDocument(); + } + + Element copyTo = new Element(); + copyTo.m_element = m_doc.createElementNS(uri, name); + this.m_element.appendChild(copyTo.m_element); + newChildElementHelper(copyFrom, copyTo); + return copyTo; + } + + private void newChildElementHelper(Element copyFrom, Element copyTo) { + copyTo.setText(copyFrom.getText()); + + + NamedNodeMap nnm = copyFrom.m_element.getAttributes(); + + if (nnm != null) { + for (int i = 0; i < nnm.getLength(); i++) { + Attr attr = (org.w3c.dom.Attr) nnm.item(i); + copyTo.addAttribute(attr.getName(), attr.getValue()); + } + } + + Iterator iter = copyFrom.getChildren().iterator(); + + while (iter.hasNext()) { + Element child = (Element) iter.next(); + copyTo.newChildElement(child); + } + + } + + /** + * Adds an attribute to the element. + * + * @param name the name of the attribute + * @param value the value of the attribute + * @return this element. + */ + public Element addAttribute(String name, String value) { + Assert.exists(name, String.class); + + m_element.setAttribute(name, value); + + return this; + } + + public Element addAttribute(String name, + String value, + String ns) { + Assert.exists(name, String.class); + Assert.exists(ns, String.class); + + m_element.setAttributeNS(ns, name, value); + + return this; + } + + /** + * Adds a child element to this element. + * + * @param newContent the new child element + * @return this element. + */ + public Element addContent(Element newContent) { + Assert.exists(newContent, Element.class); + + newContent.importInto(m_element.getOwnerDocument()); + m_element.appendChild(newContent.getInternalElement()); + + return this; + } + + /** + * Sets the text value of the current element (the part between the + * tags). If the passed in text is null then it is converted to + * the empty string. + * + * @param text the text to include + * @return this element. + */ + public Element setText(String text) { + if (text == null) { + // This converts the null to the empty string because + // org.w3c.dom does not like null and HTML does not + // differentiate between "" and null. The other option + // is to throw the NPE which causes other problems + text = ""; + } + org.w3c.dom.Text textElem = + m_element.getOwnerDocument().createTextNode(text); + m_element.appendChild(textElem); + + return this; + } + + /** + * Returns the concatenation of all the text in all child nodes + * of the current element. + * + * @return + */ + public String getText() { + StringBuilder result = new StringBuilder(); + + org.w3c.dom.NodeList nl = m_element.getChildNodes(); + + for (int i = 0; i < nl.getLength(); i++) { + org.w3c.dom.Node n = nl.item(i); + + if (n.getNodeType() == org.w3c.dom.Node.TEXT_NODE) { + result.append(((org.w3c.dom.Text) n).getData()); + } + } + + return result.toString(); + } + + public Element setCDATASection(String cdata) { + s_log.debug("Setting CDATA section to '" + cdata + "'."); + + if (cdata == null) { + cdata = ""; + } + + org.w3c.dom.CDATASection cdataSection = + m_element.getOwnerDocument().createCDATASection(cdata); + + m_element.appendChild(cdataSection); + + return this; + } + + public String getCDATASection() { + StringBuilder result = new StringBuilder(); + + org.w3c.dom.NodeList nl = m_element.getChildNodes(); + + for (int i = 0; i < nl.getLength(); i++) { + org.w3c.dom.Node n = nl.item(i); + + if (n.getNodeType() == org.w3c.dom.Node.CDATA_SECTION_NODE) { + result.append(((org.w3c.dom.CDATASection) n).getData()); + } + } + + String str = result.toString(); + + s_log.debug("Fetched this from CDATA section: " + str); + + return str; + } + + /** + * Returns a List of all the child elements nested + * directly (one level deep) within this element, as Element + * objects. If this target element has no nested elements, an empty + * List is returned. The returned list is "live", so + * changes to it affect the element's actual contents. + *

    + * + * This performs no recursion, so elements nested two levels deep would + * have to be obtained with: + *

    +     * Iterator itr = currentElement.getChildren().iterator();
    +     * while (itr.hasNext()) {
    +     *    Element oneLevelDeep = (Element)nestedElements.next();
    +     *    List twoLevelsDeep = oneLevelDeep.getChildren();
    +     *      // Do something with these children
    +     *    }
    +     * 
    + * @return list of child Element objects for this element. + */ + public java.util.List getChildren() { + java.util.List retval = new java.util.ArrayList(); + org.w3c.dom.NodeList nl = m_element.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + org.w3c.dom.Node n = nl.item(i); + if (n instanceof org.w3c.dom.Element) { + Element elt = new Element(); + elt.m_element = (org.w3c.dom.Element) n; + retval.add(elt); + } + } + return retval; + } + + public java.util.Map getAttributes() { + // Retrieve the attributes of the DOM Element + org.w3c.dom.NamedNodeMap attributeNodeMap = + m_element.getAttributes(); + + // Create the HashMap that we will return the attributes + // in + java.util.HashMap returnMap = new java.util.HashMap(); + + // Copy the attribute values in the NamedNodeMap to the + // HashMap + for (int i = 0; i < attributeNodeMap.getLength(); ++i) { + // Get the Node + org.w3c.dom.Node attributeNode = attributeNodeMap.item(i); + // Copy the name and value to the map + returnMap.put(attributeNode.getNodeName(), + attributeNode.getNodeValue()); + } + + // Return the HashMap + return returnMap; + } + + /** + * Retrieves an attribute value by name. + * @param name The name of the attribute to retrieve + * @return The Attr value as a string, + * or the empty string if that attribute does not have a specified + * or default value. + */ + public String getAttribute(String name) { + return m_element.getAttribute(name); + } + + public boolean hasAttribute(String name) { + return m_element.hasAttribute(name); + } + + public String getName() { + return m_element.getTagName(); + } + + /** + * Functions to allow this class to interact appropriately with the + * Document class (for example, allows nodes to be moved around, + * and so on). + * + * @return the internal DOM Element. + */ + protected final org.w3c.dom.Element getInternalElement() { + return m_element; + } + + /** + * Imports the internal node into another document. + * This could also be done with a combination of getInternalElement + * and a setInternalElement function. + * + * @param doc the org.w3c.dom.Document to import into + */ + protected void importInto(org.w3c.dom.Document doc) { + /* + Exception e = new Exception(); + java.io.StringWriter sw = new java.io.StringWriter(); + e.printStackTrace(new java.io.PrintWriter(sw)); + System.out.println(sw.toString().substring(0, 300)); + */ + + m_element = (org.w3c.dom.Element) doc.importNode(m_element, true); + } + + /** + * Workaround for bug in some versions of Xerces. + * For some reason, importNode doesn't also copy attribute + * values unless you call getValue() on them first. This may + * be fixed in a later version of Xerces. In the meantime, + * calling visitAllAttributes(node) before importNode should + * help. + * + * @param node the org.w3c.dom.Node about to be imported + * @deprecated with no replacement, 1 May 2003 + */ + public static void visitAllAttributes(org.w3c.dom.Node node) { + org.w3c.dom.NamedNodeMap nnm = node.getAttributes(); + if (nnm != null) { + for (int i = 0; i < nnm.getLength(); i++) { + org.w3c.dom.Attr attr = (org.w3c.dom.Attr) nnm.item(i); + attr.getValue(); + } + } + org.w3c.dom.NodeList nl = node.getChildNodes(); + if (nl != null) { + for (int i = 0; i < nl.getLength(); i++) { + visitAllAttributes(nl.item(i)); + } + } + } + + /** + * retrieve an unordered list of strings relating to node tree including + * and below the current element. Strings include element names, attribute names, + * attribute values, text and CData sections + * @return + */ + private List getXMLFragments() { + + List unsortedList = new ArrayList(); + unsortedList.add(getName()); + unsortedList.add(getText()); + // CData sections are not included in getChildren() + unsortedList.add(getCDATASection()); + Iterator it = getAttributes().entrySet().iterator(); + while (it.hasNext()) { + java.util.Map.Entry entry = (java.util.Map.Entry) it.next(); + unsortedList.add(entry.getKey()); + unsortedList.add(entry.getValue()); + } + Iterator childElements = getChildren().iterator(); + while (childElements.hasNext()) { + Element el = (Element) childElements.next(); + unsortedList.addAll(el.getXMLFragments()); + } + return unsortedList; + + } + + /** + * retrieve a string that is an ordered concatenation of all information describing + * this node and its subnodes, suitable as the basis of a hashCode or equals + * implementation. + * @return + */ + protected String getXMLHashString() { + // attributes and child nodes are retrieved as HashMap and List + // respectively. These make no guarantees about the order of + // iteration, and so we sort here to make sure the same element + // will return the same XMLHash + List sortedList = getXMLFragments(); + Collections.sort(sortedList); + StringBuilder xml = new StringBuilder(); + Iterator xmlFragments = sortedList.iterator(); + while (xmlFragments.hasNext()) { + xml.append(xmlFragments.next()); + } + s_log.debug("getXMLHashString: " + xml.toString()); + return xml.toString(); + } + + @Override + public int hashCode() { + Date start = new Date(); + String hashString = getXMLHashString(); + s_log.debug( + "hashCode: getXMLString took " + + (new Date().getTime() - start.getTime()) + + " millisecs"); + + return hashString.hashCode(); + + } + + @Override + public boolean equals(Object other) { + s_log.debug("equals invoked"); + Date start = new Date(); + if (other == null) { + return false; + } + if (!other.getClass().equals(Element.class)) { + return false; + } + Element otherElement = (Element) other; + String thisXML = getXMLHashString(); + String otherXML = otherElement.getXMLHashString(); + s_log.debug( + "Equals: getXMLString twice took " + + (new Date().getTime() - start.getTime()) + + " millisecs"); + return thisXML.equals(otherXML); + + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/xml/Formatter.java b/ccm-core/src/main/java/com/arsdigita/xml/Formatter.java new file mode 100644 index 000000000..2876190fa --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/xml/Formatter.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.xml; + +/** + * An interface providing an API for converting an object + * to a string. Thus instead of doing + *
    + *  Date today = new Date();
    + *  element.addAttribute("today", date.toString());
    + * 
    + * we can do: + *
    + *  Date today = new Date();
    + *  element.addAttribute("today", XML.format(date));
    + * 
    + * Or if you require a non-default format: + *
    + *  Date today = new Date();
    + *  element.addAttribute("today", new DateTimeFormatter.format(today));
    + * 
    + */ +public interface Formatter { + String format(Object value); +} diff --git a/ccm-core/src/main/java/com/arsdigita/xml/XML.java b/ccm-core/src/main/java/com/arsdigita/xml/XML.java new file mode 100644 index 000000000..8d049781c --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/xml/XML.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.xml; + +import com.arsdigita.util.UncheckedWrapperException; + +import com.arsdigita.xml.formatters.DateTimeFormatter; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + + +import org.apache.log4j.Logger; + +/** + * Provides a set of static helper methods for dealing with XML, + * including file parsing & object -> string serialization + */ +public class XML { + + private static final Logger s_log = Logger.getLogger(XML.class); + + // private static XMLConfig s_config; + + private static final Map s_formatters = new HashMap(); + static { + s_log.debug("Static initalizer starting..."); + s_formatters.put(Date.class, new DateTimeFormatter()); + s_log.debug("Static initalizer finished."); + } + + /** + * Constructor. All methods are static, no initialization required. + */ + private XML() {} + + /** + * Retrieves the current configuration + public static XMLConfig getConfig() { + if (s_config == null) { + s_config = new XMLConfig(); + s_config.load(); + } + return s_config; + } + */ + + /** + * Registers a formatter for serializing objects of a + * class to a String suitable for XML output. + * @param klass + * @param formatter + */ + public static void registerFormatter(Class klass, + Formatter formatter) { + s_formatters.put(klass, formatter); + } + + /** + * Unregisters a formatter against a class. + * @param klass + */ + public static void unregisterFormatter(Class klass) { + s_formatters.remove(klass); + } + + /** + * Gets a directly registered formatter for a class. + * @param klass the class to find a formatter for + * @return the formatter, or null if non is registered + */ + public static Formatter getFormatter(Class klass) { + return (Formatter)s_formatters.get(klass); + } + + /** + * Looks for the best matching formatter. + * + * @param klass the class to find a formatter for + * @return the formatter, or null if non is registered + */ + public static Formatter findFormatter(Class klass) { + Formatter formatter = null; + while (formatter == null && klass != null) { + formatter = getFormatter(klass); + klass = klass.getSuperclass(); + } + return formatter; + } + + /** + * Converts an object to a String using the closest + * matching registered Formatter implementation. Looks + * for a formatter registered against the object's + * class first, then its superclass, etc. If no formatter + * is found, uses the toString() method. + * + * @param value + * @return + */ + public static String format(Object value) { + if (value == null) { + return null; + } + + if (value instanceof String) { + return (String)value; + } + + Formatter formatter = findFormatter(value.getClass()); + if (formatter == null) { + if (s_log.isDebugEnabled()) { + s_log.debug("No formatter for " + value.getClass()); + } + return value.toString(); + } + if (s_log.isDebugEnabled()) { + s_log.debug("Processing " + value.getClass() + + " with " + formatter.getClass()); + } + return formatter.format(value); + } + + /** + * Processes an XML file with the default SAX Parser, with + * namespace processing, schema validation & DTD validation + * enabled. + * + * @param path the XML file relative to the webapp root + * @param handler the content handler + */ + public static final void parseResource(String path, + DefaultHandler handler) { + if (s_log.isDebugEnabled()) { + s_log.debug("Processing resource " + path + + " with " + handler.getClass()); + } + + if (path.startsWith("/")) { + path = path.substring(1); + } + + ClassLoader cload = Thread.currentThread().getContextClassLoader(); + InputStream stream = cload.getResourceAsStream(path); + + if (stream == null) { + throw new IllegalArgumentException("no such resource: " + path); + } + + parse(stream, handler); + } + + /** + * Processes an XML file with the default SAX Parser, with + * namespace processing, schema validation & DTD validation + * enabled. + * + * @param source the xml input stream + * @param handler the content handler + */ + public static final void parse(InputStream source, + DefaultHandler handler) { + if (s_log.isDebugEnabled()) { + s_log.debug("Processing stream " + source + + " with " + handler.getClass()); + } + + try { + // ToDo (pboy): We should use + // SAXParserFactory.newInstance(String clName, ClassLoader clLoader) + // instead to achieve independence of a JVM wide acceptable + // configuration (affecting all CCM instances which may run in a + // container). + // Requires additional modifications in c.ad.util.xml.XML + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature("http://xml.org/sax/features/namespaces", true); + SAXParser parser = spf.newSAXParser(); + parser.parse(source, handler); + } catch (ParserConfigurationException e) { + throw new UncheckedWrapperException("error parsing stream", e); + } catch (SAXException e) { + if (e.getException() != null) { + throw new UncheckedWrapperException("error parsing stream", + e.getException()); + } else { + throw new UncheckedWrapperException("error parsing stream", e); + } + } catch (IOException e) { + throw new UncheckedWrapperException("error parsing stream", e); + } + } + + /** + * This visitor is called by {@link #traverse(Element, int, XML.Action)}. + **/ + public interface Action { + void apply(Element elem, int level); + } + + /** + * Prints the skeleton structure of the element to the supplied print + * writer. + * @param element + * @param writer + **/ + public static void toSkeleton(final Element element, + final PrintWriter writer) { + + XML.traverse(element, 0, new Action() { + @Override + public void apply(Element elem, int level) { + final String padding = " "; + for (int ii=0; ii + */ +public class DateFormatter implements Formatter { + + private static DateFormatterConfig m_config; + + public static final DateFormatterConfig getConfig() { + if (m_config == null) { + m_config = new DateFormatterConfig(); + m_config.load(); + } + return m_config; + } + + @Override + public String format(Object value) { + Date date = (Date) value; + + //Locale locale = GlobalizationHelper.getNegotiatedLocale(); + final Locale locale = Locale.getDefault(); + + DateFormat format = DateFormat + .getDateInstance(DateFormat.MEDIUM, locale); + + return format.format(date); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatterConfig.java b/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatterConfig.java new file mode 100755 index 000000000..911f48c0d --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatterConfig.java @@ -0,0 +1,21 @@ +package com.arsdigita.xml.formatters; + + +import com.arsdigita.runtime.AbstractConfig; +import com.arsdigita.util.parameter.Parameter; +import com.arsdigita.util.parameter.StringParameter; + +public final class DateFormatterConfig extends AbstractConfig { + + private final Parameter m_locale; + + public DateFormatterConfig() { + m_locale = new StringParameter("waf.xml.formatters.locale", Parameter.OPTIONAL, null); + register(m_locale); + loadInfo(); + } + + public final String getLocale() { + return (String) get (m_locale); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatterConfig_parameter.properties b/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatterConfig_parameter.properties new file mode 100755 index 000000000..7c987b31a --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateFormatterConfig_parameter.properties @@ -0,0 +1,4 @@ +waf.xml.formatters.locale.title=Locale language code (see http://ftp.ics.uci.edu/pub/ietf/http/related/iso639.txt) +waf.xml.formatters.locale.purpose=If set will use this rather than the contexts locale. Useful for things that may be formatted differently in other locales, eg dates. +waf.xml.formatters.locale.example=en +waf.xml.formatters.locale.format=[string] \ No newline at end of file diff --git a/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateTimeFormatter.java b/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateTimeFormatter.java new file mode 100755 index 000000000..17846aba0 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/xml/formatters/DateTimeFormatter.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.xml.formatters; + +//import com.arsdigita.globalization.GlobalizationHelper; +import com.arsdigita.xml.Formatter; +import java.util.Locale; +import java.util.Date; +import java.text.DateFormat; + +/** + * The default formatter for java.util.Date objects, outputing the date in + * 'medium' format and the time in 'short' format. + * + * @author unknown + * @author Sören Bernstein + */ +public class DateTimeFormatter implements Formatter { + + @Override + public String format(Object value) { + Date date = (Date) value; + //Locale locale = GlobalizationHelper.getNegotiatedLocale(); + final Locale locale = Locale.getDefault(); + + DateFormat format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, + DateFormat.SHORT, locale); + + return format.format(date); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/xml/formatters/FullDateFormatter.java b/ccm-core/src/main/java/com/arsdigita/xml/formatters/FullDateFormatter.java new file mode 100755 index 000000000..5c11c4f85 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/xml/formatters/FullDateFormatter.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2002-2005 Runtime Collective Ltd. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package com.arsdigita.xml.formatters; + +//import com.arsdigita.kernel.Kernel; +import java.util.Calendar; +import java.util.Date; + +/** + * A DateFormatter which displays dates as: + *
    + * <yearNo> | <monthNo> | <dayOfMonthNo> | <dayOfWeekNo> + * | <hour> | <minute> | <second> | <apm> | <localised date> + *
    + * the numbers are padded with 0s, so the positions of the fields are always + * 0, 7, 12, 17, 21, 26, 31, 36, 41 (in Java), and 1, 8, 13, 18, 22, 27, 32, 37, 42 (in XSL). + */ +public class FullDateFormatter extends DateFormatter { + + public static String SEPARATOR = " | "; + public static String AM = "am"; + public static String PM = "pm"; + public static char ZERO = '0'; + + public String format(Object value) { + + String parentResult = super.format(value); + +// if (!XMLConfig.getConfig().getActivateFullTimeFormatter()) { +// return parentResult; +// } + + Date date = (Date) value; + Calendar cal = Calendar.getInstance(); + StringBuffer result = new StringBuffer(60); + + cal.setTime(date); + + append(result, cal.get(Calendar.YEAR)); + appendMaybeSmall(result, cal.get(Calendar.MONTH)); + appendMaybeSmall(result, cal.get(Calendar.DAY_OF_MONTH)); + append(result, cal.get(Calendar.DAY_OF_WEEK)); + appendMaybeSmall(result, cal.get(Calendar.HOUR)); + appendMaybeSmall(result, cal.get(Calendar.MINUTE)); + appendMaybeSmall(result, cal.get(Calendar.SECOND)); + + switch (cal.get(Calendar.AM_PM)) { + case Calendar.AM: result.append(AM); break; + case Calendar.PM: result.append(PM); break; + } + + result.append(SEPARATOR) + .append(parentResult); + + return result.toString(); + } + + public void appendMaybeSmall(StringBuffer sb, int value) { + if (value < 10) { + sb.append(ZERO); + } + append(sb, value); + } + + public void append(StringBuffer sb, int value) { + sb.append(value) + .append(SEPARATOR); + } +} diff --git a/ccm-core/src/main/java/com/arsdigita/xml/formatters/TimeFormatter.java b/ccm-core/src/main/java/com/arsdigita/xml/formatters/TimeFormatter.java new file mode 100755 index 000000000..edddc81bc --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/xml/formatters/TimeFormatter.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.xml.formatters; + +import com.arsdigita.xml.Formatter; +import java.util.Locale; +import java.util.Date; +import java.text.DateFormat; + +/** + * An alternate formatter for java.util.Date objects, + * outputing the date in 'medium' format. The time + * is ommitted. + * + * @author unknown + * @author Sören Bernstein + */ +public class TimeFormatter implements Formatter { + + @Override + public String format(Object value) { + Date date = (Date) value; + +// Locale locale = GlobalizationHelper.getNegotiatedLocale(); + final Locale locale = Locale.getDefault(); + + DateFormat format = DateFormat.getTimeInstance(DateFormat.SHORT, locale); + + return format.format(date); + } +} diff --git a/ccm-core/src/main/java/org/libreccm/core/CcmSessionContext.java b/ccm-core/src/main/java/org/libreccm/core/CcmSessionContext.java index 4cc846d27..ae038981b 100644 --- a/ccm-core/src/main/java/org/libreccm/core/CcmSessionContext.java +++ b/ccm-core/src/main/java/org/libreccm/core/CcmSessionContext.java @@ -33,23 +33,23 @@ public class CcmSessionContext implements Serializable { private static final long serialVersionUID = 6110177865273823685L; - private Party currentParty; - private Party effectiveParty; + private Subject currentParty; + private Subject effectiveParty; - public Party getCurrentParty() { + public Subject getCurrentParty() { return currentParty; } - public void setCurrentParty(final Party currentParty) { + public void setCurrentParty(final Subject currentParty) { this.currentParty = currentParty; this.effectiveParty = currentParty; } - public Party getEffectiveParty() { + public Subject getEffectiveParty() { return effectiveParty; } - protected void setEffectiveParty(final Party effectiveParty) { + protected void setEffectiveParty(final Subject effectiveParty) { this.effectiveParty = effectiveParty; } @@ -63,7 +63,7 @@ public class CcmSessionContext implements Serializable { * @param party The party with which permissions the code is executed. * @param runnable The code to execute. */ - public void sudo(final Party party, final Runnable runnable) { + public void sudo(final Subject party, final Runnable runnable) { //ToDo: Check if current user is permitted to use sudo. effectiveParty = party; diff --git a/ccm-core/src/main/java/org/libreccm/core/Group.java b/ccm-core/src/main/java/org/libreccm/core/Group.java index 7e50b6c32..571a9ab1e 100644 --- a/ccm-core/src/main/java/org/libreccm/core/Group.java +++ b/ccm-core/src/main/java/org/libreccm/core/Group.java @@ -45,7 +45,7 @@ import javax.xml.bind.annotation.XmlRootElement; @Entity @Table(name = "ccm_groups") @XmlRootElement(name = "user-group", namespace = CORE_XML_NS) -public class Group extends Party implements Serializable { +public class Group extends Subject implements Serializable { private static final long serialVersionUID = -5555063356689597270L; diff --git a/ccm-core/src/main/java/org/libreccm/core/Permission.java b/ccm-core/src/main/java/org/libreccm/core/Permission.java index 816dcae00..c0cd4171d 100644 --- a/ccm-core/src/main/java/org/libreccm/core/Permission.java +++ b/ccm-core/src/main/java/org/libreccm/core/Permission.java @@ -61,7 +61,7 @@ public class Permission implements Serializable { @ManyToOne @JoinColumn(name = "grantee_id") - private Party grantee; + private Subject grantee; @OneToOne @JoinColumn(name = "granted_privilege_id") @@ -94,11 +94,11 @@ public class Permission implements Serializable { this.permissionId = permissionId; } - public Party getGrantee() { + public Subject getGrantee() { return grantee; } - protected void setGrantee(final Party grantee) { + protected void setGrantee(final Subject grantee) { this.grantee = grantee; } diff --git a/ccm-core/src/main/java/org/libreccm/core/Party.java b/ccm-core/src/main/java/org/libreccm/core/Subject.java similarity index 72% rename from ccm-core/src/main/java/org/libreccm/core/Party.java rename to ccm-core/src/main/java/org/libreccm/core/Subject.java index 2cf01ab52..d9c8125d0 100644 --- a/ccm-core/src/main/java/org/libreccm/core/Party.java +++ b/ccm-core/src/main/java/org/libreccm/core/Subject.java @@ -28,8 +28,12 @@ import java.util.List; import java.util.Objects; import javax.persistence.CollectionTable; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; @@ -44,16 +48,21 @@ import javax.xml.bind.annotation.XmlRootElement; * @author Jens Pelzetter */ @Entity -@Table(name = "parties") -@XmlRootElement(name = "party", namespace = CORE_XML_NS) -public class Party extends CcmObject implements Serializable { +@Table(name = "subjects") +@XmlRootElement(name = "subject", namespace = CORE_XML_NS) +public class Subject implements Serializable { private static final long serialVersionUID = 6303836654273293979L; + @Id + @Column(name = "subject_id") + @GeneratedValue(strategy = GenerationType.AUTO) + private long subjectId; + @ElementCollection - @CollectionTable(name = "party_email_addresses", + @CollectionTable(name = "subject_email_addresses", joinColumns = { - @JoinColumn(name = "party_id")}) + @JoinColumn(name = "subject_id")}) @Size(min = 1) @XmlElementWrapper(name = "email-addresses", namespace = CORE_XML_NS) @XmlElement(name = "email-address", namespace = CORE_XML_NS) @@ -66,12 +75,20 @@ public class Party extends CcmObject implements Serializable { @XmlElement(name = "granted-permission", namespace = CORE_XML_NS) private List grantedPermissions; - public Party() { + public Subject() { super(); grantedPermissions = new ArrayList<>(); } + public long getSubjectId() { + return subjectId; + } + + protected void setSubjectId(final long subjectId) { + this.subjectId = subjectId; + } + public List getEmailAddresses() { if (emailAddresses == null) { return null; @@ -113,24 +130,25 @@ public class Party extends CcmObject implements Serializable { @Override public int hashCode() { - int hash = super.hashCode(); - hash = 41 * hash + Objects.hashCode(emailAddresses); + int hash = 7; + hash = 53 * hash + (int) (subjectId ^ (subjectId >>> 32)); + hash = 53 * hash + Objects.hashCode(emailAddresses); return hash; } @Override public boolean equals(final Object obj) { - if (!super.equals(obj)) { - return false; - } - if (obj == null) { return false; } - if (!(obj instanceof Party)) { + if (!(obj instanceof Subject)) { return false; } - final Party other = (Party) obj; + final Subject other = (Subject) obj; + if (subjectId != other.getSubjectId()) { + return false; + } + if (!other.canEqual(this)) { return false; } @@ -138,9 +156,24 @@ public class Party extends CcmObject implements Serializable { return Objects.equals(emailAddresses, other.getEmailAddresses()); } - @Override public boolean canEqual(final Object obj) { - return obj instanceof Party; + return obj instanceof Subject; + } + + @Override + public final String toString() { + return toString(""); + } + + public String toString(final String data) { + return String.format("%s{ " + + "subjectId = %d, " + + "emailAddresses = %s" + + "%s}", + super.toString(), + subjectId, + Objects.toString(emailAddresses), + data); } } diff --git a/ccm-core/src/main/java/org/libreccm/core/User.java b/ccm-core/src/main/java/org/libreccm/core/User.java index 889625de3..18acb2c8e 100644 --- a/ccm-core/src/main/java/org/libreccm/core/User.java +++ b/ccm-core/src/main/java/org/libreccm/core/User.java @@ -38,6 +38,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; @@ -47,11 +49,17 @@ import javax.xml.bind.annotation.XmlTransient; /** * The {@code User} entity stores the name and the password of a user along with * some other informations. - * + * * @author Jens Pelzetter */ @Entity @Table(name = "ccm_users") +@NamedQueries({ + @NamedQuery(name = "findUserByScreenName", + query = "SELECT u FROM User u WHERE u.screenName = :screenname"), + @NamedQuery(name = "findUserByEmail", + query = "SELECT u FROM User u JOIN u.emailAddresses e" + + "WHERE e.address = :emailAddress")}) @XmlRootElement(name = "user", namespace = CORE_XML_NS) //Supressing a few warnings from PMD because they misleading here. //User is perfectly fine class name, and the complexity is not to high... @@ -59,7 +67,7 @@ import javax.xml.bind.annotation.XmlTransient; "PMD.CyclomaticComplexity", "PMD.StdCyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity"}) -public class User extends Party implements Serializable { +public class User extends Subject implements Serializable { private static final long serialVersionUID = 892038270064849732L; @@ -77,18 +85,18 @@ public class User extends Party implements Serializable { /** * A user name of the user. Usually an abbreviation of the users real name. - * For example a the John Doe might have the scree name - * jdoe. The screen name is used as user name for logins (if - * the system if configured so, otherwise the email address of the user is + * For example a the John Doe might have the scree name + * jdoe. The screen name is used as user name for logins (if + * the system if configured so, otherwise the email address of the user is * used). */ - @Column(name = "screen_name", length = 255, nullable = false) + @Column(name = "screen_name", length = 255, nullable = false, unique = true) @NotBlank @XmlElement(name = "screen-name", namespace = CORE_XML_NS) private String screenName; /** - * A user can be banned which means that he or she can't login into the + * A user can be banned which means that he or she can't login into the * system anymore. */ @Column(name = "banned") @@ -96,7 +104,8 @@ public class User extends Party implements Serializable { private boolean banned; /** - * An alias for the user used in an another system for SSO, for example LDAP. + * An alias for the user used in an another system for SSO, for example + * LDAP. */ @Column(name = "sso_login", length = 512) @XmlElement(name = "sso-login", namespace = CORE_XML_NS) @@ -117,25 +126,25 @@ public class User extends Party implements Serializable { private String salt; /** - * The hash algorithm used to hash the password. This allows us - * the change to another, stronger hash algorithm without invalidating - * existing accounts. The algorithm to use for new passwords can be - * configured by the administrator. - * + * The hash algorithm used to hash the password. This allows us the change + * to another, stronger hash algorithm without invalidating existing + * accounts. The algorithm to use for new passwords can be configured by the + * administrator. + * */ @Column(name = "hash_algorithm", length = 64) @XmlTransient private String hashAlgorithm; /** - * Indicates that the user should be forced to change his or her password - * on the next login. + * Indicates that the user should be forced to change his or her password on + * the next login. */ @Column(name = "password_reset_required") private boolean passwordResetRequired; /** - * Question the recover a forgotten password. + * Question the recover a forgotten password. */ @Column(name = "password_question", length = 2048) @XmlElement(name = "password-question", namespace = CORE_XML_NS) diff --git a/ccm-core/src/main/java/org/libreccm/core/UserManager.java b/ccm-core/src/main/java/org/libreccm/core/UserManager.java new file mode 100644 index 000000000..3253952fd --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/UserManager.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.core; + + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + +/** + * This class provides complex operations on {@link User} objects like updating + * the password. To use this class add an injection point to your class. + * + * @author Jens Pelzetter + */ +@RequestScoped +public class UserManager { + + /** + * {@link UserRepository} for interacting with the database. The method + * takes care of hashing the password with random salt. + * + */ + @Inject + private transient UserRepository userRepository; + + /** + * Update the password of an user. + * + * @param user The user whose password is to be updated. + * @param password The new password. + */ + public void updatePassword(final User user, final String password) { + + try { + final Random random = new Random(System.currentTimeMillis()); + final byte[] passwordBytes = password.getBytes("UTF-8"); + final byte[] salt = new byte[getSaltLength()]; + random.nextBytes(salt); + + final byte[] saltedPassword = new byte[passwordBytes.length + + salt.length]; + System.arraycopy(passwordBytes, + 0, + saltedPassword, + 0, + passwordBytes.length); + System.arraycopy(salt, + 0, + saltedPassword, + passwordBytes.length, + salt.length); + final MessageDigest digest = MessageDigest.getInstance( + getHashAlgorithm()); + final byte[] hashedBytes = digest.digest(saltedPassword); + + final String hashedPassword = new String(hashedBytes, "UTF-8"); + + user.setPassword(hashedPassword); + userRepository.save(user); + + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(String.format( + "Configured hash algorithm '%s 'is not available.", + getHashAlgorithm()), ex); + } catch (UnsupportedEncodingException ex) { + throw new RuntimeException("UTF-8 charset is not supported."); + } + } + + /** + * Gets the hash algorithm to use. + * + * ToDo: Make configurable. + * + * @return At the moment SHA-512, will be made configurable. + */ + private String getHashAlgorithm() { + return "SHA-512"; + } + + /** + * Returns the length for the salt (number of bytes). + * + * ToDo: Make configurable. + * + * @return At the moment 256. Will be made configurable. + */ + private int getSaltLength() { + return 256; + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/UserRepository.java b/ccm-core/src/main/java/org/libreccm/core/UserRepository.java new file mode 100644 index 000000000..8fefffb5e --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/UserRepository.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.core; + +import java.util.List; + +import javax.enterprise.context.RequestScoped; +import javax.persistence.TypedQuery; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +public class UserRepository extends AbstractEntityRepository { + + @Override + public Class getEntityClass() { + return User.class; + } + + @Override + public boolean isNew(final User entity) { + return entity.getSubjectId() == 0; + } + + public User findByScreenName(final String screenname) { + final TypedQuery query = getEntityManager().createNamedQuery( + "findUserByScreenName", User.class); + query.setParameter("screenname", screenname); + + final List result = query.getResultList(); + + if (result.isEmpty()) { + return null; + } else if (result.size() == 1) { + return result.get(0); + } else { + throw new MultipleMatchingUserException(String.format( + "Found multipe users identified by screen name '%s'. " + + "Check your database.", + screenname)); + } + } + + public User findByEmailAddress(final String emailAddress) { + final TypedQuery query = getEntityManager().createNamedQuery( + "findUserByEmailAddress", User.class); + query.setParameter("emailAddress", emailAddress); + + final List result = query.getResultList(); + + if (result.isEmpty()) { + return null; + } else if(result.size() == 1) { + return result.get(0); + } else { + throw new MultipleMatchingUserException(String.format( + "Found multipe users identified by email address '%s'. " + + "Check your database.", + emailAddress)); + } + } + + private class MultipleMatchingUserException extends RuntimeException { + + private static final long serialVersionUID = 100237510055701060L; + + public MultipleMatchingUserException(final String message) { + super(message); + } + + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/authentication/LocalLoginModule.java b/ccm-core/src/main/java/org/libreccm/core/authentication/LocalLoginModule.java new file mode 100644 index 000000000..636f47c0a --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/authentication/LocalLoginModule.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.core.authentication; + +import com.arsdigita.kernel.KernelConfig; + +import org.libreccm.core.User; +import org.libreccm.core.UserRepository; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; + +import javax.inject.Inject; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.login.LoginException; + +/** + * Checks a username and a password in the database. + * + * @author Jens Pelzetter + */ +public class LocalLoginModule extends PasswordLoginModule { + + /** + * {@link UserRepository} instance for getting user accounts from the + * database. + */ + @Inject + private transient UserRepository userRepository; + + private transient Subject subject; + + @Override + public void initialize(final Subject subject, + final CallbackHandler callbackHandler, + final Map sharedState, + final Map options) { + super.initialize(subject, callbackHandler, sharedState, options); + this.subject = subject; + } + + @Override + public boolean commit() throws LoginException { + return true; + } + + @Override + public boolean abort() throws LoginException { + return true; + } + + @Override + public boolean logout() throws LoginException { + return true; + } + + /** + * Checks the provided password against the (hashed) password in the + * database. + * + * @param username The username identifying the user account the verify. + * @param password The password to verify. + * + * @return {@code true} if the password matches the password in the + * database, {@code false} if not or if there is no user account + * identified by the provided user name. + * + * @throws LoginException If an error occurs in the process. + */ + @Override + protected boolean checkPassword(final String username, + final String password) + throws LoginException { + + //Depending on the configured user identifier retrieve the user account + //using the screen name or the email address. + final User user; + if (KernelConfig.getConfig().emailIsPrimaryIdentifier()) { + user = userRepository.findByEmailAddress(username); + } else { + user = userRepository.findByScreenName(username); + } + + //If no matching user is found report this by throwing an exception. + if (user == null) { + throw new AccountNotFoundException(String.format( + "No user account identified by '%s' found.", username)); + } + + // Verify the password. The algorithm used for hashing is stored in the + // database so we need to retrieve the correct MessageDigest instance + // first. + try { + final MessageDigest digest = MessageDigest.getInstance(user + .getHashAlgorithm()); + final String saltedPassword = String.format("%s%s", + password, + user.getSalt()); + final String passwordHash = new String(digest.digest( + saltedPassword.getBytes())); + + if (passwordHash.equals(user.getPassword())) { + subject.getPrincipals().add(new SubjectPrincipal(user + .getSubjectId())); + return true; + } else { + return false; + } + } catch (NoSuchAlgorithmException ex) { + throw new LoginException(String.format( + "Failed to validate password because the password stored for " + + "user '%s' in the database is hashed with algorithm '%s' " + + "which is not avialable.", + username, user.getHashAlgorithm())); + } + + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/authentication/LoginManager.java b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginManager.java new file mode 100644 index 000000000..2c35b342a --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/authentication/LoginManager.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.core.authentication; + +import org.libreccm.core.CcmSessionContext; +import org.libreccm.core.User; +import org.libreccm.core.UserRepository; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Set; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +/** + * Provides methods for authenticating a user and for logging out a user. + * + * Under the hood JAAS is used for authentication. + * + * If a user is authenticated successfully the user object is stored in the + * session scoped bean {@link CcmSessionContext}. + * + * @author Jens Pelzetter + */ +@RequestScoped +public class LoginManager { + + /** + * Name of the register login context. + */ + private static final String REGISTER_LOGIN_CONTEXT = "RegisterLoginContext"; + + @Inject + private transient CcmSessionContext sessionContext; + + @Inject + private transient UserRepository userRepository; + + public void login(final String username, final String password) + throws LoginException { + + final CallbackHandler callbackHandler = new LoginCallbackHandler( + username, password); + final LoginContext loginContext = new LoginContext( + REGISTER_LOGIN_CONTEXT, + callbackHandler); + loginContext.login(); + final Subject subject = loginContext.getSubject(); + + final Set principals = subject.getPrincipals( + SubjectPrincipal.class); + if (principals.isEmpty()) { + throw new LoginException("No principal set"); + } else { + final Iterator iterator = principals.iterator(); + final SubjectPrincipal principal = iterator.next(); + final User user = userRepository.findById(principal.getSubjectId()); + + sessionContext.setCurrentParty(user); + } + } + + private class LoginCallbackHandler implements CallbackHandler { + + private final String username; + private final String password; + + public LoginCallbackHandler(final String username, + final String password) { + this.username = username; + this.password = password; + } + + @Override + public void handle(final Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) { + ((NameCallback) callback).setName(username); + } else if (callback instanceof PasswordCallback) { + ((PasswordCallback) callback).setPassword(password + .toCharArray()); + } else { + throw new UnsupportedCallbackException(callback); + } + } + } + + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/authentication/PasswordLoginModule.java b/ccm-core/src/main/java/org/libreccm/core/authentication/PasswordLoginModule.java new file mode 100644 index 000000000..1c5a4f95b --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/authentication/PasswordLoginModule.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.core.authentication; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; + +import java.io.IOException; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +/** + * This {@code LoginModule} provides common methods for {@code LoginModule}s + * using a username/password combination to authenticate users. If provides + * common methods. It tries to fetch username and password from shared data + * provided by the calling {@link LoginContext} if possible. Otherwise is + * queries the user using {@link Callback}s. Username and password are stored in + * the shared data for use by other {@code LoginModule}s. + * + * This class in a reworked version of + * {@code org.arsdigita.kernel.security.PasswordLoginModule} developed by Sameer + * Ajmani (according to the JavaDoc). The main differences is that the new + * version uses generics and multi-catch for exceptions. Also the code, + * especially if clauses have been reworked to match the conventions enforced by + * PMD and other style checkers. Also the methods {@code getPassword} and + * {@code getUsername} have been renamed to {@code retrievePassword} and + * {@code retrieveUsername} because this class is not a Java Bean and the values + * are not Java Bean Properties. + * + * This class is abstract. The methods + * {@link #checkPassword(java.lang.String, char[])}, {@link #commit()}, {@link #abort()} + * and {@link #logout()} are left to implement by sub classes. + * + * + * @author Jens Pelzetter + */ +public abstract class PasswordLoginModule implements LoginModule { + + private static final Logger LOGGER = LogManager.getLogger( + PasswordLoginModule.class); + /** + * Key for username in shared data map. + */ + private static final String NAME_KEY = "javax.security.auth.login.name"; + /** + * Key for password in shared data map. + */ + private static final String PASSWORD_KEY + = "javax.security.auth.login.password"; + + /** + * Fields set by the {@link #initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map) + * method. + * We only set the fields we use in this class. + */ + private CallbackHandler callbackHandler; + private Map sharedState; + + /** + * {@inheritDoc } + * + * @param subject {@inheritDoc } + * @param callbackHandler {@inheritDoc } + * @param sharedState {@inheritDoc } + * @param options {@inheritDoc } + */ + @Override + public void initialize(final Subject subject, + final CallbackHandler callbackHandler, + final Map sharedState, + final Map options) { + LOGGER.debug("Initalizing..."); + this.callbackHandler = callbackHandler; + this.sharedState = sharedState; + LOGGER.debug("Initalized..."); + } + + /** + * Implementation of the {@link LoginModule#login()} method. Retrieves the + * username and the password using the {@link #retrieveUsername()} and + * {@link #retrievePassword()} methods and call the + * {@link #checkPassword(java.lang.String, char[])} method. + * + * @return The return value of the + * {@link #checkPassword(java.lang.String, char[])} method. + * + * @throws LoginException If something goes wrong. + */ + @Override + public boolean login() throws LoginException { + LOGGER.debug("Trying to authenticate user..."); + return checkPassword(retrieveUsername(), retrievePassword()); + } + + /** + * {@inheritDoc } + * + * @return {@inheritDoc } + * + * @throws LoginException {@inheritDoc } + */ + @Override + public abstract boolean commit() throws LoginException; + + /** + * {@inheritDoc } + * + * @return {@inheritDoc } + * + * @throws LoginException {@inheritDoc } + */ + @Override + public abstract boolean abort() throws LoginException; + + /** + * {@inheritDoc } + * + * @return {@inheritDoc } + * + * @throws LoginException {@inheritDoc } + */ + @Override + public abstract boolean logout() throws LoginException; + + /** + * Attempts to read username from shared data map; otherwise retreives it + * using a NameCallback. + * + * @return the username. + * + * @throws LoginException if an error occurs. + */ + private String retrieveUsername() throws LoginException { + String username = (String) sharedState.get(NAME_KEY); + if (username == null) { + try { + final NameCallback callback = new NameCallback("Username: "); + callbackHandler.handle(new Callback[]{callback}); + username = callback.getName(); + } catch (IOException | UnsupportedCallbackException ex) { + throw new LoginException("Could not get Username"); + } + } + + return username; + } + + /** + * Attempts to read password from shared data map; otherwise retreives it + * using a PasswordCallback. + * + * @return the password. + * + * @throws LoginException if an error occurs. + */ + private String retrievePassword() throws LoginException { + String password = (String) sharedState.get(PASSWORD_KEY); + + if (password == null) { + try { + final PasswordCallback callback = new PasswordCallback( + "Password: ", + false); + callbackHandler.handle(new Callback[]{callback}); + password = new String(callback.getPassword()); + } catch (UnsupportedCallbackException | IOException ex) { + throw new LoginException("Could not get password"); + } + } + + return password; + } + + /** + * Checks whether the given username/password combination is valid. + * + * @param username the username to check. + * @param password the password to check. + * + * @return {@code true} if the username/password combination is valid, + * {@code false} to otherwise. + * + * @throws LoginException If an error occurs. + */ + protected abstract boolean checkPassword(String username, String password) + throws LoginException; + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/authentication/SubjectPrincipal.java b/ccm-core/src/main/java/org/libreccm/core/authentication/SubjectPrincipal.java new file mode 100644 index 000000000..b9bc0180e --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/authentication/SubjectPrincipal.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.core.authentication; + +import java.security.Principal; + +/** + * + * @author Jens Pelzetter + */ +public final class SubjectPrincipal implements Principal { + + private final long subjectId; + + public SubjectPrincipal(final long subjectId) { + this.subjectId = subjectId; + } + + public long getSubjectId() { + return subjectId; + } + + @Override + public String getName() { + return Long.toString(subjectId); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 41 * hash + (int) (this.subjectId ^ (this.subjectId >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final SubjectPrincipal other = (SubjectPrincipal) obj; + return this.subjectId == other.getSubjectId(); + } + + @Override + public String toString() { + return String.format("%s{ " + + "subjectId = %d" + + " }", + super.toString(), + subjectId); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/messaging/Message.java b/ccm-core/src/main/java/org/libreccm/messaging/Message.java index 02b5a7c0a..a137052cc 100644 --- a/ccm-core/src/main/java/org/libreccm/messaging/Message.java +++ b/ccm-core/src/main/java/org/libreccm/messaging/Message.java @@ -19,7 +19,7 @@ package org.libreccm.messaging; import org.libreccm.core.CcmObject; -import org.libreccm.core.Party; +import org.libreccm.core.Subject; import org.libreccm.jpa.utils.MimeTypeConverter; import java.io.Serializable; @@ -56,7 +56,7 @@ public class Message extends CcmObject implements Serializable { @OneToOne @JoinColumn(name = "sender_id") - private Party sender; + private Subject sender; @Column(name = "subject") private String subject; @@ -82,11 +82,11 @@ public class Message extends CcmObject implements Serializable { @OneToMany(mappedBy = "message") private List attachments; - public Party getSender() { + public Subject getSender() { return sender; } - protected void setSender(final Party sender) { + protected void setSender(final Subject sender) { this.sender = sender; } diff --git a/ccm-core/src/main/java/org/libreccm/notification/Digest.java b/ccm-core/src/main/java/org/libreccm/notification/Digest.java index a46342923..f8d13f6f2 100644 --- a/ccm-core/src/main/java/org/libreccm/notification/Digest.java +++ b/ccm-core/src/main/java/org/libreccm/notification/Digest.java @@ -19,7 +19,7 @@ package org.libreccm.notification; import org.libreccm.core.CcmObject; -import org.libreccm.core.Party; +import org.libreccm.core.Subject; import java.io.Serializable; import java.util.Date; @@ -57,7 +57,7 @@ public class Digest extends CcmObject implements Serializable { @OneToOne @JoinColumn(name = "from_party_id") - private Party fromParty; + private Subject fromParty; @Column(name = "subject", length = 255, nullable = false) private String subject; @@ -78,11 +78,11 @@ public class Digest extends CcmObject implements Serializable { @Temporal(TemporalType.TIMESTAMP) private Date nextRun; - public Party getFromParty() { + public Subject getFromParty() { return fromParty; } - public void setFromParty(final Party fromParty) { + public void setFromParty(final Subject fromParty) { this.fromParty = fromParty; } diff --git a/ccm-core/src/main/java/org/libreccm/notification/Notification.java b/ccm-core/src/main/java/org/libreccm/notification/Notification.java index 0ce3247a5..0b4322e79 100644 --- a/ccm-core/src/main/java/org/libreccm/notification/Notification.java +++ b/ccm-core/src/main/java/org/libreccm/notification/Notification.java @@ -19,7 +19,7 @@ package org.libreccm.notification; import org.libreccm.core.CcmObject; -import org.libreccm.core.Party; +import org.libreccm.core.Subject; import org.libreccm.messaging.Message; import java.io.Serializable; @@ -80,7 +80,7 @@ public class Notification extends CcmObject implements Serializable { @OneToOne @JoinColumn(name = "receiver_id") - private Party receiver; + private Subject receiver; @OneToOne @JoinColumn(name = "digest_id") @@ -119,11 +119,11 @@ public class Notification extends CcmObject implements Serializable { @Column(name = "expunge_message") private boolean expungeMessage; - public Party getReceiver() { + public Subject getReceiver() { return receiver; } - public void setReceiver(final Party receiver) { + public void setReceiver(final Subject receiver) { this.receiver = receiver; } diff --git a/ccm-core/src/main/java/org/libreccm/notification/QueueItem.java b/ccm-core/src/main/java/org/libreccm/notification/QueueItem.java index 53552fa35..12c4125d9 100644 --- a/ccm-core/src/main/java/org/libreccm/notification/QueueItem.java +++ b/ccm-core/src/main/java/org/libreccm/notification/QueueItem.java @@ -18,7 +18,7 @@ */ package org.libreccm.notification; -import org.libreccm.core.Party; +import org.libreccm.core.Subject; import org.libreccm.messaging.Message; import java.io.Serializable; @@ -60,7 +60,7 @@ public class QueueItem implements Serializable { @OneToOne @JoinColumn(name = "receiver_id") - private Party receiver; + private Subject receiver; @Column(name = "retry_count") private long retryCount; @@ -89,11 +89,11 @@ public class QueueItem implements Serializable { this.queueItemId = queueItemId; } - public Party getReceiver() { + public Subject getReceiver() { return receiver; } - public void setReceiver(final Party receiver) { + public void setReceiver(final Subject receiver) { this.receiver = receiver; } diff --git a/ccm-core/src/main/java/org/libreccm/search/lucene/Document.java b/ccm-core/src/main/java/org/libreccm/search/lucene/Document.java index abc6dab45..0e4cfb2ac 100644 --- a/ccm-core/src/main/java/org/libreccm/search/lucene/Document.java +++ b/ccm-core/src/main/java/org/libreccm/search/lucene/Document.java @@ -18,7 +18,7 @@ */ package org.libreccm.search.lucene; -import org.libreccm.core.Party; +import org.libreccm.core.Subject; import java.io.Serializable; import java.util.Date; @@ -92,7 +92,7 @@ public class Document implements Serializable { @OneToOne @JoinColumn(name = "created_by_party_id") - private Party createdBy; + private Subject createdBy; @Column(name = "last_modified") @Temporal(TemporalType.TIMESTAMP) @@ -100,7 +100,7 @@ public class Document implements Serializable { @OneToOne @JoinColumn(name = "last_modified_by") - private Party lastModifiedBy; + private Subject lastModifiedBy; @Column(name = "content_section", length = 512) private String contentSection; @@ -213,11 +213,11 @@ public class Document implements Serializable { } } - public Party getCreatedBy() { + public Subject getCreatedBy() { return createdBy; } - public void setCreatedBy(final Party createdBy) { + public void setCreatedBy(final Subject createdBy) { this.createdBy = createdBy; } @@ -237,11 +237,11 @@ public class Document implements Serializable { } } - public Party getLastModifiedBy() { + public Subject getLastModifiedBy() { return lastModifiedBy; } - public void setLastModifiedBy(final Party lastModifiedBy) { + public void setLastModifiedBy(final Subject lastModifiedBy) { this.lastModifiedBy = lastModifiedBy; } diff --git a/ccm-core/src/main/resources/com/arsdigita/kernel/KernelConfig_parameter.properties b/ccm-core/src/main/resources/com/arsdigita/kernel/KernelConfig_parameter.properties new file mode 100755 index 000000000..0db71c6a2 --- /dev/null +++ b/ccm-core/src/main/resources/com/arsdigita/kernel/KernelConfig_parameter.properties @@ -0,0 +1,44 @@ +waf.debug.title=Global debug flag +waf.debug.purpose=Enables or disables WAF debugging +waf.debug.example=true +waf.debug.format=true|false + +waf.kernel.data_permission_check_enabled.title=DML permission checking flag +waf.kernel.data_permission_check_enabled.purpose=Enables or disables permissions checks on database writes +waf.kernel.data_permission_check_enabled.example=true +waf.kernel.data_permission_check_enabled.format=true|false + +waf.kernel.primary_user_identifier.title=The primary user identification +waf.kernel.primary_user_identifier.purpose=Determines whether email addresses or screen names are used to authenticate users +waf.kernel.primary_user_identifier.example=email +waf.kernel.primary_user_identifier.format=email|screen_name + +waf.kernel.remember_login.title=Remember login by default +waf.kernel.remember_login.purpose=Determines whether the "remember login" feature is enabled or disabled by default +waf.kernel.remember_login.example=true +waf.kernel.remember_login.format=true|false + +waf.kernel.secure_login.title=Require secure login +waf.kernel.secure_login.purpose=Accept only credentials presented over secure connection +waf.kernel.secure_login.example=true +waf.kernel.secure_login.format=true|false + +waf.kernel.sso_login.title=Enable SSO login +waf.kernel.sso_login.purpose=Enable alternative "RegisterSSO" login context. +waf.kernel.sso_login.example=false +waf.kernel.sso_login.format=true|false + +waf.kernel.supported_languages.title=Set the supported languages for categorization +waf.kernel.supported_languages.purpose=Set the supported languages for categorization. First entry is the default language +waf.kernel.supported_languages.example=en,de,fr,nl,it,pt,es +waf.kernel.supported_languages.format=[string] + +waf.kernel.language_independent_items.title=Allow language independent content items +waf.kernel.language_independent_items.purpose=Allow language independent content items +waf.kernel.language_independent_items.example=false +waf.kernel.language_independent_items.format=true|false + +waf.kernel.language_independent_code.title=Select language independent code +waf.kernel.language_independent_code.purpose=Allow language independent code +waf.kernel.language_independent_code.example=-- +waf.kernel.language_independent_code.format=[string] \ No newline at end of file diff --git a/ccm-core/src/test/java/org/libreccm/core/EqualsAndHashCodeTest.java b/ccm-core/src/test/java/org/libreccm/core/EqualsAndHashCodeTest.java index 4381e915e..e163353fd 100644 --- a/ccm-core/src/test/java/org/libreccm/core/EqualsAndHashCodeTest.java +++ b/ccm-core/src/test/java/org/libreccm/core/EqualsAndHashCodeTest.java @@ -41,7 +41,7 @@ public class EqualsAndHashCodeTest extends EqualsVerifier { CcmObject.class, EmailAddress.class, GroupMembership.class, - Party.class, + Subject.class, Permission.class, PersonName.class, Privilege.class, diff --git a/ccm-core/src/test/java/org/libreccm/core/ToStringTest.java b/ccm-core/src/test/java/org/libreccm/core/ToStringTest.java index cd9581bf0..2af5e44b2 100644 --- a/ccm-core/src/test/java/org/libreccm/core/ToStringTest.java +++ b/ccm-core/src/test/java/org/libreccm/core/ToStringTest.java @@ -41,7 +41,7 @@ public class ToStringTest extends ToStringVerifier { CcmObject.class, EmailAddress.class, GroupMembership.class, - Party.class, + Subject.class, Permission.class, PersonName.class, Privilege.class,