Transfer code from svn to gitlab.alfresco.com

This commit is contained in:
dhulley
2016-08-31 18:16:27 +01:00
parent 264b8f4eed
commit 3cd73ed8dc
220 changed files with 32707 additions and 0 deletions

233
pom.xml Normal file
View File

@@ -0,0 +1,233 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-super-pom</artifactId>
<version>6</version>
</parent>
<artifactId>alfresco-core</artifactId>
<version>6.5-SNAPSHOT</version>
<name>Alfresco Core</name>
<description>Alfresco core libraries and utils</description>
<scm>
<connection>scm:svn:https://svn.alfresco.com/repos/alfresco-open-mirror/services/alfresco-core/trunk/</connection>
<developerConnection>scm:svn:https://svn.alfresco.com/repos/alfresco-enterprise/services/alfresco-core/trunk/</developerConnection>
</scm>
<distributionManagement>
<repository>
<id>alfresco-internal</id>
<url>https://artifacts.alfresco.com/nexus/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>alfresco-internal-snapshots</id>
<url>https://artifacts.alfresco.com/nexus/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<properties>
<dependency.spring.version>3.2.16.RELEASE</dependency.spring.version>
<dependency.surf.version>6.8</dependency.surf.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1-HTTPCLIENT-1265</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.6-alf-20131023</version>
</dependency>
<dependency>
<groupId>org.safehaus.jug</groupId>
<artifactId>jug</artifactId>
<version>2.0.0</version>
<classifier>asl</classifier>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.5</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160212</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${dependency.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${dependency.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${dependency.spring.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>1.8.3-alfresco-patched</version>
</dependency>
<dependency>
<groupId>org.alfresco.surf</groupId>
<artifactId>spring-surf-core-configservice</artifactId>
<version>${dependency.surf.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-xjc</artifactId>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.guessencoding</groupId>
<artifactId>guessencoding</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.0.1b</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<!-- provided dependencies -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- Test only dependencies, as popped up while running mvn test -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${dependency.spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4-DBCP330</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
<tagNameFormat>@{project.version}</tagNameFormat>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.api;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation is used to denote a class or method as part
* of the public API. When a class or method is so designated then
* we will not change it within a release in a way that would make
* it no longer backwardly compatible with an earlier version within
* the release.
*
* @author Greg Melahn
*/
@Target( {ElementType.TYPE,ElementType.METHOD} )
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AlfrescoPublicApi
{
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Enumeration;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.StringUtils;
/**
* Simple extension to the{@link DefaultPropertiesPersister} to strip trailing whitespace
* from incoming properties.
*
* @author shane frensley
* @see org.springframework.util.DefaultPropertiesPersister
*/
public class AlfrescoPropertiesPersister extends DefaultPropertiesPersister
{
private static Log logger = LogFactory.getLog(AlfrescoPropertiesPersister.class);
@Override
public void load(Properties props, InputStream is) throws IOException
{
super.load(props, is);
strip(props);
}
@Override
public void load(Properties props, Reader reader) throws IOException
{
super.load(props, reader);
strip(props);
}
public void loadFromXml(Properties props, InputStream is) throws IOException
{
super.loadFromXml(props, is);
strip(props);
}
private void strip(Properties props)
{
for (Enumeration<Object> keys = props.keys(); keys.hasMoreElements();)
{
String key = (String) keys.nextElement();
String val = StringUtils.trimTrailingWhitespace(props.getProperty(key));
if (logger.isTraceEnabled())
{
logger.trace("Trimmed trailing whitespace for property " + key + " = " + val);
}
props.setProperty(key, val);
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.sql.Connection;
import javax.naming.NamingException;
import javax.sql.DataSource;
/**
* An extended version of JndiObjectFactoryBean that actually tests a JNDI data source before falling back to its
* default object. Allows continued backward compatibility with old-style datasource configuration.
*
* @author dward
*/
public class JndiObjectFactoryBean extends org.springframework.jndi.JndiObjectFactoryBean
{
@Override
protected Object lookup() throws NamingException
{
Object candidate = super.lookup();
if (candidate instanceof DataSource)
{
Connection con = null;
try
{
con = ((DataSource) candidate).getConnection();
}
catch (Exception e)
{
NamingException e1 = new NamingException("Unable to get connection from " + getJndiName());
e1.setRootCause(e);
throw e1;
}
finally
{
try
{
if (con != null)
{
con.close();
}
}
catch (Exception e)
{
}
}
}
return candidate;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.util.Properties;
import javax.naming.NamingException;
import org.springframework.jndi.JndiTemplate;
/**
* An extended {@link SystemPropertiesFactoryBean} that allows properties to be set through JNDI entries in
* java:comp/env/properties/*. The precedence given to system properties is still as per the superclass.
*
* @author dward
*/
public class JndiPropertiesFactoryBean extends SystemPropertiesFactoryBean
{
private JndiTemplate jndiTemplate = new JndiTemplate();
@Override
protected void resolveMergedProperty(String propertyName, Properties props)
{
try
{
Object value = this.jndiTemplate.lookup("java:comp/env/properties/" + propertyName);
if (value != null)
{
String stringValue = value.toString();
if (stringValue.length() > 0)
{
// Unfortunately, JBoss 4 wrongly expects every env-entry declared in web.xml to have an
// env-entry-value (even though these are meant to be decided on deployment!). So we treat the empty
// string as null.
props.setProperty(propertyName, stringValue);
}
}
}
catch (NamingException e)
{
// Fall back to merged value in props
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.util.Properties;
import javax.naming.NamingException;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.jndi.JndiTemplate;
/**
* An extended {@link PropertyPlaceholderConfigurer} that allows properties to be set through JNDI entries in
* java:comp/env/properties/*. The precedence given to system properties is still as per the superclass.
*
* @author dward
*/
public class JndiPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer
{
private JndiTemplate jndiTemplate = new JndiTemplate();
@Override
protected String resolvePlaceholder(String placeholder, Properties props)
{
String result = null;
try
{
Object value = this.jndiTemplate.lookup("java:comp/env/properties/" + placeholder);
if (value != null)
{
result = value.toString();
}
}
catch (NamingException e)
{
}
// Unfortunately, JBoss 4 wrongly expects every env-entry declared in web.xml to have an env-entry-value (even
// though these are meant to be decided on deployment!). So we treat the empty string as null.
return result == null || result.length() == 0 ? super.resolvePlaceholder(placeholder, props) : result;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource;
import org.springframework.beans.BeansException;
/**
* A non-blocking version of LazyInitTargetSource.
*
* @author dward
*/
public class NonBlockingLazyInitTargetSource extends AbstractBeanFactoryBasedTargetSource
{
private static final long serialVersionUID = 4509578245779492037L;
private Object target;
private ReadWriteLock lock = new ReentrantReadWriteLock();
public Object getTarget() throws BeansException
{
this.lock.readLock().lock();
try
{
if (this.target != null)
{
return this.target;
}
}
finally
{
this.lock.readLock().unlock();
}
this.lock.writeLock().lock();
try
{
if (this.target == null)
{
this.target = getBeanFactory().getBean(getTargetBeanName());
}
return this.target;
}
finally
{
this.lock.writeLock().unlock();
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.io.IOException;
import java.net.URL;
import java.util.Set;
import org.springframework.core.io.Resource;
import org.springframework.util.PathMatcher;
/**
* An interface for plug ins to JBossEnabledResourcePatternResolver that avoids direct dependencies on
* application server specifics.
*
* @author dward
*/
public interface PathMatchingHelper
{
/**
* Indicates whether this helper is capable of searching the given URL (i.e. its protocol is supported).
*
* @param rootURL
* the root url to be searched
* @return <code>true</code> if this helper is capable of searching the given URL
*/
public boolean canHandle(URL rootURL);
/**
* Gets the resource at the given URL.
*
* @param url URL
* @return the resource at the given URL
* @throws IOException
* for any error
*/
public Resource getResource(URL url) throws IOException;
/**
* Gets the set of resources under the given URL whose path matches the given sub pattern.
*
* @param matcher
* the matcher
* @param rootURL
* the root URL to be searched
* @param subPattern
* the ant-style pattern to match
* @return the set of matching resources
* @throws IOException
* for any error
*/
public Set<Resource> getResources(PathMatcher matcher, URL rootURL, String subPattern) throws IOException;
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.Constants;
/**
* Like the parent <code>PropertiesFactoryBean</code>, but overrides or augments the resulting property set with values
* from VM system properties. As with the Spring {@link PropertyPlaceholderConfigurer} the following modes are
* supported:
* <ul>
* <li><b>SYSTEM_PROPERTIES_MODE_NEVER: </b>Don't use system properties at all.</li>
* <li><b>SYSTEM_PROPERTIES_MODE_FALLBACK: </b>Fallback to a system property only for undefined properties.</li>
* <li><b>SYSTEM_PROPERTIES_MODE_OVERRIDE: (DEFAULT)</b>Use a system property if it is available.</li>
* </ul>
* Note that system properties will only be included in the property set if defaults for the property have already been
* defined using {@link #setProperties(Properties)} or {@link #setLocations(org.springframework.core.io.Resource[])} or
* their names have been included explicitly in the set passed to {@link #setSystemProperties(Set)}.
*
* @author Derek Hulley
*/
public class SystemPropertiesFactoryBean extends PropertiesFactoryBean
{
private static final Constants constants = new Constants(PropertyPlaceholderConfigurer.class);
private int systemPropertiesMode = PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE;
private Set<String> systemProperties = Collections.emptySet();
/**
* Set the system property mode by the name of the corresponding constant, e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE".
*
* @param constantName
* name of the constant
* @throws java.lang.IllegalArgumentException
* if an invalid constant was specified
* @see #setSystemPropertiesMode
*/
public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException
{
this.systemPropertiesMode = SystemPropertiesFactoryBean.constants.asNumber(constantName).intValue();
}
/**
* Set how to check system properties.
*
* @see PropertyPlaceholderConfigurer#setSystemPropertiesMode(int)
*/
public void setSystemPropertiesMode(int systemPropertiesMode)
{
this.systemPropertiesMode = systemPropertiesMode;
}
/**
* Set the names of the properties that can be considered for overriding.
*
* @param systemProperties
* a set of properties that can be fetched from the system properties
*/
public void setSystemProperties(Set<String> systemProperties)
{
this.systemProperties = systemProperties;
}
@SuppressWarnings("unchecked")
@Override
protected Properties mergeProperties() throws IOException
{
// First do the default merge
Properties props = super.mergeProperties();
// Now resolve all the merged properties
if (this.systemPropertiesMode == PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_NEVER)
{
// If we are in never mode, we don't refer to system properties at all
for (String systemProperty : (Set<String>) (Set) props.keySet())
{
resolveMergedProperty(systemProperty, props);
}
}
else
{
// Otherwise, we allow unset properties to drift through from the systemProperties set and potentially set
// ones to be overriden by system properties
Set<String> propNames = new HashSet<String>((Set<String>) (Set) props.keySet());
propNames.addAll(this.systemProperties);
for (String systemProperty : propNames)
{
resolveMergedProperty(systemProperty, props);
if (this.systemPropertiesMode == PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK
&& props.containsKey(systemProperty))
{
// It's already there
continue;
}
// Get the system value and assign if present
String systemPropertyValue = System.getProperty(systemProperty);
if (systemPropertyValue != null)
{
props.put(systemProperty, systemPropertyValue);
}
}
}
return props;
}
/**
* Override hook. Allows subclasses to resolve a merged property from an alternative source, whilst still respecting
* the chosen system property fallback path.
*
* @param systemProperty String
* @param props Properties
*/
protected void resolveMergedProperty(String systemProperty, Properties props)
{
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.config;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Takes a set of properties and pushes them into the Java environment. Usually, VM properties
* are required by the system (see {@link SystemPropertiesFactoryBean} and
* Spring's <tt>PropertyPlaceholderConfigurer</tt>); sometimes it is necessary to take properties
* available to Spring and push them onto the VM.
* <p>
* For simplicity, the system property, if present, will NOT be modified. Also, property placeholders
* (<b>${...}</b>), empty values and null values will be ignored.
* <p>
* Use the {@link #init()} method to push the properties.
*
* @author Derek Hulley
* @since V3.1
*/
public class SystemPropertiesSetterBean
{
private static Log logger = LogFactory.getLog(SystemPropertiesSetterBean.class);
private Map<String, String> propertyMap;
SystemPropertiesSetterBean()
{
propertyMap = new HashMap<String, String>(3);
}
/**
* Set the properties that will be pushed onto the JVM.
*
* @param propertyMap a map of <b>property name</b> to <b>property value</b>
*/
public void setPropertyMap(Map<String, String> propertyMap)
{
this.propertyMap = propertyMap;
}
public void init()
{
for (Map.Entry<String, String> entry : propertyMap.entrySet())
{
String name = entry.getKey();
String value = entry.getValue();
// Some values can be ignored
if (value == null || value.length() == 0)
{
continue;
}
if (value.startsWith("${") && value.endsWith("}"))
{
continue;
}
// Check the system properties
if (System.getProperty(name) != null)
{
// It was already there
if (logger.isDebugEnabled())
{
logger.debug("\n" +
"Not pushing up system property: \n" +
" Property: " + name + "\n" +
" Value already present: " + System.getProperty(name) + "\n" +
" Value provided: " + value);
}
continue;
}
System.setProperty(name, value);
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encoding;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @since 2.1
* @author Derek Hulley
*/
public abstract class AbstractCharactersetFinder implements CharactersetFinder
{
private static Log logger = LogFactory.getLog(AbstractCharactersetFinder.class);
private static boolean isDebugEnabled = logger.isDebugEnabled();
private int bufferSize;
public AbstractCharactersetFinder()
{
this.bufferSize = 8192;
}
/**
* Set the maximum number of bytes to read ahead when attempting to determine the characterset.
* Most characterset detectors are efficient and can process 8K of buffered data very quickly.
* Some, may need to be constrained a bit.
*
* @param bufferSize the number of bytes - default 8K.
*/
public void setBufferSize(int bufferSize)
{
this.bufferSize = bufferSize;
}
/**
* {@inheritDoc}
* <p>
* The input stream is checked to ensure that it supports marks, after which
* a buffer is extracted, leaving the stream in its original state.
*/
public final Charset detectCharset(InputStream is)
{
// Only support marking streams
if (!is.markSupported())
{
throw new IllegalArgumentException("The InputStream must support marks. Wrap the stream in a BufferedInputStream.");
}
try
{
int bufferSize = getBufferSize();
if (bufferSize < 0)
{
throw new RuntimeException("The required buffer size may not be negative: " + bufferSize);
}
// Mark the stream for just a few more than we actually will need
is.mark(bufferSize);
// Create a buffer to hold the data
byte[] buffer = new byte[bufferSize];
// Fill it
int read = is.read(buffer);
// Create an appropriately sized buffer
if (read > -1 && read < buffer.length)
{
byte[] copyBuffer = new byte[read];
System.arraycopy(buffer, 0, copyBuffer, 0, read);
buffer = copyBuffer;
}
// Detect
return detectCharset(buffer);
}
catch (IOException e)
{
// Attempt a reset
throw new AlfrescoRuntimeException("IOException while attempting to detect charset encoding.", e);
}
finally
{
try { is.reset(); } catch (Throwable ee) {}
}
}
public final Charset detectCharset(byte[] buffer)
{
try
{
Charset charset = detectCharsetImpl(buffer);
// Done
if (isDebugEnabled)
{
if (charset == null)
{
// Read a few characters for debug purposes
logger.debug("\n" +
"Failed to identify stream character set: \n" +
" Guessed 'chars': " + Arrays.toString(buffer));
}
else
{
// Read a few characters for debug purposes
logger.debug("\n" +
"Identified character set from stream:\n" +
" Charset: " + charset + "\n" +
" Detected chars: " + new String(buffer, charset.name()));
}
}
return charset;
}
catch (Throwable e)
{
logger.error("IOException while attempting to detect charset encoding.", e);
return null;
}
}
/**
* Some implementations may only require a few bytes to do detect the stream type,
* whilst others may be more efficient with larger buffers. In either case, the
* number of bytes actually present in the buffer cannot be enforced.
* <p>
* Only override this method if there is a very compelling reason to adjust the buffer
* size, and then consider handling the {@link #setBufferSize(int)} method by issuing a
* warning. This will prevent users from setting the buffer size when it has no effect.
*
* @return Returns the maximum desired size of the buffer passed
* to the {@link CharactersetFinder#detectCharset(byte[])} method.
*
* @see #setBufferSize(int)
*/
protected int getBufferSize()
{
return bufferSize;
}
/**
* Worker method for implementations to override. All exceptions will be reported and
* absorbed and <tt>null</tt> returned.
* <p>
* The interface contract is that the data buffer must not be altered in any way.
*
* @param buffer the buffer of data no bigger than the requested
* {@linkplain #getBufferSize() best buffer size}. This can,
* very efficiently, be turned into an <tt>InputStream</tt> using a
* <tt>ByteArrayInputStream<tt>.
* @return Returns the charset or <tt>null</tt> if an accurate conclusion
* is not possible
* @throws Exception Any exception, checked or not
*/
protected abstract Charset detectCharsetImpl(byte[] buffer) throws Exception;
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encoding;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Byte Order Marker encoding detection.
*
* @since 2.1
* @author Pacific Northwest National Lab
* @author Derek Hulley
*/
public class BomCharactersetFinder extends AbstractCharactersetFinder
{
private static Log logger = LogFactory.getLog(BomCharactersetFinder.class);
@Override
public void setBufferSize(int bufferSize)
{
logger.warn("Setting the buffersize has no effect for charset finder: " + BomCharactersetFinder.class.getName());
}
/**
* @return Returns 64
*/
@Override
protected int getBufferSize()
{
return 64;
}
/**
* Just searches the Byte Order Marker, i.e. the first three characters for a sign of
* the encoding.
*/
protected Charset detectCharsetImpl(byte[] buffer) throws Exception
{
Charset charset = null;
ByteArrayInputStream bis = null;
try
{
bis = new ByteArrayInputStream(buffer);
bis.mark(3);
char[] byteHeader = new char[3];
InputStreamReader in = new InputStreamReader(bis);
int bytesRead = in.read(byteHeader);
bis.reset();
if (bytesRead < 2)
{
// ASCII
charset = Charset.forName("Cp1252");
}
else if (
byteHeader[0] == 0xFE &&
byteHeader[1] == 0xFF)
{
// UCS-2 Big Endian
charset = Charset.forName("UTF-16BE");
}
else if (
byteHeader[0] == 0xFF &&
byteHeader[1] == 0xFE)
{
// UCS-2 Little Endian
charset = Charset.forName("UTF-16LE");
}
else if (
bytesRead >= 3 &&
byteHeader[0] == 0xEF &&
byteHeader[1] == 0xBB &&
byteHeader[2] == 0xBF)
{
// UTF-8
charset = Charset.forName("UTF-8");
}
else
{
// No idea
charset = null;
}
// Done
return charset;
}
finally
{
if (bis != null)
{
try { bis.close(); } catch (Throwable e) {}
}
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encoding;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* Interface for classes that are able to read a text-based input stream and determine
* the character encoding.
* <p>
* There are quite a few libraries that do this, but none are perfect. It is therefore
* necessary to abstract the implementation to allow these finders to be configured in
* as required.
* <p>
* Implementations should have a default constructor and be completely thread safe and
* stateless. This will allow them to be constructed and held indefinitely to do the
* decoding work.
* <p>
* Where the encoding cannot be determined, it is left to the client to decide what to do.
* Some implementations may guess and encoding or use a default guess - it is up to the
* implementation to specify the behaviour.
*
* @since 2.1
* @author Derek Hulley
*/
public interface CharactersetFinder
{
/**
* Attempt to detect the character set encoding for the give input stream. The input
* stream will not be altered or closed by this method, and must therefore support
* marking. If the input stream available doesn't support marking, then it can be wrapped with
* a {@link BufferedInputStream}.
* <p>
* The current state of the stream will be restored before the method returns.
*
* @param is an input stream that must support marking
* @return Returns the encoding of the stream,
* or <tt>null</tt> if encoding cannot be identified
*/
public Charset detectCharset(InputStream is);
/**
* Attempt to detect the character set encoding for the given buffer.
*
* @param buffer the first <i>n</i> bytes of the character stream
* @return Returns the encoding of the buffer,
* or <tt>null</tt> if encoding cannot be identified
*/
public Charset detectCharset(byte[] buffer);
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encoding;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import com.glaforge.i18n.io.CharsetToolkit;
/**
* Uses the <a href="http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding">Guess Encoding</a>
* library.
*
* @since 2.1
* @author Derek Hulley
*/
public class GuessEncodingCharsetFinder extends AbstractCharactersetFinder
{
/** Dummy charset to detect the default guess */
private static final Charset DUMMY_CHARSET = new DummyCharset();
@Override
protected Charset detectCharsetImpl(byte[] buffer) throws Exception
{
CharsetToolkit charsetToolkit = new CharsetToolkit(buffer, DUMMY_CHARSET);
charsetToolkit.setEnforce8Bit(true); // Force the default instead of a guess
Charset charset = charsetToolkit.guessEncoding();
if (charset == DUMMY_CHARSET)
{
return null;
}
else
{
return charset;
}
}
/**
* A dummy charset to detect a default hit.
*/
public static class DummyCharset extends Charset
{
DummyCharset()
{
super("dummy", new String[] {});
}
@Override
public boolean contains(Charset cs)
{
throw new UnsupportedOperationException();
}
@Override
public CharsetDecoder newDecoder()
{
throw new UnsupportedOperationException();
}
@Override
public CharsetEncoder newEncoder()
{
throw new UnsupportedOperationException();
}
}
}

View File

@@ -0,0 +1,312 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SealedObject;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Basic support for encryption engines.
*
* @since 4.0
*/
public abstract class AbstractEncryptor implements Encryptor
{
protected static final Log logger = LogFactory.getLog(Encryptor.class);
protected String cipherAlgorithm;
protected String cipherProvider;
protected KeyProvider keyProvider;
/**
* Constructs with defaults
*/
protected AbstractEncryptor()
{
}
/**
* @param keyProvider provides encryption keys based on aliases
*/
public void setKeyProvider(KeyProvider keyProvider)
{
this.keyProvider = keyProvider;
}
public KeyProvider getKeyProvider()
{
return keyProvider;
}
public void init()
{
PropertyCheck.mandatory(this, "keyProvider", keyProvider);
}
/**
* Factory method to be written by implementations to construct <b>and initialize</b>
* physical ciphering objects.
*
* @param keyAlias the key alias
* @param params algorithm-specific parameters
* @param mode the cipher mode
* @return Cipher
*/
protected abstract Cipher getCipher(String keyAlias, AlgorithmParameters params, int mode);
/**
* {@inheritDoc}
*/
@Override
public Pair<byte[], AlgorithmParameters> encrypt(String keyAlias, AlgorithmParameters params, byte[] input)
{
Cipher cipher = getCipher(keyAlias, params, Cipher.ENCRYPT_MODE);
if (cipher == null)
{
return new Pair<byte[], AlgorithmParameters>(input, null);
}
try
{
byte[] output = cipher.doFinal(input);
params = cipher.getParameters();
return new Pair<byte[], AlgorithmParameters>(output, params);
}
catch (Throwable e)
{
// cipher.init(Cipher.ENCRYPT_MODE, key, params);
throw new AlfrescoRuntimeException("Decryption failed for key alias: " + keyAlias, e);
}
}
protected void resetCipher()
{
}
/**
* {@inheritDoc}
*/
@Override
public byte[] decrypt(String keyAlias, AlgorithmParameters params, byte[] input)
{
Cipher cipher = getCipher(keyAlias, params, Cipher.DECRYPT_MODE);
if (cipher == null)
{
return input;
}
try
{
return cipher.doFinal(input);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Decryption failed for key alias: " + keyAlias, e);
}
}
/**
* {@inheritDoc}
*/
@Override
public InputStream decrypt(String keyAlias, AlgorithmParameters params, InputStream input)
{
Cipher cipher = getCipher(keyAlias, params, Cipher.DECRYPT_MODE);
if (cipher == null)
{
return input;
}
try
{
return new CipherInputStream(input, cipher);
}
catch (Throwable e)
{
throw new AlfrescoRuntimeException("Decryption failed for key alias: " + keyAlias, e);
}
}
/**
* {@inheritDoc}
* <p/>
* Serializes and {@link #encrypt(String, AlgorithmParameters, byte[]) encrypts} the input data.
*/
@Override
public Pair<byte[], AlgorithmParameters> encryptObject(String keyAlias, AlgorithmParameters params, Object input)
{
try
{
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(input);
byte[] unencrypted = bos.toByteArray();
return encrypt(keyAlias, params, unencrypted);
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to serialize or encrypt object", e);
}
}
/**
* {@inheritDoc}
* <p/>
* {@link #decrypt(String, AlgorithmParameters, byte[]) Decrypts} and deserializes the input data
*/
@Override
public Object decryptObject(String keyAlias, AlgorithmParameters params, byte[] input)
{
try
{
byte[] unencrypted = decrypt(keyAlias, params, input);
ByteArrayInputStream bis = new ByteArrayInputStream(unencrypted);
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject();
return obj;
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to deserialize or decrypt object", e);
}
}
@Override
public Serializable sealObject(String keyAlias, AlgorithmParameters params, Serializable input)
{
if (input == null)
{
return null;
}
Cipher cipher = getCipher(keyAlias, params, Cipher.ENCRYPT_MODE);
if (cipher == null)
{
return input;
}
try
{
return new SealedObject(input, cipher);
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to seal object", e);
}
}
@Override
public Serializable unsealObject(String keyAlias, Serializable input) throws InvalidKeyException
{
if (input == null)
{
return input;
}
// Don't unseal it if it is not sealed
if (!(input instanceof SealedObject))
{
return input;
}
// Get the Key, rather than a Cipher
Key key = keyProvider.getKey(keyAlias);
if (key == null)
{
// The client will be expecting to unseal the object
throw new IllegalStateException("No key matching " + keyAlias + ". Cannot unseal object.");
}
// Unseal it using the key
SealedObject sealedInput = (SealedObject) input;
try
{
Serializable output = (Serializable) sealedInput.getObject(key);
// Done
return output;
}
catch(InvalidKeyException e)
{
// let these through, can be useful to client code to know this is the cause
throw e;
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to unseal object", e);
}
}
public void setCipherAlgorithm(String cipherAlgorithm)
{
this.cipherAlgorithm = cipherAlgorithm;
}
public String getCipherAlgorithm()
{
return this.cipherAlgorithm;
}
public void setCipherProvider(String cipherProvider)
{
this.cipherProvider = cipherProvider;
}
public String getCipherProvider()
{
return this.cipherProvider;
}
/**
* {@inheritDoc}
*/
@Override
public AlgorithmParameters decodeAlgorithmParameters(byte[] encoded)
{
try
{
AlgorithmParameters p = null;
String algorithm = "DESede";
if(getCipherProvider() != null)
{
p = AlgorithmParameters.getInstance(algorithm, getCipherProvider());
}
else
{
p = AlgorithmParameters.getInstance(algorithm);
}
p.init(encoded);
return p;
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("", e);
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
/**
* Basic support for key providers
* <p/>
* TODO: This class will provide the alias name mapping so that use-cases can be mapped
* to different alias names in the keystore.
*
* @author Derek Hulley
* @since 4.0
*/
public abstract class AbstractKeyProvider implements KeyProvider
{
/*
* Not a useless class.
*/
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
import java.util.Set;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
/**
* Manages a Java Keystore for Alfresco, including caching keys where appropriate.
*
* @since 4.0
*
*/
public interface AlfrescoKeyStore
{
public static final String KEY_KEYSTORE_PASSWORD = "keystore.password";
/**
* The name of the keystore.
*
* @return the name of the keystore.
*/
public String getName();
/**
* Backup the keystore to the backup location. Write the keys to the backup keystore.
*/
public void backup();
/**
* The key store parameters.
*
* @return KeyStoreParameters
*/
public KeyStoreParameters getKeyStoreParameters();
/**
* The backup key store parameters.
*
* @return * @return
*/
public KeyStoreParameters getBackupKeyStoreParameters();
/**
* Does the underlying key store exist?
*
* @return true if it exists, false otherwise
*/
public boolean exists();
/**
* Return the key with the given key alias.
*
* @param keyAlias String
* @return Key
*/
public Key getKey(String keyAlias);
/**
* Return the timestamp (in ms) of when the key was last loaded from the keystore on disk.
*
* @param keyAlias String
* @return long
*/
public long getKeyTimestamp(String keyAlias);
/**
* Return the backup key with the given key alias.
*
* @param keyAlias String
* @return Key
*/
public Key getBackupKey(String keyAlias);
/**
* Return all key aliases in the key store.
*
* @return Set<String>
*/
public Set<String> getKeyAliases();
/**
* Create an array of key managers from keys in the key store.
*
* @return KeyManager[]
*/
public KeyManager[] createKeyManagers();
/**
* Create an array of trust managers from certificates in the key store.
*
* @return TrustManager[]
*/
public TrustManager[] createTrustManagers();
/**
* Create the key store if it doesn't exist.
* A key for each key alias will be written to the keystore on disk, either from the cached keys or, if not present, a key will be generated.
*/
public void create();
/**
* Reload the keys from the key store.
*/
public void reload() throws InvalidKeystoreException, MissingKeyException;
/**
* Check that the keys in the key store are valid i.e. that they match those registered.
*/
public void validateKeys() throws InvalidKeystoreException, MissingKeyException;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
/**
*
* Represents a loaded, cached encryption key. The key can be <tt>null</tt>.
*
* @since 4.0
*
*/
public class CachedKey
{
public static CachedKey NULL = new CachedKey(null, null);
private Key key;
private long timestamp;
CachedKey(Key key, Long timestamp)
{
this.key = key;
this.timestamp = (timestamp != null ? timestamp.longValue() : -1);
}
public CachedKey(Key key)
{
super();
this.key = key;
this.timestamp = System.currentTimeMillis();
}
public Key getKey()
{
return key;
}
public long getTimestamp()
{
return timestamp;
}
}

View File

@@ -0,0 +1,355 @@
/*
* Copyright 2005-2010 Alfresco Software, Ltd. All rights reserved.
*
* License rights for this program may be obtained from Alfresco Software, Ltd.
* pursuant to a written agreement and any use of this program without such an
* agreement is prohibited.
*/
package org.alfresco.encryption;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* An input stream that encrypts data produced by a {@link EncryptingOutputStream}. A lightweight yet secure hybrid
* encryption scheme is used. A random symmetric key is decrypted using the receiver's private key. The supplied data is
* then decrypted using the symmetric key and read on a streaming basis. When the end of the stream is reached or the
* stream is closed, a HMAC checksum of the entire stream contents is validated.
*/
public class DecryptingInputStream extends InputStream
{
/** The wrapped stream. */
private final DataInputStream wrapped;
/** The input cipher. */
private final Cipher inputCipher;
/** The MAC generator. */
private final Mac mac;
/** Internal buffer for MAC computation. */
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
/** A DataOutputStream on top of our interal buffer. */
private final DataOutputStream dataStr = new DataOutputStream(this.buffer);
/** The current unencrypted data block. */
private byte[] currentDataBlock;
/** The next encrypted data block. (could be the HMAC checksum) */
private byte[] nextDataBlock;
/** Have we read to the end of the underlying stream?. */
private boolean isAtEnd;
/** Our current position within currentDataBlock. */
private int currentDataPos;
/**
* Constructs a DecryptingInputStream using default symmetric encryption parameters.
*
* @param wrapped
* the input stream to decrypt
* @param privKey
* the receiver's private key for decrypting the symmetric key
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws NoSuchAlgorithmException
* the no such algorithm exception
* @throws NoSuchPaddingException
* the no such padding exception
* @throws InvalidKeyException
* the invalid key exception
* @throws IllegalBlockSizeException
* the illegal block size exception
* @throws BadPaddingException
* the bad padding exception
* @throws InvalidAlgorithmParameterException
* the invalid algorithm parameter exception
* @throws NoSuchProviderException
* the no such provider exception
*/
public DecryptingInputStream(final InputStream wrapped, final PrivateKey privKey) throws IOException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException
{
this(wrapped, privKey, "AES", "CBC", "PKCS5PADDING");
}
/**
* Constructs a DecryptingInputStream.
*
* @param wrapped
* the input stream to decrypt
* @param privKey
* the receiver's private key for decrypting the symmetric key
* @param algorithm
* encryption algorithm (e.g. "AES")
* @param mode
* encryption mode (e.g. "CBC")
* @param padding
* padding scheme (e.g. "PKCS5PADDING")
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws NoSuchAlgorithmException
* the no such algorithm exception
* @throws NoSuchPaddingException
* the no such padding exception
* @throws InvalidKeyException
* the invalid key exception
* @throws IllegalBlockSizeException
* the illegal block size exception
* @throws BadPaddingException
* the bad padding exception
* @throws InvalidAlgorithmParameterException
* the invalid algorithm parameter exception
* @throws NoSuchProviderException
* the no such provider exception
*/
public DecryptingInputStream(final InputStream wrapped, final PrivateKey privKey, final String algorithm,
final String mode, final String padding) throws IOException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException, NoSuchProviderException
{
// Initialise a secure source of randomness
this.wrapped = new DataInputStream(wrapped);
final SecureRandom secRand = SecureRandom.getInstance("SHA1PRNG");
// Set up RSA
final Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING");
rsa.init(Cipher.DECRYPT_MODE, privKey, secRand);
// Read and decrypt the symmetric key
final SecretKey symKey = new SecretKeySpec(rsa.doFinal(readBlock()), algorithm);
// Read and decrypt initialisation vector
final byte[] keyIV = rsa.doFinal(readBlock());
// Set up cipher for decryption
this.inputCipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding);
this.inputCipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(keyIV));
// Read and decrypt the MAC key
final SecretKey macKey = new SecretKeySpec(this.inputCipher.doFinal(readBlock()), "HMACSHA1");
// Set up HMAC
this.mac = Mac.getInstance("HMACSHA1");
this.mac.init(macKey);
// Always read a block ahead so we can intercept the HMAC block
this.nextDataBlock = readBlock(false);
}
/**
* Reads the next block of data, adding it to the HMAC checksum. Strips the header recording the number of bytes in
* the block.
*
* @return the data block, or <code>null</code> if the end of the stream has been reached
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private byte[] readBlock() throws IOException
{
return readBlock(true);
}
/**
* Reads the next block of data, optionally adding it to the HMAC checksum. Strips the header recording the number
* of bytes in the block.
*
* @param updateMac
* should the block be added to the HMAC checksum?
* @return the data block, or <code>null</code> if the end of the stream has been reached
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private byte[] readBlock(final boolean updateMac) throws IOException
{
int len;
try
{
len = this.wrapped.readInt();
}
catch (final EOFException e)
{
return null;
}
final byte[] in = new byte[len];
this.wrapped.readFully(in);
if (updateMac)
{
macBlock(in);
}
return in;
}
/**
* Updates the HMAC checksum with the given data block.
*
* @param block
* the block
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private void macBlock(final byte[] block) throws IOException
{
this.dataStr.writeInt(block.length);
this.dataStr.write(block);
// If we don't have the MAC key yet, buffer up until we do
if (this.mac != null)
{
this.dataStr.flush();
final byte[] bytes = this.buffer.toByteArray();
this.buffer.reset();
this.mac.update(bytes);
}
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read()
*/
@Override
public int read() throws IOException
{
final byte[] buf = new byte[1];
int bytesRead;
while ((bytesRead = read(buf)) == 0)
{
;
}
return bytesRead == -1 ? -1 : buf[0] & 0xFF;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read(byte[])
*/
@Override
public int read(final byte b[]) throws IOException
{
return read(b, 0, b.length);
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read(byte[], int, int)
*/
@Override
public int read(final byte b[], int off, final int len) throws IOException
{
if (b == null)
{
throw new NullPointerException();
}
else if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0)
{
throw new IndexOutOfBoundsException();
}
else if (len == 0)
{
return 0;
}
int bytesToRead = len;
OUTER: while (bytesToRead > 0)
{
// Fetch another block if necessary
while (this.currentDataBlock == null || this.currentDataPos >= this.currentDataBlock.length)
{
byte[] newDataBlock;
// We're right at the end of the last block so finish
if (this.isAtEnd)
{
this.currentDataBlock = this.nextDataBlock = null;
break OUTER;
}
// We've already read the last block so validate the MAC code
else if ((newDataBlock = readBlock(false)) == null)
{
if (!MessageDigest.isEqual(this.mac.doFinal(), this.nextDataBlock))
{
throw new IOException("Invalid HMAC");
}
// We still have what's left in the cipher to read
try
{
this.currentDataBlock = this.inputCipher.doFinal();
}
catch (final GeneralSecurityException e)
{
throw new RuntimeException(e);
}
this.isAtEnd = true;
}
// We have an ordinary data block to MAC and decrypt
else
{
macBlock(this.nextDataBlock);
this.currentDataBlock = this.inputCipher.update(this.nextDataBlock);
this.nextDataBlock = newDataBlock;
}
this.currentDataPos = 0;
}
final int bytesRead = Math.min(bytesToRead, this.currentDataBlock.length - this.currentDataPos);
System.arraycopy(this.currentDataBlock, this.currentDataPos, b, off, bytesRead);
bytesToRead -= bytesRead;
off += bytesRead;
this.currentDataPos += bytesRead;
}
return bytesToRead == len ? -1 : len - bytesToRead;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#available()
*/
@Override
public int available() throws IOException
{
return this.currentDataBlock == null ? 0 : this.currentDataBlock.length - this.currentDataPos;
}
/*
* (non-Javadoc)
* @see java.io.InputStream#close()
*/
@Override
public void close() throws IOException
{
// Read right to the end, just to ensure the MAC code is valid!
if (this.nextDataBlock != null)
{
final byte[] skipBuff = new byte[1024];
while (read(skipBuff) != -1)
{
;
}
}
this.wrapped.close();
this.dataStr.close();
}
}

View File

@@ -0,0 +1,480 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.AlgorithmParameters;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.encryption.MACUtils.MACInput;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.IPUtils;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.Base64;
import org.springframework.util.FileCopyUtils;
/**
* Various encryption utility methods.
*
* @since 4.0
*/
public class DefaultEncryptionUtils implements EncryptionUtils
{
// Logger
protected static Log logger = LogFactory.getLog(Encryptor.class);
protected static String HEADER_ALGORITHM_PARAMETERS = "XAlfresco-algorithmParameters";
protected static String HEADER_MAC = "XAlfresco-mac";
protected static String HEADER_TIMESTAMP = "XAlfresco-timestamp";
protected Encryptor encryptor;
protected MACUtils macUtils;
protected long messageTimeout; // ms
protected String remoteIP;
protected String localIP;
public DefaultEncryptionUtils()
{
try
{
this.localIP = InetAddress.getLocalHost().getHostAddress();
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("Unable to initialise EncryptionUtils", e);
}
}
public String getRemoteIP()
{
return remoteIP;
}
public void setRemoteIP(String remoteIP)
{
try
{
this.remoteIP = IPUtils.getRealIPAddress(remoteIP);
}
catch (UnknownHostException e)
{
throw new AlfrescoRuntimeException("Failed to get server IP address", e);
}
}
/**
* Get the local registered IP address for authentication purposes
*
* @return String
*/
protected String getLocalIPAddress()
{
return localIP;
}
public void setMessageTimeout(long messageTimeout)
{
this.messageTimeout = messageTimeout;
}
public void setEncryptor(Encryptor encryptor)
{
this.encryptor = encryptor;
}
public void setMacUtils(MACUtils macUtils)
{
this.macUtils = macUtils;
}
protected void setRequestMac(HttpMethod method, byte[] mac)
{
if(mac == null)
{
throw new AlfrescoRuntimeException("Mac cannot be null");
}
method.setRequestHeader(HEADER_MAC, Base64.encodeBytes(mac));
}
/**
* Set the MAC on the HTTP response
*
* @param response HttpServletResponse
* @param mac byte[]
*/
protected void setMac(HttpServletResponse response, byte[] mac)
{
if(mac == null)
{
throw new AlfrescoRuntimeException("Mac cannot be null");
}
response.setHeader(HEADER_MAC, Base64.encodeBytes(mac));
}
/**
* Get the MAC (Message Authentication Code) on the HTTP request
*
* @param req HttpServletRequest
* @return the MAC
* @throws IOException
*/
protected byte[] getMac(HttpServletRequest req) throws IOException
{
String header = req.getHeader(HEADER_MAC);
if(header != null)
{
return Base64.decode(header);
}
else
{
return null;
}
}
/**
* Get the MAC (Message Authentication Code) on the HTTP response
*
* @param res HttpMethod
* @return the MAC
* @throws IOException
*/
protected byte[] getResponseMac(HttpMethod res) throws IOException
{
Header header = res.getResponseHeader(HEADER_MAC);
if(header != null)
{
return Base64.decode(header.getValue());
}
else
{
return null;
}
}
/**
* Set the timestamp on the HTTP request
* @param method HttpMethod
* @param timestamp (ms, in UNIX time)
*/
protected void setRequestTimestamp(HttpMethod method, long timestamp)
{
method.setRequestHeader(HEADER_TIMESTAMP, String.valueOf(timestamp));
}
/**
* Set the timestamp on the HTTP response
* @param res HttpServletResponse
* @param timestamp (ms, in UNIX time)
*/
protected void setTimestamp(HttpServletResponse res, long timestamp)
{
res.setHeader(HEADER_TIMESTAMP, String.valueOf(timestamp));
}
/**
* Get the timestamp on the HTTP response
*
* @param method HttpMethod
* @return timestamp (ms, in UNIX time)
* @throws IOException
*/
protected Long getResponseTimestamp(HttpMethod method) throws IOException
{
Header header = method.getResponseHeader(HEADER_TIMESTAMP);
if(header != null)
{
return Long.valueOf(header.getValue());
}
else
{
return null;
}
}
/**
* Get the timestamp on the HTTP request
*
* @param method HttpServletRequest
* @return timestamp (ms, in UNIX time)
* @throws IOException
*/
protected Long getTimestamp(HttpServletRequest method) throws IOException
{
String header = method.getHeader(HEADER_TIMESTAMP);
if(header != null)
{
return Long.valueOf(header);
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public void setRequestAlgorithmParameters(HttpMethod method, AlgorithmParameters params) throws IOException
{
if(params != null)
{
method.setRequestHeader(HEADER_ALGORITHM_PARAMETERS, Base64.encodeBytes(params.getEncoded()));
}
}
/**
* Set the algorithm parameters header on the HTTP response
*
* @param response HttpServletResponse
* @param params AlgorithmParameters
* @throws IOException
*/
protected void setAlgorithmParameters(HttpServletResponse response, AlgorithmParameters params) throws IOException
{
if(params != null)
{
response.setHeader(HEADER_ALGORITHM_PARAMETERS, Base64.encodeBytes(params.getEncoded()));
}
}
/**
* Decode cipher algorithm parameters from the HTTP method
*
* @param method HttpMethod
* @return decoded algorithm parameters
* @throws IOException
*/
protected AlgorithmParameters decodeAlgorithmParameters(HttpMethod method) throws IOException
{
Header header = method.getResponseHeader(HEADER_ALGORITHM_PARAMETERS);
if(header != null)
{
byte[] algorithmParams = Base64.decode(header.getValue());
AlgorithmParameters algorithmParameters = encryptor.decodeAlgorithmParameters(algorithmParams);
return algorithmParameters;
}
else
{
return null;
}
}
/**
* Decode cipher algorithm parameters from the HTTP method
*
* @param req
* @return decoded algorithm parameters
* @throws IOException
*/
protected AlgorithmParameters decodeAlgorithmParameters(HttpServletRequest req) throws IOException
{
String header = req.getHeader(HEADER_ALGORITHM_PARAMETERS);
if(header != null)
{
byte[] algorithmParams = Base64.decode(header);
AlgorithmParameters algorithmParameters = encryptor.decodeAlgorithmParameters(algorithmParams);
return algorithmParameters;
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public byte[] decryptResponseBody(HttpMethod method) throws IOException
{
// TODO fileoutputstream if content is especially large?
InputStream body = method.getResponseBodyAsStream();
if(body != null)
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
FileCopyUtils.copy(body, out);
AlgorithmParameters params = decodeAlgorithmParameters(method);
if(params != null)
{
byte[] decrypted = encryptor.decrypt(KeyProvider.ALIAS_SOLR, params, out.toByteArray());
return decrypted;
}
else
{
throw new AlfrescoRuntimeException("Unable to decrypt response body, missing encryption algorithm parameters");
}
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public byte[] decryptBody(HttpServletRequest req) throws IOException
{
if(req.getMethod().equals("POST"))
{
InputStream bodyStream = req.getInputStream();
if(bodyStream != null)
{
// expect algorithParameters header
AlgorithmParameters p = decodeAlgorithmParameters(req);
// decrypt the body
InputStream in = encryptor.decrypt(KeyProvider.ALIAS_SOLR, p, bodyStream);
return IOUtils.toByteArray(in);
}
else
{
return null;
}
}
else
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean authenticateResponse(HttpMethod method, String remoteIP, byte[] decryptedBody)
{
try
{
byte[] expectedMAC = getResponseMac(method);
Long timestamp = getResponseTimestamp(method);
if(timestamp == null)
{
return false;
}
remoteIP = IPUtils.getRealIPAddress(remoteIP);
return authenticate(expectedMAC, new MACInput(decryptedBody, timestamp.longValue(), remoteIP));
}
catch(Exception e)
{
throw new RuntimeException("Unable to authenticate HTTP response", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean authenticate(HttpServletRequest req, byte[] decryptedBody)
{
try
{
byte[] expectedMAC = getMac(req);
Long timestamp = getTimestamp(req);
if(timestamp == null)
{
return false;
}
String ipAddress = IPUtils.getRealIPAddress(req.getRemoteAddr());
return authenticate(expectedMAC, new MACInput(decryptedBody, timestamp.longValue(), ipAddress));
}
catch(Exception e)
{
throw new AlfrescoRuntimeException("Unable to authenticate HTTP request", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void setRequestAuthentication(HttpMethod method, byte[] message) throws IOException
{
long requestTimestamp = System.currentTimeMillis();
// add MAC header
byte[] mac = macUtils.generateMAC(KeyProvider.ALIAS_SOLR, new MACInput(message, requestTimestamp, getLocalIPAddress()));
if(logger.isDebugEnabled())
{
logger.debug("Setting MAC " + Arrays.toString(mac) + " on HTTP request " + method.getPath());
logger.debug("Setting timestamp " + requestTimestamp + " on HTTP request " + method.getPath());
}
setRequestMac(method, mac);
// prevent replays
setRequestTimestamp(method, requestTimestamp);
}
/**
* {@inheritDoc}
*/
@Override
public void setResponseAuthentication(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
byte[] responseBody, AlgorithmParameters params) throws IOException
{
long responseTimestamp = System.currentTimeMillis();
byte[] mac = macUtils.generateMAC(KeyProvider.ALIAS_SOLR,
new MACInput(responseBody, responseTimestamp, getLocalIPAddress()));
if(logger.isDebugEnabled())
{
logger.debug("Setting MAC " + Arrays.toString(mac) + " on HTTP response to request " + httpRequest.getRequestURI());
logger.debug("Setting timestamp " + responseTimestamp + " on HTTP response to request " + httpRequest.getRequestURI());
}
setAlgorithmParameters(httpResponse, params);
setMac(httpResponse, mac);
// prevent replays
setTimestamp(httpResponse, responseTimestamp);
}
protected boolean authenticate(byte[] expectedMAC, MACInput macInput)
{
// check the MAC and, if valid, check that the timestamp is under the threshold and that the remote IP is
// the expected IP
boolean authorized = macUtils.validateMAC(KeyProvider.ALIAS_SOLR, expectedMAC, macInput) &&
validateTimestamp(macInput.getTimestamp());
return authorized;
}
protected boolean validateTimestamp(long timestamp)
{
long currentTime = System.currentTimeMillis();
return((currentTime - timestamp) < messageTimeout);
}
}

View File

@@ -0,0 +1,268 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.PropertyCheck;
/**
* @author Derek Hulley
* @since 4.0
*/
public class DefaultEncryptor extends AbstractEncryptor
{
private boolean cacheCiphers = true;
private final ThreadLocal<Map<CipherKey, CachedCipher>> threadCipher;
/**
* Default constructor for IOC
*/
public DefaultEncryptor()
{
threadCipher = new ThreadLocal<Map<CipherKey, CachedCipher>>();
}
/**
* Convenience constructor for tests
*/
/* package */ DefaultEncryptor(KeyProvider keyProvider, String cipherAlgorithm, String cipherProvider)
{
this();
setKeyProvider(keyProvider);
setCipherAlgorithm(cipherAlgorithm);
setCipherProvider(cipherProvider);
}
public void init()
{
super.init();
PropertyCheck.mandatory(this, "cipherAlgorithm", cipherAlgorithm);
}
public void setCacheCiphers(boolean cacheCiphers)
{
this.cacheCiphers = cacheCiphers;
}
protected Cipher createCipher(int mode, String algorithm, String provider, Key key, AlgorithmParameters params)
throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, InvalidAlgorithmParameterException
{
Cipher cipher = null;
if (cipherProvider == null)
{
cipher = Cipher.getInstance(algorithm);
}
else
{
cipher = Cipher.getInstance(algorithm, provider);
}
cipher.init(mode, key, params);
return cipher;
}
protected Cipher getCachedCipher(String keyAlias, int mode, AlgorithmParameters params, Key key)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidAlgorithmParameterException
{
CachedCipher cipherInfo = null;
Cipher cipher = null;
Map<CipherKey, CachedCipher> ciphers = threadCipher.get();
if(ciphers == null)
{
ciphers = new HashMap<CipherKey, CachedCipher>(5);
threadCipher.set(ciphers);
}
cipherInfo = ciphers.get(new CipherKey(keyAlias, mode));
if(cipherInfo == null)
{
cipher = createCipher(mode, cipherAlgorithm, cipherProvider, key, params);
ciphers.put(new CipherKey(keyAlias, mode), new CachedCipher(cipher, key));
// Done
if (logger.isDebugEnabled())
{
logger.debug("Cipher constructed: alias=" + keyAlias + "; mode=" + mode + ": " + cipher);
}
}
else
{
// the key has changed, re-construct the cipher
if(cipherInfo.getKey() != key)
{
// key has changed, rendering the cached cipher out of date. Re-create the cipher with
// the new key.
cipher = createCipher(mode, cipherAlgorithm, cipherProvider, key, params);
ciphers.put(new CipherKey(keyAlias, mode), new CachedCipher(cipher, key));
}
else
{
cipher = cipherInfo.getCipher();
}
}
return cipher;
}
@Override
public Cipher getCipher(String keyAlias, AlgorithmParameters params, int mode)
{
Cipher cipher = null;
// Get the encryption key
Key key = keyProvider.getKey(keyAlias);
if(key == null)
{
// No encryption possible
return null;
}
try
{
if(cacheCiphers)
{
cipher = getCachedCipher(keyAlias, mode, params, key);
}
else
{
cipher = createCipher(mode, cipherAlgorithm, cipherProvider, key, params);
}
}
catch (Exception e)
{
throw new AlfrescoRuntimeException(
"Failed to construct cipher: alias=" + keyAlias + "; mode=" + mode,
e);
}
return cipher;
}
public boolean keyAvailable(String keyAlias)
{
return keyProvider.getKey(keyAlias) != null;
}
private static class CipherKey
{
private String keyAlias;
private int mode;
public CipherKey(String keyAlias, int mode)
{
super();
this.keyAlias = keyAlias;
this.mode = mode;
}
public String getKeyAlias()
{
return keyAlias;
}
public int getMode()
{
return mode;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result
+ ((keyAlias == null) ? 0 : keyAlias.hashCode());
result = prime * result + mode;
return result;
}
@Override
public boolean equals(Object obj)
{
if(this == obj)
{
return true;
}
if(!(obj instanceof CipherKey))
{
return false;
}
CipherKey other = (CipherKey)obj;
if(keyAlias == null)
{
if (other.keyAlias != null)
{
return false;
}
}
else if(!keyAlias.equals(other.keyAlias))
{
return false;
}
if(mode != other.mode)
{
return false;
}
return true;
}
}
/*
* Stores a cipher and the key used to construct it.
*/
private static class CachedCipher
{
private Key key;
private Cipher cipher;
public CachedCipher(Cipher cipher, Key key)
{
super();
this.cipher = cipher;
this.key = key;
}
public Cipher getCipher()
{
return cipher;
}
public Key getKey()
{
return key;
}
}
}

View File

@@ -0,0 +1,230 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.InputStream;
import java.io.Serializable;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.Pair;
/**
* The fallback encryptor provides a fallback mechanism for decryption, first using the default
* encryption keys and, if they fail (perhaps because they have been changed), falling back
* to a backup set of keys.
*
* Note that encryption will be performed only using the default encryption keys.
*
* @since 4.0
*/
public class DefaultFallbackEncryptor implements FallbackEncryptor
{
private Encryptor fallback;
private Encryptor main;
public DefaultFallbackEncryptor()
{
}
public DefaultFallbackEncryptor(Encryptor main, Encryptor fallback)
{
this();
this.main = main;
this.fallback = fallback;
}
public void setFallback(Encryptor fallback)
{
this.fallback = fallback;
}
public void setMain(Encryptor main)
{
this.main = main;
}
/**
* {@inheritDoc}
*/
@Override
public Pair<byte[], AlgorithmParameters> encrypt(String keyAlias,
AlgorithmParameters params, byte[] input)
{
// Note: encrypt supported only for main encryptor
Pair<byte[], AlgorithmParameters> ret = main.encrypt(keyAlias, params, input);
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public byte[] decrypt(String keyAlias, AlgorithmParameters params,
byte[] input)
{
byte[] ret;
// for decryption, try the main encryptor. If that fails (possibly as a result of the keys being updated),
// fall back to fallback encryptor.
try
{
ret = main.decrypt(keyAlias, params, input);
}
catch(Throwable e)
{
ret = fallback.decrypt(keyAlias, params, input);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public InputStream decrypt(String keyAlias, AlgorithmParameters params,
InputStream in)
{
InputStream ret;
// for decryption, try the main encryptor. If that fails (possibly as a result of the keys being updated),
// fall back to fallback encryptor.
try
{
ret = main.decrypt(keyAlias, params, in);
}
catch(Throwable e)
{
ret = fallback.decrypt(keyAlias, params, in);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public Pair<byte[], AlgorithmParameters> encryptObject(String keyAlias,
AlgorithmParameters params, Object input)
{
// Note: encrypt supported only for main encryptor
Pair<byte[], AlgorithmParameters> ret = main.encryptObject(keyAlias, params, input);
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public Object decryptObject(String keyAlias, AlgorithmParameters params,
byte[] input)
{
Object ret;
// for decryption, try the main encryptor. If that fails (possibly as a result of the keys being updated),
// fall back to fallback encryptor.
try
{
ret = main.decryptObject(keyAlias, params, input);
}
catch(Throwable e)
{
ret = fallback.decryptObject(keyAlias, params, input);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public Serializable sealObject(String keyAlias, AlgorithmParameters params,
Serializable input)
{
// Note: encrypt supported only for main encryptor
Serializable ret = main.sealObject(keyAlias, params, input);
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public Serializable unsealObject(String keyAlias, Serializable input)
throws InvalidKeyException
{
Serializable ret;
// for decryption, try the main encryptor. If that fails (possibly as a result of the keys being updated),
// fall back to fallback encryptor.
try
{
ret = main.unsealObject(keyAlias, input);
}
catch(Throwable e)
{
ret = fallback.unsealObject(keyAlias, input);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public AlgorithmParameters decodeAlgorithmParameters(byte[] encoded)
{
AlgorithmParameters ret;
try
{
ret = main.decodeAlgorithmParameters(encoded);
}
catch(AlfrescoRuntimeException e)
{
ret = fallback.decodeAlgorithmParameters(encoded);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public boolean keyAvailable(String keyAlias)
{
return main.keyAvailable(keyAlias);
}
/**
* {@inheritDoc}
*/
@Override
public boolean backupKeyAvailable(String keyAlias)
{
return fallback.keyAvailable(keyAlias);
}
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2005-2010 Alfresco Software, Ltd. All rights reserved.
*
* License rights for this program may be obtained from Alfresco Software, Ltd.
* pursuant to a written agreement and any use of this program without such an
* agreement is prohibited.
*/
package org.alfresco.encryption;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
/**
* An output stream that encrypts data to another output stream. A lightweight yet secure hybrid encryption scheme is
* used. A random symmetric key is generated and encrypted using the receiver's public key. The supplied data is then
* encrypted using the symmetric key and sent to the underlying stream on a streaming basis. An HMAC checksum is also
* computed on an ongoing basis and appended to the output when the stream is closed. This class can be used in
* conjunction with {@link DecryptingInputStream} to transport data securely.
*/
public class EncryptingOutputStream extends OutputStream
{
/** The wrapped stream. */
private final OutputStream wrapped;
/** The output cipher. */
private final Cipher outputCipher;
/** The MAC generator. */
private final Mac mac;
/** Internal buffer for MAC computation. */
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
/** A DataOutputStream on top of our interal buffer. */
private final DataOutputStream dataStr = new DataOutputStream(this.buffer);
/**
* Constructs an EncryptingOutputStream using default symmetric encryption parameters.
*
* @param wrapped
* outputstream to store the encrypted data
* @param receiverKey
* the receiver's public key for encrypting the symmetric key
* @param rand
* a secure source of randomness
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws NoSuchAlgorithmException
* the no such algorithm exception
* @throws NoSuchPaddingException
* the no such padding exception
* @throws InvalidKeyException
* the invalid key exception
* @throws BadPaddingException
* the bad padding exception
* @throws IllegalBlockSizeException
* the illegal block size exception
*/
public EncryptingOutputStream(final OutputStream wrapped, final PublicKey receiverKey, final SecureRandom rand)
throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException
{
this(wrapped, receiverKey, "AES", rand, 128, "CBC", "PKCS5PADDING");
}
/**
* Constructs an EncryptingOutputStream.
*
* @param wrapped
* outputstream to store the encrypted data
* @param receiverKey
* the receiver's public key for encrypting the symmetric key
* @param algorithm
* symmetric encryption algorithm (e.g. "AES")
* @param rand
* a secure source of randomness
* @param strength
* the key size in bits (e.g. 128)
* @param mode
* encryption mode (e.g. "CBC")
* @param padding
* padding scheme (e.g. "PKCS5PADDING")
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws NoSuchAlgorithmException
* the no such algorithm exception
* @throws NoSuchPaddingException
* the no such padding exception
* @throws InvalidKeyException
* the invalid key exception
* @throws BadPaddingException
* the bad padding exception
* @throws IllegalBlockSizeException
* the illegal block size exception
*/
public EncryptingOutputStream(final OutputStream wrapped, final PublicKey receiverKey, final String algorithm,
final SecureRandom rand, final int strength, final String mode, final String padding) throws IOException,
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// Initialise
this.wrapped = wrapped;
// Generate a random symmetric key
final KeyGenerator keyGen = KeyGenerator.getInstance(algorithm);
keyGen.init(strength, rand);
final Key symKey = keyGen.generateKey();
// Instantiate Symmetric cipher for encryption.
this.outputCipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding);
this.outputCipher.init(Cipher.ENCRYPT_MODE, symKey, rand);
// Set up HMAC
this.mac = Mac.getInstance("HMACSHA1");
final byte[] macKeyBytes = new byte[20];
rand.nextBytes(macKeyBytes);
final Key macKey = new SecretKeySpec(macKeyBytes, "HMACSHA1");
this.mac.init(macKey);
// Set up RSA to encrypt symmetric key
final Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING");
rsa.init(Cipher.ENCRYPT_MODE, receiverKey, rand);
// Write the header
// Write out an RSA-encrypted block for the key of the cipher.
writeBlock(rsa.doFinal(symKey.getEncoded()));
// Write out RSA-encrypted Initialisation Vector block
writeBlock(rsa.doFinal(this.outputCipher.getIV()));
// Write out key for HMAC.
writeBlock(this.outputCipher.doFinal(macKey.getEncoded()));
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#write(int)
*/
@Override
public void write(final int b) throws IOException
{
write(new byte[]
{
(byte) b
}, 0, 1);
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#write(byte[])
*/
@Override
public void write(final byte b[]) throws IOException
{
write(b, 0, b.length);
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#write(byte[], int, int)
*/
@Override
public void write(final byte b[], final int off, final int len) throws IOException
{
if (b == null)
{
throw new NullPointerException();
}
else if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0)
{
throw new IndexOutOfBoundsException();
}
else if (len == 0)
{
return;
}
final byte[] out = this.outputCipher.update(b, off, len); // Encrypt data.
if (out != null && out.length > 0)
{
writeBlock(out);
}
}
/**
* Writes a block of data, preceded by its length, and adds it to the HMAC checksum.
*
* @param out
* the data to be written.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private void writeBlock(final byte[] out) throws IOException
{
this.dataStr.writeInt(out.length); // Write length.
this.dataStr.write(out); // Write encrypted data.
this.dataStr.flush();
final byte[] block = this.buffer.toByteArray();
this.buffer.reset();
this.mac.update(block);
this.wrapped.write(block);
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#flush()
*/
@Override
public void flush() throws IOException
{
this.wrapped.flush();
}
/*
* (non-Javadoc)
* @see java.io.OutputStream#close()
*/
@Override
public void close() throws IOException
{
try
{
// Write the last block
writeBlock(this.outputCipher.doFinal());
}
catch (final GeneralSecurityException e)
{
throw new RuntimeException(e);
}
// Write the MAC code
writeBlock(this.mac.doFinal());
this.wrapped.close();
this.dataStr.close();
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
import java.util.List;
import java.util.Set;
/**
* Stores registered encryption keys.
*
* @since 4.0
*
*/
public interface EncryptionKeysRegistry
{
public static enum KEY_STATUS
{
OK, CHANGED, MISSING;
};
/**
* Is the key with alias 'keyAlias' registered?
* @param keyAlias String
* @return boolean
*/
public boolean isKeyRegistered(String keyAlias);
/**
* Register the key.
*
* @param keyAlias String
* @param key Key
*/
public void registerKey(String keyAlias, Key key);
/**
* Unregister the key.
*
* @param keyAlias String
*/
public void unregisterKey(String keyAlias);
/**
* Check the validity of the key against the registry.
*
* @param keyAlias String
* @param key Key
* @return KEY_STATUS
*/
public KEY_STATUS checkKey(String keyAlias, Key key);
/**
* Remove the set of keys from the registry.
*
* @param keys Set<String>
*/
public void removeRegisteredKeys(Set<String> keys);
/**
* Return those keys in the set that have been registered.
*
* @param keys Set<String>
* @return List<String>
*/
public List<String> getRegisteredKeys(Set<String> keys);
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.IOException;
import java.security.AlgorithmParameters;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.HttpMethod;
/**
* Various encryption utility methods.
*
* @since 4.0
*/
public interface EncryptionUtils
{
/**
* Decrypt the response body of the http method
*
* @param method
* @return decrypted response body
* @throws IOException
*/
public byte[] decryptResponseBody(HttpMethod method) throws IOException;
/**
* Decrypt the body of the http request
*
* @param req
* @return decrypted response body
* @throws IOException
*/
public byte[] decryptBody(HttpServletRequest req) throws IOException;
/**
* Authenticate the http method response: validate the MAC, check that the remote IP is
* as expected and that the timestamp is recent.
*
* @param method
* @param remoteIP
* @param decryptedBody
* @return true if the method reponse is authentic, false otherwise
*/
public boolean authenticateResponse(HttpMethod method, String remoteIP, byte[] decryptedBody);
/**
* Authenticate the http request: validate the MAC, check that the remote IP is
* as expected and that the timestamp is recent.
*
* @param req
* @param decryptedBody
* @return true if the method request is authentic, false otherwise
*/
public boolean authenticate(HttpServletRequest req, byte[] decryptedBody);
/**
* Encrypt the http method request body
*
* @param method
* @param message
* @throws IOException
*/
public void setRequestAuthentication(HttpMethod method, byte[] message) throws IOException;
/**
* Sets authentication headers on the HTTP response.
*
* @param httpRequest
* @param httpResponse
* @param responseBody
* @param params
* @throws IOException
*/
public void setResponseAuthentication(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
byte[] responseBody, AlgorithmParameters params) throws IOException;
/**
* Set the algorithm parameters header on the method request
*
* @param method
* @param params
* @throws IOException
*/
public void setRequestAlgorithmParameters(HttpMethod method, AlgorithmParameters params) throws IOException;
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.InputStream;
import java.io.Serializable;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import org.alfresco.util.Pair;
/**
* Interface providing methods to encrypt and decrypt data.
*
* @since 4.0
*/
public interface Encryptor
{
/**
* Encrypt some bytes
*
* @param keyAlias the encryption key alias
* @param input the data to encrypt
* @return the encrypted data and parameters used
*/
Pair<byte[], AlgorithmParameters> encrypt(String keyAlias, AlgorithmParameters params, byte[] input);
/**
* Decrypt some bytes
*
* @param keyAlias the encryption key alias
* @param input the data to decrypt
* @return the unencrypted data
*/
byte[] decrypt(String keyAlias, AlgorithmParameters params, byte[] input);
/**
* Decrypt an input stream
*
* @param keyAlias the encryption key alias
* @param in the data to decrypt
* @return the unencrypted data
*/
InputStream decrypt(String keyAlias, AlgorithmParameters params, InputStream in);
/**
* Encrypt an object
*
* @param keyAlias the encryption key alias
* @param input the object to write to bytes
* @return the encrypted data and parameters used
*/
Pair<byte[], AlgorithmParameters> encryptObject(String keyAlias, AlgorithmParameters params, Object input);
/**
* Decrypt data as an object
*
* @param keyAlias the encryption key alias
* @param input the data to decrypt
* @return the unencrypted data deserialized
*/
Object decryptObject(String keyAlias, AlgorithmParameters params, byte[] input);
/**
* Convenience method to seal on object up cryptographically.
* <p/>
* Note that the original object may be returned directly if there is no key associated with
* the alias.
*
* @param keyAlias the encryption key alias
* @param input the object to encrypt and seal
* @return the sealed object that can be decrypted with the original key
*/
Serializable sealObject(String keyAlias, AlgorithmParameters params, Serializable input);
/**
* Convenience method to unseal on object sealed up cryptographically.
* <p/>
* Note that the algorithm parameters not provided on the assumption that a symmetric key
* algorithm is in use - only the key is required for unsealing.
* <p/>
* Note that the original object may be returned directly if there is no key associated with
* the alias or if the input object is not a <code>SealedObject</code>.
*
* @param keyAlias the encryption key alias
* @param input the object to decrypt and unseal
* @return the original unsealed object that was encrypted with the original key
* @throws IllegalStateException if the key alias is not valid <b>and</b> the input is a
* <tt>SealedObject</tt>
*/
Serializable unsealObject(String keyAlias, Serializable input) throws InvalidKeyException;
/**
* Decodes encoded cipher algorithm parameters
*
* @param encoded the encoded cipher algorithm parameters
* @return the decoded cipher algorithmParameters
*/
AlgorithmParameters decodeAlgorithmParameters(byte[] encoded);
boolean keyAvailable(String keyAlias);
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
/**
* A fallback encryptor provides a fallback mechanism for decryption, first using the default
* encryption keys and, if they fail (perhaps because they have been changed), falling back
* to a backup set of keys.
*
* Note that encryption will be performed only using the default encryption keys.
*
* @since 4.0
*/
public interface FallbackEncryptor extends Encryptor
{
/**
* Is the backup key available in order to fall back to?
*
* @return boolean
*/
boolean backupKeyAvailable(String keyAlias);
}

View File

@@ -0,0 +1,47 @@
package org.alfresco.encryption;
import java.security.SecureRandom;
import javax.crypto.spec.DESedeKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
*
* Generate a secret key for use by the repository.
*
* @since 4.0
*
*/
public class GenerateSecretKey
{
public byte[] generateKeyData()
{
try
{
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(System.currentTimeMillis());
byte bytes[] = new byte[DESedeKeySpec.DES_EDE_KEY_LEN];
random.nextBytes(bytes);
return bytes;
}
catch(Exception e)
{
throw new RuntimeException("Unable to generate secret key", e);
}
}
public static void main(String args[])
{
try
{
GenerateSecretKey gen = new GenerateSecretKey();
byte[] bytes = gen.generateKeyData();
System.out.print(Base64.encodeBase64String(bytes));
}
catch(Throwable e)
{
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
/**
*
* @since 4.0
*
*/
public class InvalidKeystoreException extends Exception
{
private static final long serialVersionUID = -1324791685965572313L;
public InvalidKeystoreException(String message)
{
super(message);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* A simple map of key aliases to keys. Each key has an associated timestamp indicating
* when it was last loaded from the keystore on disk.
*
* @since 4.0
*
*/
public class KeyMap
{
private Map<String, CachedKey> keys;
public KeyMap()
{
this.keys = new HashMap<String, CachedKey>(5);
}
public KeyMap(Map<String, CachedKey> keys)
{
super();
this.keys = keys;
}
public int numKeys()
{
return keys.size();
}
public Set<String> getKeyAliases()
{
return keys.keySet();
}
// always returns an instance; if null will return a CachedKey.NULL
public CachedKey getCachedKey(String keyAlias)
{
CachedKey cachedKey = keys.get(keyAlias);
return (cachedKey != null ? cachedKey : CachedKey.NULL);
}
public Key getKey(String keyAlias)
{
return getCachedKey(keyAlias).getKey();
}
public void setKey(String keyAlias, Key key)
{
keys.put(keyAlias, new CachedKey(key));
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
/**
* A key provider returns the secret keys for different use cases.
*
* @since 4.0
*/
public interface KeyProvider
{
// TODO: Allow the aliases to be configured i.e. include an alias mapper
/**
* Constant representing the keystore alias for keys to encrypt/decrypt node metadata
*/
public static final String ALIAS_METADATA = "metadata";
/**
* Constant representing the keystore alias for keys to encrypt/decrypt SOLR transfer data
*/
public static final String ALIAS_SOLR = "solr";
/**
* Get an encryption key if available.
*
* @param keyAlias the key alias
* @return the encryption key and a timestamp of when it was last changed
*/
public Key getKey(String keyAlias);
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* Manages key resources (key store and key store passwords)
*
* @since 4.0
*
*/
public interface KeyResourceLoader
{
/**
* Loads and returns an InputStream of the key store at the configured location.
* If the file cannot be found this method returns null.
*
* @return InputStream
* @throws FileNotFoundException
*/
public InputStream getKeyStore(String keyStoreLocation) throws FileNotFoundException;
/**
* Loads key metadata from the configured passwords file location.
*
* Note that the passwords are not cached locally.
* If the file cannot be found this method returns null.
*
* @return Properties
* @throws IOException
*/
public Properties loadKeyMetaData(String keyMetaDataFileLocation) throws IOException, FileNotFoundException;
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import org.alfresco.util.PropertyCheck;
/**
* Stores Java keystore initialisation parameters.
*
* @since 4.0
*
*/
public class KeyStoreParameters
{
private String name;
private String type;
private String provider;
private String keyMetaDataFileLocation;
private String location;
public KeyStoreParameters()
{
}
public KeyStoreParameters(String name, String type, String keyStoreProvider,
String keyMetaDataFileLocation, String location)
{
super();
this.name = name;
this.type = type;
this.provider = keyStoreProvider;
this.keyMetaDataFileLocation = keyMetaDataFileLocation;
this.location = location;
}
public void init()
{
if (!PropertyCheck.isValidPropertyString(getLocation()))
{
setLocation(null);
}
if (!PropertyCheck.isValidPropertyString(getProvider()))
{
setProvider(null);
}
if (!PropertyCheck.isValidPropertyString(getType()))
{
setType(null);
}
if (!PropertyCheck.isValidPropertyString(getKeyMetaDataFileLocation()))
{
setKeyMetaDataFileLocation(null);
}
}
public String getName()
{
return name;
}
public String getType()
{
return type;
}
public String getProvider()
{
return provider;
}
public String getKeyMetaDataFileLocation()
{
return keyMetaDataFileLocation;
}
public String getLocation()
{
return location;
}
public void setName(String name)
{
this.name = name;
}
public void setType(String type)
{
this.type = type;
}
public void setProvider(String provider)
{
this.provider = provider;
}
public void setKeyMetaDataFileLocation(String keyMetaDataFileLocation)
{
this.keyMetaDataFileLocation = keyMetaDataFileLocation;
}
public void setLocation(String location)
{
this.location = location;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.util.List;
/**
* A report on which keys have changed and which keys have not changed.
*
* @since 4.0
*
*/
public class KeysReport
{
private List<String> keysChanged;
private List<String> keysUnchanged;
public KeysReport(List<String> keysChanged, List<String> keysUnchanged)
{
super();
this.keysChanged = keysChanged;
this.keysUnchanged = keysUnchanged;
}
public List<String> getKeysChanged()
{
return keysChanged;
}
public List<String> getKeysUnchanged()
{
return keysUnchanged;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.security.Key;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
*
* Provides system-wide secret keys for symmetric database encryption from a key store
* in the filesystem. Just wraps a key store.
*
* @author Derek Hulley
* @since 4.0
*/
public class KeystoreKeyProvider extends AbstractKeyProvider
{
private static final Log logger = LogFactory.getLog(KeystoreKeyProvider.class);
private AlfrescoKeyStore keyStore;
private boolean useBackupKeys = false;
/**
* Constructs the provider with required defaults
*/
public KeystoreKeyProvider()
{
}
public KeystoreKeyProvider(KeyStoreParameters keyStoreParameters, KeyResourceLoader keyResourceLoader)
{
this();
this.keyStore = new AlfrescoKeyStoreImpl(keyStoreParameters, keyResourceLoader);
init();
}
public void setUseBackupKeys(boolean useBackupKeys)
{
this.useBackupKeys = useBackupKeys;
}
/**
*
* @param keyStore
*/
public KeystoreKeyProvider(AlfrescoKeyStore keyStore)
{
this();
this.keyStore = keyStore;
init();
}
public void setKeyStore(AlfrescoKeyStore keyStore)
{
this.keyStore = keyStore;
}
public void init()
{
}
/**
* {@inheritDoc}
*/
@Override
public Key getKey(String keyAlias)
{
if(useBackupKeys)
{
return keyStore.getBackupKey(keyAlias);
}
else
{
return keyStore.getKey(keyAlias);
}
}
}

View File

@@ -0,0 +1,291 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Key;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.crypto.Mac;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Provides support for generating and checking MACs (Message Authentication Codes) using Alfresco's
* secret keys.
*
* @since 4.0
*
*/
public class MACUtils
{
private static Log logger = LogFactory.getLog(Encryptor.class);
private static byte SEPARATOR = 0;
private final ThreadLocal<Mac> threadMac;
private KeyProvider keyProvider;
private String macAlgorithm;
/**
* Default constructor for IOC
*/
public MACUtils()
{
threadMac = new ThreadLocal<Mac>();
}
public void setKeyProvider(KeyProvider keyProvider)
{
this.keyProvider = keyProvider;
}
public void setMacAlgorithm(String macAlgorithm)
{
this.macAlgorithm = macAlgorithm;
}
protected Mac getMac(String keyAlias) throws Exception
{
Mac mac = threadMac.get();
if(mac == null)
{
mac = Mac.getInstance(macAlgorithm);
threadMac.set(mac);
}
Key key = keyProvider.getKey(keyAlias);
if(key == null)
{
throw new AlfrescoRuntimeException("Unexpected null key for key alias " + keyAlias);
}
mac.init(key);
return mac;
}
protected byte[] longToByteArray(long l) throws IOException
{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeLong(l);
dos.flush();
return bos.toByteArray();
}
public byte[] generateMAC(String keyAlias, MACInput macInput)
{
try
{
InputStream fullMessage = macInput.getMACInput();
if(logger.isDebugEnabled())
{
logger.debug("Generating MAC for " + macInput + "...");
}
Mac mac = getMac(keyAlias);
byte[] buf = new byte[1024];
int len;
while((len = fullMessage.read(buf, 0, 1024)) != -1)
{
mac.update(buf, 0, len);
}
byte[] newMAC = mac.doFinal();
if(logger.isDebugEnabled())
{
logger.debug("...done. MAC is " + Arrays.toString(newMAC));
}
return newMAC;
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to generate MAC", e);
}
}
/**
* Compares the expectedMAC against the MAC generated from
* Assumes message has been decrypted
* @param keyAlias String
* @param expectedMAC byte[]
* @param macInput MACInput
* @return boolean
*/
public boolean validateMAC(String keyAlias, byte[] expectedMAC, MACInput macInput)
{
try
{
byte[] mac = generateMAC(keyAlias, macInput);
if(logger.isDebugEnabled())
{
logger.debug("Validating expected MAC " + Arrays.toString(expectedMAC) + " against mac " + Arrays.toString(mac) + " for MAC input " + macInput + "...");
}
boolean areEqual = Arrays.equals(expectedMAC, mac);
if(logger.isDebugEnabled())
{
logger.debug(areEqual ? "...MAC validation succeeded." : "...MAC validation failed.");
}
return areEqual;
}
catch (Exception e)
{
throw new AlfrescoRuntimeException("Failed to validate MAC", e);
}
}
/**
* Represents the information to be fed into the MAC generator
*
* @since 4.0
*
*/
public static class MACInput
{
// The message, may be null
private InputStream message;
private long timestamp;
private String ipAddress;
public MACInput(byte[] message, long timestamp, String ipAddress)
{
this.message = (message != null ? new ByteArrayInputStream(message) : null);
this.timestamp = timestamp;
this.ipAddress = ipAddress;
}
public InputStream getMessage()
{
return message;
}
public long getTimestamp()
{
return timestamp;
}
public String getIpAddress()
{
return ipAddress;
}
public InputStream getMACInput() throws IOException
{
List<InputStream> inputStreams = new ArrayList<InputStream>();
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bytes);
out.writeUTF(ipAddress);
out.writeByte(SEPARATOR);
out.writeLong(timestamp);
inputStreams.add(new ByteArrayInputStream(bytes.toByteArray()));
if(message != null)
{
inputStreams.add(message);
}
return new MessageInputStream(inputStreams);
}
public String toString()
{
StringBuilder sb = new StringBuilder("MACInput[");
sb.append("timestamp: ").append(getTimestamp());
sb.append("ipAddress: ").append(getIpAddress());
return sb.toString();
}
}
private static class MessageInputStream extends InputStream
{
private List<InputStream> input;
private InputStream activeInputStream;
private int currentStream = 0;
public MessageInputStream(List<InputStream> input)
{
this.input = input;
this.currentStream = 0;
this.activeInputStream = input.get(currentStream);
}
@Override
public void close() throws IOException
{
IOException firstIOException = null;
for(InputStream in : input)
{
try
{
in.close();
}
catch(IOException e)
{
if(firstIOException == null)
{
firstIOException = e;
}
}
}
if(firstIOException != null)
{
throw firstIOException;
}
}
@Override
public int read() throws IOException
{
int i = activeInputStream.read();
if(i == -1)
{
currentStream++;
if(currentStream >= input.size())
{
return -1;
}
else
{
activeInputStream = input.get(currentStream);
i = activeInputStream.read();
}
}
return i;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
/**
*
* @since 4.0
*
*/
public class MissingKeyException extends Exception
{
private static final long serialVersionUID = -7843412242954504581L;
private String keyAlias;
private String keyStoreLocation;
public MissingKeyException(String message)
{
super(message);
}
public MissingKeyException(String keyAlias, String keyStoreLocation)
{
// TODO i18n
super("Key " + keyAlias + " is missing from keystore " + keyStoreLocation);
}
public String getKeyAlias()
{
return keyAlias;
}
public String getKeyStoreLocation()
{
return keyStoreLocation;
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.util.ResourceUtils;
/**
* Loads key resources (key store and key store passwords) from the Spring classpath.
*
* @since 4.0
*
*/
public class SpringKeyResourceLoader implements KeyResourceLoader, ApplicationContextAware
{
/**
* The application context might not be available, in which case the usual URL
* loading is used.
*/
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
/**
* Helper method to switch between application context resource loading or
* simpler current classloader resource loading.
*/
private InputStream getSafeInputStream(String location)
{
try
{
final InputStream is;
if (applicationContext != null)
{
Resource resource = applicationContext.getResource(location);
if (resource.exists())
{
is = new BufferedInputStream(resource.getInputStream());
}
else
{
// Fall back to conventional loading
File file = ResourceUtils.getFile(location);
if (file.exists())
{
is = new BufferedInputStream(new FileInputStream(file));
}
else
{
is = null;
}
}
}
else
{
// Load conventionally (i.e. we are in a unit test)
File file = ResourceUtils.getFile(location);
if (file.exists())
{
is = new BufferedInputStream(new FileInputStream(file));
}
else
{
is = null;
}
}
return is;
}
catch (IOException e)
{
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public InputStream getKeyStore(String keyStoreLocation)
{
if (keyStoreLocation == null)
{
return null;
}
return getSafeInputStream(keyStoreLocation);
}
/**
* {@inheritDoc}
*/
@Override
public Properties loadKeyMetaData(String keyMetaDataFileLocation) throws IOException
{
if (keyMetaDataFileLocation == null)
{
return null;
}
try
{
InputStream is = getSafeInputStream(keyMetaDataFileLocation);
if (is == null)
{
return null;
}
else
{
try
{
Properties p = new Properties();
p.load(is);
return p;
}
finally
{
try { is.close(); } catch (Throwable e) {}
}
}
}
catch(FileNotFoundException e)
{
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption.ssl;
/**
* <p>
* Signals fatal error in initialization of {@link AuthSSLProtocolSocketFactory}.
* </p>
*
* <p>
* Adapted from code here: http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLX509TrustManager.java?revision=608014&view=co
* </p>
*
* @since 4.0
*/
public class AuthSSLInitializationError extends Error
{
private static final long serialVersionUID = 8135341334029823112L;
/**
* Creates a new AuthSSLInitializationError.
*/
public AuthSSLInitializationError()
{
super();
}
/**
* Creates a new AuthSSLInitializationError with the specified message.
*
* @param message error message
*/
public AuthSSLInitializationError(String message)
{
super(message);
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption.ssl;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.KeyStore;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import org.alfresco.encryption.AlfrescoKeyStore;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>
* Mutual Authentication against an Alfresco repository.
*
* AuthSSLProtocolSocketFactory can be used to validate the identity of the HTTPS
* server against a list of trusted certificates and to authenticate to the HTTPS
* server using a private key.
* </p>
*
* <p>
* Adapted from code here: http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLX509TrustManager.java?revision=608014&view=co
* </p>
*
* <p>
* AuthSSLProtocolSocketFactory will enable server authentication when supplied with
* a {@link KeyStore truststore} file containing one or several trusted certificates.
* The client secure socket will reject the connection during the SSL session handshake
* if the target HTTPS server attempts to authenticate itself with a non-trusted
* certificate.
* </p>
*
* <p>
* AuthSSLProtocolSocketFactory will enable client authentication when supplied with
* a {@link KeyStore keystore} file containg a private key/public certificate pair.
* The client secure socket will use the private key to authenticate itself to the target
* HTTPS server during the SSL session handshake if requested to do so by the server.
* The target HTTPS server will in its turn verify the certificate presented by the client
* in order to establish client's authenticity
* </p>
*
*
* @since 4.0
*/
public class AuthSSLProtocolSocketFactory implements SecureProtocolSocketFactory
{
/** Log object for this class. */
private static final Log logger = LogFactory.getLog(AuthSSLProtocolSocketFactory.class);
private SSLContext sslcontext = null;
private AlfrescoKeyStore keyStore = null;
private AlfrescoKeyStore trustStore = null;
/**
* Constructor for AuthSSLProtocolSocketFactory. Either a keystore or truststore file
* must be given. Otherwise SSL context initialization error will result.
*
* @param sslKeyStore SSL parameters to use.
* @param keyResourceLoader loads key resources from an arbitrary source e.g. classpath
*/
public AuthSSLProtocolSocketFactory(AlfrescoKeyStore sslKeyStore, AlfrescoKeyStore sslTrustStore, KeyResourceLoader keyResourceLoader)
{
super();
this.keyStore = sslKeyStore;
this.trustStore = sslTrustStore;
}
private SSLContext createSSLContext()
{
KeyManager[] keymanagers = keyStore.createKeyManagers();;
TrustManager[] trustmanagers = trustStore.createTrustManagers();
try
{
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(keymanagers, trustmanagers, null);
return sslcontext;
}
catch(Throwable e)
{
throw new AlfrescoRuntimeException("Unable to create SSL context", e);
}
}
private SSLContext getSSLContext()
{
try
{
if(this.sslcontext == null)
{
this.sslcontext = createSSLContext();
}
return this.sslcontext;
}
catch(Throwable e)
{
throw new AlfrescoRuntimeException("Unable to create SSL context", e);
}
}
/**
* Attempts to get a new socket connection to the given host within the given time limit.
* <p>
* To circumvent the limitations of older JREs that do not support connect timeout a
* controller thread is executed. The controller thread attempts to create a new socket
* within the given limit of time. If socket constructor does not return until the
* timeout expires, the controller terminates and throws an {@link ConnectTimeoutException}
* </p>
*
* @param host the host name/IP
* @param port the port on the host
* @param localAddress the local host name/IP to bind the socket to
* @param localPort the port on the local machine
* @param params {@link HttpConnectionParams Http connection parameters}
*
* @return Socket a new socket
*
* @throws IOException if an I/O error occurs while creating the socket
* @throws UnknownHostException if the IP address of the host cannot be
* determined
*/
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort,
final HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException
{
SSLSocket sslSocket = null;
if(params == null)
{
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketfactory = getSSLContext().getSocketFactory();
if(timeout == 0)
{
sslSocket = (SSLSocket)socketfactory.createSocket(host, port, localAddress, localPort);
}
else
{
sslSocket = (SSLSocket)socketfactory.createSocket();
SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteaddr = new InetSocketAddress(host, port);
sslSocket.bind(localaddr);
sslSocket.connect(remoteaddr, timeout);
}
return sslSocket;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
*/
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort)
throws IOException, UnknownHostException
{
SSLSocket sslSocket = (SSLSocket)getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
return sslSocket;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
*/
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
SSLSocket sslSocket = (SSLSocket)getSSLContext().getSocketFactory().createSocket(host, port);
return sslSocket;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
*/
public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
throws IOException, UnknownHostException
{
SSLSocket sslSocket = (SSLSocket)getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
return sslSocket;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.encryption.ssl;
import org.alfresco.encryption.KeyStoreParameters;
/**
*
* @since 4.0
*
*/
public class SSLEncryptionParameters
{
private KeyStoreParameters keyStoreParameters;
private KeyStoreParameters trustStoreParameters;
/**
* Default constructor (for use by Spring)
*/
public SSLEncryptionParameters()
{
super();
}
public SSLEncryptionParameters(KeyStoreParameters keyStoreParameters, KeyStoreParameters trustStoreParameters)
{
super();
this.keyStoreParameters = keyStoreParameters;
this.trustStoreParameters = trustStoreParameters;
}
public KeyStoreParameters getKeyStoreParameters()
{
return keyStoreParameters;
}
public KeyStoreParameters getTrustStoreParameters()
{
return trustStoreParameters;
}
public void setKeyStoreParameters(KeyStoreParameters keyStoreParameters)
{
this.keyStoreParameters = keyStoreParameters;
}
public void setTrustStoreParameters(KeyStoreParameters trustStoreParameters)
{
this.trustStoreParameters = trustStoreParameters;
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.error;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.extensions.surf.util.I18NUtil;
import org.alfresco.api.AlfrescoPublicApi;
/**
* I18n'ed runtime exception thrown by Alfresco code.
*
* @author gavinc
*/
@AlfrescoPublicApi
public class AlfrescoRuntimeException extends RuntimeException
{
/**
* Serial version UUID
*/
private static final long serialVersionUID = 3787143176461219632L;
private static final String MESSAGE_DELIMITER = " ";
private String msgId;
private transient Object[] msgParams = null;
/**
* Helper factory method making use of variable argument numbers
*/
public static AlfrescoRuntimeException create(String msgId, Object ...objects)
{
return new AlfrescoRuntimeException(msgId, objects);
}
/**
* Helper factory method making use of variable argument numbers
*/
public static AlfrescoRuntimeException create(Throwable cause, String msgId, Object ...objects)
{
return new AlfrescoRuntimeException(msgId, objects, cause);
}
/**
* Utility to convert a general Throwable to a RuntimeException. No conversion is done if the
* throwable is already a <tt>RuntimeException</tt>.
*
* @see #create(Throwable, String, Object...)
*/
public static RuntimeException makeRuntimeException(Throwable e, String msgId, Object ...objects)
{
if (e instanceof RuntimeException)
{
return (RuntimeException) e;
}
// Convert it
return AlfrescoRuntimeException.create(e, msgId, objects);
}
/**
* Constructor
*
* @param msgId the message id
*/
public AlfrescoRuntimeException(String msgId)
{
super(resolveMessage(msgId, null));
this.msgId = msgId;
}
/**
* Constructor
*
* @param msgId the message id
* @param msgParams the message parameters
*/
public AlfrescoRuntimeException(String msgId, Object[] msgParams)
{
super(resolveMessage(msgId, msgParams));
this.msgId = msgId;
this.msgParams = msgParams;
}
/**
* Constructor
*
* @param msgId the message id
* @param cause the exception cause
*/
public AlfrescoRuntimeException(String msgId, Throwable cause)
{
super(resolveMessage(msgId, null), cause);
this.msgId = msgId;
}
/**
* Constructor
*
* @param msgId the message id
* @param msgParams the message parameters
* @param cause the exception cause
*/
public AlfrescoRuntimeException(String msgId, Object[] msgParams, Throwable cause)
{
super(resolveMessage(msgId, msgParams), cause);
this.msgId = msgId;
this.msgParams = msgParams;
}
/**
* @return the msgId
*/
public String getMsgId()
{
return msgId;
}
/**
* @return the msgParams
*/
public Object[] getMsgParams()
{
return msgParams;
}
/**
* @return the numericalId
*/
public String getNumericalId()
{
return getMessage().split(MESSAGE_DELIMITER)[0];
}
/**
* Resolves the message id to the localised string.
* <p>
* If a localised message can not be found then the message Id is
* returned.
*
* @param messageId the message Id
* @param params message parameters
* @return the localised message (or the message id if none found)
*/
private static String resolveMessage(String messageId, Object[] params)
{
String message = I18NUtil.getMessage(messageId, params);
if (message == null)
{
// If a localized string cannot be found then return the messageId and the params
message = messageId;
if (params != null)
{
message += " - " + Arrays.toString(params);
}
}
return buildErrorLogNumber(message);
}
/**
* Generate an error log number - based on MMDDXXXX - where M is month,
* D is day and X is an atomic integer count.
*
* @param message Message to prepend the error log number to
*
* @return message with error log number prefix
*/
private static String buildErrorLogNumber(String message)
{
// ensure message is not null
if (message == null)
{
message= "";
}
Date today = new Date();
StringBuilder buf = new StringBuilder(message.length() + 10);
padInt(buf, today.getMonth(), 2);
padInt(buf, today.getDate(), 2);
padInt(buf, errorCounter.getAndIncrement(), 4);
buf.append(MESSAGE_DELIMITER);
buf.append(message);
return buf.toString();
}
/**
* Helper to zero pad a number to specified length
*/
private static void padInt(StringBuilder buffer, int value, int length)
{
String strValue = Integer.toString(value);
for (int i = length - strValue.length(); i > 0; i--)
{
buffer.append('0');
}
buffer.append(strValue);
}
private static AtomicInteger errorCounter = new AtomicInteger();
/**
* Get the root cause.
*/
public Throwable getRootCause()
{
Throwable cause = this;
for (Throwable tmp = this; tmp != null ; tmp = cause.getCause())
{
cause = tmp;
}
return cause;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.error;
/**
* Helper class to provide information about exception stacks.
*
* @author Derek Hulley
*/
public class ExceptionStackUtil
{
/**
* Searches through the exception stack of the given throwable to find any instance
* of the possible cause. The top-level throwable will also be tested.
*
* @param throwable the exception condition to search
* @param possibleCauses the types of the exception conditions of interest
* @return Returns the first instance that matches one of the given
* possible types, or null if there is nothing in the stack
*/
public static Throwable getCause(Throwable throwable, Class<?> ... possibleCauses)
{
while (throwable != null)
{
for (Class<?> possibleCauseClass : possibleCauses)
{
Class<?> throwableClass = throwable.getClass();
if (possibleCauseClass.isAssignableFrom(throwableClass))
{
// We have a match
return throwable;
}
}
// There was no match, so dig deeper
Throwable cause = throwable.getCause();
throwable = (throwable == cause) ? null : cause;
}
// Nothing found
return null;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.error;
/**
* Helper class around outputting stack traces.
*
* @author Derek Hulley
*/
public class StackTraceUtil
{
/**
* Builds a message with the stack trace of the form:
* <pre>
* SOME MESSAGE:
* Started at:
* com.package...
* com.package...
* ...
* </pre>
*
* @param msg the initial error message
* @param stackTraceElements the stack trace elements
* @param sb the buffer to append to
* @param maxDepth the maximum number of trace elements to output. 0 or less means output all.
*/
public static void buildStackTrace(
String msg,
StackTraceElement[] stackTraceElements,
StringBuilder sb,
int maxDepth)
{
String lineEnding = System.getProperty("line.separator", "\n");
sb.append(msg).append(" ").append(lineEnding)
.append(" Started at: ").append(lineEnding);
for (int i = 0; i < stackTraceElements.length; i++)
{
if (i > maxDepth && maxDepth > 0)
{
sb.append(" ...");
break;
}
sb.append(" ").append(stackTraceElements[i]);
if (i < stackTraceElements.length - 1)
{
sb.append(lineEnding);
}
}
}
}

View File

@@ -0,0 +1,177 @@
/*
* 2011 - Alfresco Software, Ltd.
* This file was copied from org.hibernate.dialect.DialectFactory
*/
package org.alfresco.hibernate;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.util.ReflectHelper;
/**
* A factory for generating Dialect instances.
*
* @author Steve Ebersole
* @author Alfresco
*/
public class DialectFactory {
/**
* Builds an appropriate Dialect instance.
* <p/>
* If a dialect is explicitly named in the incoming properties, it is used. Otherwise, the database name and version
* (obtained from connection metadata) are used to make the dertemination.
* <p/>
* An exception is thrown if a dialect was not explicitly set and the database name is not known.
*
* @param props The configuration properties.
* @param databaseName The name of the database product (obtained from metadata).
* @param databaseMajorVersion The major version of the database product (obtained from metadata).
*
* @return The appropriate dialect.
*
* @throws HibernateException No dialect specified and database name not known.
*/
public static Dialect buildDialect(Properties props, String databaseName, int databaseMajorVersion)
throws HibernateException {
String dialectName = props.getProperty( Environment.DIALECT );
if ( dialectName == null || dialectName.length() == 0) {
return determineDialect( databaseName, databaseMajorVersion );
}
else {
// Push the dialect onto the system properties
System.setProperty(Environment.DIALECT, dialectName);
return buildDialect( dialectName );
}
}
/**
* Determine the appropriate Dialect to use given the database product name
* and major version.
*
* @param databaseName The name of the database product (obtained from metadata).
* @param databaseMajorVersion The major version of the database product (obtained from metadata).
*
* @return An appropriate dialect instance.
*/
public static Dialect determineDialect(String databaseName, int databaseMajorVersion) {
if ( databaseName == null ) {
throw new HibernateException( "Hibernate Dialect must be explicitly set" );
}
DatabaseDialectMapper mapper = ( DatabaseDialectMapper ) MAPPERS.get( databaseName );
if ( mapper == null ) {
throw new HibernateException( "Hibernate Dialect must be explicitly set for database: " + databaseName );
}
String dialectName = mapper.getDialectClass( databaseMajorVersion );
// Push the dialect onto the system properties
System.setProperty(Environment.DIALECT, dialectName);
return buildDialect( dialectName );
}
/**
* Returns a dialect instance given the name of the class to use.
*
* @param dialectName The name of the dialect class.
*
* @return The dialect instance.
*/
public static Dialect buildDialect(String dialectName) {
try {
return ( Dialect ) ReflectHelper.classForName( dialectName ).newInstance();
}
catch ( ClassNotFoundException cnfe ) {
throw new HibernateException( "Dialect class not found: " + dialectName );
}
catch ( Exception e ) {
throw new HibernateException( "Could not instantiate dialect class", e );
}
}
/**
* For a given database product name, instances of
* DatabaseDialectMapper know which Dialect to use for different versions.
*/
public static interface DatabaseDialectMapper {
public String getDialectClass(int majorVersion);
}
/**
* A simple DatabaseDialectMapper for dialects which are independent
* of the underlying database product version.
*/
public static class VersionInsensitiveMapper implements DatabaseDialectMapper {
private String dialectClassName;
public VersionInsensitiveMapper(String dialectClassName) {
this.dialectClassName = dialectClassName;
}
public String getDialectClass(int majorVersion) {
return dialectClassName;
}
}
// TODO : this is the stuff it'd be nice to move to a properties file or some other easily user-editable place
private static final Map<String, VersionInsensitiveMapper> MAPPERS = new HashMap<String, DialectFactory.VersionInsensitiveMapper>();
static {
// detectors...
MAPPERS.put( "HSQL Database Engine", new VersionInsensitiveMapper( "org.hibernate.dialect.HSQLDialect" ) );
MAPPERS.put( "H2", new VersionInsensitiveMapper( "org.hibernate.dialect.H2Dialect" ) );
MAPPERS.put( "MySQL", new VersionInsensitiveMapper( "org.hibernate.dialect.MySQLDialect" ) );
MAPPERS.put( "PostgreSQL", new VersionInsensitiveMapper( "org.hibernate.dialect.PostgreSQLDialect" ) );
MAPPERS.put( "Apache Derby", new VersionInsensitiveMapper( "org.hibernate.dialect.DerbyDialect" ) );
MAPPERS.put( "Ingres", new VersionInsensitiveMapper( "org.hibernate.dialect.IngresDialect" ) );
MAPPERS.put( "ingres", new VersionInsensitiveMapper( "org.hibernate.dialect.IngresDialect" ) );
MAPPERS.put( "INGRES", new VersionInsensitiveMapper( "org.hibernate.dialect.IngresDialect" ) );
MAPPERS.put( "Microsoft SQL Server Database", new VersionInsensitiveMapper( "org.hibernate.dialect.SQLServerDialect" ) );
MAPPERS.put( "Microsoft SQL Server", new VersionInsensitiveMapper( "org.hibernate.dialect.SQLServerDialect" ) );
MAPPERS.put( "Sybase SQL Server", new VersionInsensitiveMapper( "org.hibernate.dialect.SybaseDialect" ) );
MAPPERS.put( "Adaptive Server Enterprise", new VersionInsensitiveMapper( "org.hibernate.dialect.SybaseDialect" ) );
MAPPERS.put( "Informix Dynamic Server", new VersionInsensitiveMapper( "org.hibernate.dialect.InformixDialect" ) );
MAPPERS.put( "DB2/NT", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "DB2/LINUX", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "DB2/6000", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "DB2/HPUX", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "DB2/SUN", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "DB2/LINUX390", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "DB2/AIX64", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "DB2",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/NT",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/NT64",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2 UDP",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/LINUX",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/LINUX390",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/LINUXZ64",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/400 SQL",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/6000",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2 UDB iSeries",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/AIX64",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/HPUX",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/HP64",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/SUN",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/SUN64",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/PTX",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/2",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "DB2/LINUXX8664",new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ));
MAPPERS.put( "MySQL", new VersionInsensitiveMapper( "org.hibernate.dialect.MySQLInnoDBDialect" ) );
MAPPERS.put( "DB2/NT64", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "DB2/LINUX", new VersionInsensitiveMapper( "org.hibernate.dialect.DB2Dialect" ) );
MAPPERS.put( "Microsoft SQL Server Database", new VersionInsensitiveMapper( "org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect" ) );
MAPPERS.put( "Microsoft SQL Server", new VersionInsensitiveMapper( "org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect" ) );
MAPPERS.put( "Sybase SQL Server", new VersionInsensitiveMapper( "org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect" ) );
MAPPERS.put( "Oracle", new VersionInsensitiveMapper( "org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9Dialect" ) );
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.hibernate;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
/**
* Factory for the Hibernate dialect. Allows dialect detection logic to be centralized and the dialect to be injected
* where required as a singleton from the container.
*
* @author dward
*/
public class DialectFactoryBean implements FactoryBean<Dialect>
{
/** The local session factory. */
private LocalSessionFactoryBean localSessionFactory;
/**
* Sets the local session factory.
*
* @param localSessionFactory
* the new local session factory
*/
public void setLocalSessionFactory(LocalSessionFactoryBean localSessionFactory)
{
this.localSessionFactory = localSessionFactory;
}
@SuppressWarnings("deprecation")
@Override
public Dialect getObject() throws SQLException
{
Session session = ((SessionFactory) this.localSessionFactory.getObject()).openSession();
Configuration cfg = this.localSessionFactory.getConfiguration();
Connection con = null;
try
{
// make sure that we AUTO-COMMIT
con = session.connection();
con.setAutoCommit(true);
DatabaseMetaData meta = con.getMetaData();
Dialect dialect = DialectFactory.buildDialect(cfg.getProperties(), meta.getDatabaseProductName(), meta
.getDatabaseMajorVersion());
dialect = changeDialect(cfg, dialect);
return dialect;
}
finally
{
try
{
con.close();
}
catch (Exception e)
{
}
}
}
/**
* Substitute the dialect with an alternative, if possible.
*
* @param cfg
* the configuration
* @param dialect
* the dialect
* @return the dialect
*/
private Dialect changeDialect(Configuration cfg, Dialect dialect)
{
String dialectName = cfg.getProperty(Environment.DIALECT);
if (dialectName == null || dialectName.length() == 0)
{
// Fix the dialect property to match the detected dialect
cfg.setProperty(Environment.DIALECT, dialect.getClass().getName());
}
return dialect;
// TODO: https://issues.alfresco.com/jira/browse/ETHREEOH-679
// else if (dialectName.equals(Oracle9Dialect.class.getName()))
// {
// String subst = AlfrescoOracle9Dialect.class.getName();
// LogUtil.warn(logger, WARN_DIALECT_SUBSTITUTING, dialectName, subst);
// cfg.setProperty(Environment.DIALECT, subst);
// }
// else if (dialectName.equals(MySQLDialect.class.getName()))
// {
// String subst = MySQLInnoDBDialect.class.getName();
// LogUtil.warn(logger, WARN_DIALECT_SUBSTITUTING, dialectName, subst);
// cfg.setProperty(Environment.DIALECT, subst);
// }
// else if (dialectName.equals(MySQL5Dialect.class.getName()))
// {
// String subst = MySQLInnoDBDialect.class.getName();
// LogUtil.warn(logger, WARN_DIALECT_SUBSTITUTING, dialectName, subst);
// cfg.setProperty(Environment.DIALECT, subst);
// }
}
@Override
public Class<?> getObjectType()
{
return Dialect.class;
}
@Override
public boolean isSingleton()
{
return true;
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.IOException;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public abstract class AbstractHttpClient implements AlfrescoHttpClient
{
private static final Log logger = LogFactory.getLog(AlfrescoHttpClient.class);
public static final String ALFRESCO_DEFAULT_BASE_URL = "/alfresco";
public static final int DEFAULT_SAVEPOST_BUFFER = 4096;
// Remote Server access
protected HttpClient httpClient = null;
private String baseUrl = ALFRESCO_DEFAULT_BASE_URL;
public AbstractHttpClient(HttpClient httpClient)
{
this.httpClient = httpClient;
}
protected HttpClient getHttpClient()
{
return httpClient;
}
/**
* @return the baseUrl
*/
public String getBaseUrl()
{
return baseUrl;
}
/**
* @param baseUrl the baseUrl to set
*/
public void setBaseUrl(String baseUrl)
{
this.baseUrl = baseUrl;
}
private boolean isRedirect(HttpMethod method)
{
switch (method.getStatusCode()) {
case HttpStatus.SC_MOVED_TEMPORARILY:
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_SEE_OTHER:
case HttpStatus.SC_TEMPORARY_REDIRECT:
if (method.getFollowRedirects()) {
return true;
} else {
return false;
}
default:
return false;
}
}
/**
* Send Request to the repository
*/
protected HttpMethod sendRemoteRequest(Request req) throws AuthenticationException, IOException
{
if (logger.isDebugEnabled())
{
logger.debug("");
logger.debug("* Request: " + req.getMethod() + " " + req.getFullUri() + (req.getBody() == null ? "" : "\n" + new String(req.getBody(), "UTF-8")));
}
HttpMethod method = createMethod(req);
// execute method
executeMethod(method);
// Deal with redirect
if(isRedirect(method))
{
Header locationHeader = method.getResponseHeader("location");
if (locationHeader != null)
{
String redirectLocation = locationHeader.getValue();
method.setURI(new URI(redirectLocation, true));
httpClient.executeMethod(method);
}
}
return method;
}
protected long executeMethod(HttpMethod method) throws HttpException, IOException
{
// execute method
long startTime = System.currentTimeMillis();
// TODO: Pool, and sent host configuration and state on execution
getHttpClient().executeMethod(method);
return System.currentTimeMillis() - startTime;
}
protected HttpMethod createMethod(Request req) throws IOException
{
StringBuilder url = new StringBuilder(128);
url.append(baseUrl);
url.append("/service/");
url.append(req.getFullUri());
// construct method
HttpMethod httpMethod = null;
String method = req.getMethod();
if(method.equalsIgnoreCase("GET"))
{
GetMethod get = new GetMethod(url.toString());
httpMethod = get;
httpMethod.setFollowRedirects(true);
}
else if(method.equalsIgnoreCase("POST"))
{
PostMethod post = new PostMethod(url.toString());
httpMethod = post;
ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(req.getBody(), req.getType());
if (req.getBody().length > DEFAULT_SAVEPOST_BUFFER)
{
post.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE, true);
}
post.setRequestEntity(requestEntity);
// Note: not able to automatically follow redirects for POST, this is handled by sendRemoteRequest
}
else if(method.equalsIgnoreCase("HEAD"))
{
HeadMethod head = new HeadMethod(url.toString());
httpMethod = head;
httpMethod.setFollowRedirects(true);
}
else
{
throw new AlfrescoRuntimeException("Http Method " + method + " not supported");
}
if (req.getHeaders() != null)
{
for (Map.Entry<String, String> header : req.getHeaders().entrySet())
{
httpMethod.setRequestHeader(header.getKey(), header.getValue());
}
}
return httpMethod;
}
/* (non-Javadoc)
* @see org.alfresco.httpclient.AlfrescoHttpClient#close()
*/
@Override
public void close()
{
if(httpClient != null)
{
HttpConnectionManager connectionManager = httpClient.getHttpConnectionManager();
if(connectionManager instanceof MultiThreadedHttpConnectionManager)
{
((MultiThreadedHttpConnectionManager)connectionManager).shutdown();
}
}
}
}

View File

@@ -0,0 +1,30 @@
package org.alfresco.httpclient;
import java.io.IOException;
/**
*
* @since 4.0
*
*/
public interface AlfrescoHttpClient
{
/**
* Send Request to the repository
*/
public Response sendRequest(Request req) throws AuthenticationException, IOException;
/**
* Set the base url to alfresco
* - normally /alfresco
* @param baseUrl
*/
public void setBaseUrl(String baseUrl);
/**
*
*/
public void close();
}

View File

@@ -0,0 +1,21 @@
package org.alfresco.httpclient;
import org.apache.commons.httpclient.HttpMethod;
public class AuthenticationException extends Exception
{
private static final long serialVersionUID = -407003742855571557L;
private HttpMethod method;
public AuthenticationException(HttpMethod method)
{
this.method = method;
}
public HttpMethod getMethod()
{
return method;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
/**
* HTTP GET Request
*
* @since 4.0
*/
public class GetRequest extends Request
{
public GetRequest(String uri)
{
super("get", uri);
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
/**
* HTTP HEAD request
*
* @since 4.0
*/
public class HeadRequest extends Request
{
public HeadRequest(String uri)
{
super("head", uri);
}
}

View File

@@ -0,0 +1,777 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AlgorithmParameters;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.alfresco.encryption.AlfrescoKeyStore;
import org.alfresco.encryption.AlfrescoKeyStoreImpl;
import org.alfresco.encryption.EncryptionUtils;
import org.alfresco.encryption.Encryptor;
import org.alfresco.encryption.KeyProvider;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.encryption.KeyStoreParameters;
import org.alfresco.encryption.ssl.AuthSSLProtocolSocketFactory;
import org.alfresco.encryption.ssl.SSLEncryptionParameters;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.Pair;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpHost;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.HttpVersion;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.SimpleHttpConnectionManager;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.DefaultHttpParams;
import org.apache.commons.httpclient.params.DefaultHttpParamsFactory;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.params.HttpParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A factory to create HttpClients and AlfrescoHttpClients based on the setting of the 'secureCommsType' property.
*
* @since 4.0
*/
public class HttpClientFactory
{
public static enum SecureCommsType
{
HTTPS, NONE;
public static SecureCommsType getType(String type)
{
if(type.equalsIgnoreCase("https"))
{
return HTTPS;
}
else if(type.equalsIgnoreCase("none"))
{
return NONE;
}
else
{
throw new IllegalArgumentException("Invalid communications type");
}
}
};
private static final Log logger = LogFactory.getLog(HttpClientFactory.class);
private SSLEncryptionParameters sslEncryptionParameters;
private KeyResourceLoader keyResourceLoader;
private SecureCommsType secureCommsType;
// for md5 http client (no longer used but kept for now)
private KeyStoreParameters keyStoreParameters;
private MD5EncryptionParameters encryptionParameters;
private String host;
private int port;
private int sslPort;
private AlfrescoKeyStore sslKeyStore;
private AlfrescoKeyStore sslTrustStore;
private ProtocolSocketFactory sslSocketFactory;
private int maxTotalConnections = 40;
private int maxHostConnections = 40;
private Integer socketTimeout = null;
private int connectionTimeout = 0;
public HttpClientFactory()
{
}
public HttpClientFactory(SecureCommsType secureCommsType, SSLEncryptionParameters sslEncryptionParameters,
KeyResourceLoader keyResourceLoader, KeyStoreParameters keyStoreParameters,
MD5EncryptionParameters encryptionParameters, String host, int port, int sslPort, int maxTotalConnections,
int maxHostConnections, int socketTimeout)
{
this.secureCommsType = secureCommsType;
this.sslEncryptionParameters = sslEncryptionParameters;
this.keyResourceLoader = keyResourceLoader;
this.keyStoreParameters = keyStoreParameters;
this.encryptionParameters = encryptionParameters;
this.host = host;
this.port = port;
this.sslPort = sslPort;
this.maxTotalConnections = maxTotalConnections;
this.maxHostConnections = maxHostConnections;
this.socketTimeout = socketTimeout;
init();
}
public void init()
{
this.sslKeyStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getKeyStoreParameters(), keyResourceLoader);
this.sslTrustStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getTrustStoreParameters(), keyResourceLoader);
this.sslSocketFactory = new AuthSSLProtocolSocketFactory(sslKeyStore, sslTrustStore, keyResourceLoader);
// Setup the Apache httpclient library to use our concurrent HttpParams factory
DefaultHttpParams.setHttpParamsFactory(new NonBlockingHttpParamsFactory());
}
public void setHost(String host)
{
this.host = host;
}
public String getHost()
{
return host;
}
public void setPort(int port)
{
this.port = port;
}
public int getPort()
{
return port;
}
public void setSslPort(int sslPort)
{
this.sslPort = sslPort;
}
public boolean isSSL()
{
return secureCommsType == SecureCommsType.HTTPS;
}
public void setSecureCommsType(String type)
{
try
{
this.secureCommsType = SecureCommsType.getType(type);
}
catch(IllegalArgumentException e)
{
throw new AlfrescoRuntimeException("", e);
}
}
public void setSSLEncryptionParameters(SSLEncryptionParameters sslEncryptionParameters)
{
this.sslEncryptionParameters = sslEncryptionParameters;
}
public void setKeyStoreParameters(KeyStoreParameters keyStoreParameters)
{
this.keyStoreParameters = keyStoreParameters;
}
public void setEncryptionParameters(MD5EncryptionParameters encryptionParameters)
{
this.encryptionParameters = encryptionParameters;
}
public void setKeyResourceLoader(KeyResourceLoader keyResourceLoader)
{
this.keyResourceLoader = keyResourceLoader;
}
/**
* @return the maxTotalConnections
*/
public int getMaxTotalConnections()
{
return maxTotalConnections;
}
/**
* @param maxTotalConnections the maxTotalConnections to set
*/
public void setMaxTotalConnections(int maxTotalConnections)
{
this.maxTotalConnections = maxTotalConnections;
}
/**
* @return the maxHostConnections
*/
public int getMaxHostConnections()
{
return maxHostConnections;
}
/**
* @param maxHostConnections the maxHostConnections to set
*/
public void setMaxHostConnections(int maxHostConnections)
{
this.maxHostConnections = maxHostConnections;
}
/**
* Attempts to connect to a server will timeout after this period (millis).
* Default is zero (the timeout is not used).
*
* @param connectionTimeout time in millis.
*/
public void setConnectionTimeout(int connectionTimeout)
{
this.connectionTimeout = connectionTimeout;
}
protected HttpClient constructHttpClient()
{
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient httpClient = new HttpClient(connectionManager);
HttpClientParams params = httpClient.getParams();
params.setBooleanParameter(HttpConnectionParams.TCP_NODELAY, true);
params.setBooleanParameter(HttpConnectionParams.STALE_CONNECTION_CHECK, true);
if (socketTimeout != null)
{
params.setSoTimeout(socketTimeout);
}
HttpConnectionManagerParams connectionManagerParams = httpClient.getHttpConnectionManager().getParams();
connectionManagerParams.setMaxTotalConnections(maxTotalConnections);
connectionManagerParams.setDefaultMaxConnectionsPerHost(maxHostConnections);
connectionManagerParams.setConnectionTimeout(connectionTimeout);
return httpClient;
}
protected HttpClient getHttpsClient()
{
return getHttpsClient(host, sslPort);
}
protected HttpClient getHttpsClient(String httpsHost, int httpsPort)
{
// Configure a custom SSL socket factory that will enforce mutual authentication
HttpClient httpClient = constructHttpClient();
HttpHostFactory hostFactory = new HttpHostFactory(new Protocol("https", sslSocketFactory, httpsPort));
httpClient.setHostConfiguration(new HostConfigurationWithHostFactory(hostFactory));
httpClient.getHostConfiguration().setHost(httpsHost, httpsPort, "https");
return httpClient;
}
protected HttpClient getDefaultHttpClient()
{
return getDefaultHttpClient(host, port);
}
protected HttpClient getDefaultHttpClient(String httpHost, int httpPort)
{
HttpClient httpClient = constructHttpClient();
httpClient.getHostConfiguration().setHost(httpHost, httpPort);
return httpClient;
}
protected AlfrescoHttpClient getAlfrescoHttpsClient()
{
AlfrescoHttpClient repoClient = new HttpsClient(getHttpsClient());
return repoClient;
}
protected AlfrescoHttpClient getAlfrescoHttpClient()
{
AlfrescoHttpClient repoClient = new DefaultHttpClient(getDefaultHttpClient());
return repoClient;
}
protected HttpClient getMD5HttpClient(String host, int port)
{
HttpClient httpClient = constructHttpClient();
httpClient.getHostConfiguration().setHost(host, port);
return httpClient;
}
public AlfrescoHttpClient getRepoClient(String host, int port)
{
AlfrescoHttpClient repoClient = null;
if(secureCommsType == SecureCommsType.HTTPS)
{
repoClient = getAlfrescoHttpsClient();
}
else if(secureCommsType == SecureCommsType.NONE)
{
repoClient = getAlfrescoHttpClient();
}
else
{
throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
}
return repoClient;
}
public HttpClient getHttpClient()
{
HttpClient httpClient = null;
if(secureCommsType == SecureCommsType.HTTPS)
{
httpClient = getHttpsClient();
}
else if(secureCommsType == SecureCommsType.NONE)
{
httpClient = getDefaultHttpClient();
}
else
{
throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
}
return httpClient;
}
public HttpClient getHttpClient(String host, int port)
{
HttpClient httpClient = null;
if(secureCommsType == SecureCommsType.HTTPS)
{
httpClient = getHttpsClient(host, port);
}
else if(secureCommsType == SecureCommsType.NONE)
{
httpClient = getDefaultHttpClient(host, port);
}
else
{
throw new AlfrescoRuntimeException("Invalid Solr secure communications type configured in alfresco.secureComms, should be 'ssl'or 'none'");
}
return httpClient;
}
/**
* A secure client connection to the repository.
*
* @since 4.0
*
*/
class HttpsClient extends AbstractHttpClient
{
public HttpsClient(HttpClient httpClient)
{
super(httpClient);
}
/**
* Send Request to the repository
*/
public Response sendRequest(Request req) throws AuthenticationException, IOException
{
HttpMethod method = super.sendRemoteRequest(req);
return new HttpMethodResponse(method);
}
}
/**
* Simple HTTP client to connect to the Alfresco server. Simply wraps a HttpClient.
*
* @since 4.0
*/
class DefaultHttpClient extends AbstractHttpClient
{
public DefaultHttpClient(HttpClient httpClient)
{
super(httpClient);
}
/**
* Send Request to the repository
*/
public Response sendRequest(Request req) throws AuthenticationException, IOException
{
HttpMethod method = super.sendRemoteRequest(req);
return new HttpMethodResponse(method);
}
}
static class SecureHttpMethodResponse extends HttpMethodResponse
{
protected HostConfiguration hostConfig;
protected EncryptionUtils encryptionUtils;
// Need to get as a byte array because we need to read the request twice, once for authentication
// and again by the web service.
protected byte[] decryptedBody;
public SecureHttpMethodResponse(HttpMethod method, HostConfiguration hostConfig,
EncryptionUtils encryptionUtils) throws AuthenticationException, IOException
{
super(method);
this.hostConfig = hostConfig;
this.encryptionUtils = encryptionUtils;
if(method.getStatusCode() == HttpStatus.SC_OK)
{
this.decryptedBody = encryptionUtils.decryptResponseBody(method);
// authenticate the response
if(!authenticate())
{
throw new AuthenticationException(method);
}
}
}
protected boolean authenticate() throws IOException
{
return encryptionUtils.authenticateResponse(method, hostConfig.getHost(), decryptedBody);
}
public InputStream getContentAsStream() throws IOException
{
if(decryptedBody != null)
{
return new ByteArrayInputStream(decryptedBody);
}
else
{
return null;
}
}
}
private static class HttpHostFactory
{
private Map<String, Protocol> protocols;
public HttpHostFactory(Protocol httpsProtocol)
{
protocols = new HashMap<String, Protocol>(2);
protocols.put("https", httpsProtocol);
}
/** Get a host for the given parameters. This method need not be thread-safe. */
public HttpHost getHost(String host, int port, String scheme)
{
if(scheme == null)
{
scheme = "http";
}
Protocol protocol = protocols.get(scheme);
if(protocol == null)
{
protocol = Protocol.getProtocol("http");
if(protocol == null)
{
throw new IllegalArgumentException("Unrecognised scheme parameter");
}
}
return new HttpHost(host, port, protocol);
}
}
private static class HostConfigurationWithHostFactory extends HostConfiguration
{
private final HttpHostFactory factory;
public HostConfigurationWithHostFactory(HttpHostFactory factory)
{
this.factory = factory;
}
public synchronized void setHost(String host, int port, String scheme)
{
setHost(factory.getHost(host, port, scheme));
}
public synchronized void setHost(String host, int port)
{
setHost(factory.getHost(host, port, "http"));
}
@SuppressWarnings("unused")
public synchronized void setHost(URI uri)
{
try {
setHost(uri.getHost(), uri.getPort(), uri.getScheme());
} catch(URIException e) {
throw new IllegalArgumentException(e.toString());
}
}
}
/**
* An extension of the DefaultHttpParamsFactory that uses a RRW lock pattern rather than
* full synchronization around the parameter CRUD - to avoid locking on many reads.
*
* @author Kevin Roast
*/
public static class NonBlockingHttpParamsFactory extends DefaultHttpParamsFactory
{
private volatile HttpParams httpParams;
/* (non-Javadoc)
* @see org.apache.commons.httpclient.params.DefaultHttpParamsFactory#getDefaultParams()
*/
@Override
public HttpParams getDefaultParams()
{
if (httpParams == null)
{
synchronized (this)
{
if (httpParams == null)
{
httpParams = createParams();
}
}
}
return httpParams;
}
/**
* NOTE: This is a copy of the code in {@link DefaultHttpParamsFactory}
* Unfortunately this is required because although the factory pattern allows the
* override of the default param creation, it does not allow the class of the actual
* HttpParam implementation to be changed.
*/
@Override
protected HttpParams createParams()
{
HttpClientParams params = new NonBlockingHttpParams(null);
params.setParameter(HttpMethodParams.USER_AGENT, "Spring Surf via Apache HttpClient/3.1");
params.setVersion(HttpVersion.HTTP_1_1);
params.setConnectionManagerClass(SimpleHttpConnectionManager.class);
params.setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
params.setHttpElementCharset("US-ASCII");
params.setContentCharset("ISO-8859-1");
params.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
List<String> datePatterns = Arrays.asList(
new String[] {
DateUtil.PATTERN_RFC1123,
DateUtil.PATTERN_RFC1036,
DateUtil.PATTERN_ASCTIME,
"EEE, dd-MMM-yyyy HH:mm:ss z",
"EEE, dd-MMM-yyyy HH-mm-ss z",
"EEE, dd MMM yy HH:mm:ss z",
"EEE dd-MMM-yyyy HH:mm:ss z",
"EEE dd MMM yyyy HH:mm:ss z",
"EEE dd-MMM-yyyy HH-mm-ss z",
"EEE dd-MMM-yy HH:mm:ss z",
"EEE dd MMM yy HH:mm:ss z",
"EEE,dd-MMM-yy HH:mm:ss z",
"EEE,dd-MMM-yyyy HH:mm:ss z",
"EEE, dd-MM-yyyy HH:mm:ss z",
}
);
params.setParameter(HttpMethodParams.DATE_PATTERNS, datePatterns);
String agent = null;
try
{
agent = System.getProperty("httpclient.useragent");
}
catch (SecurityException ignore)
{
}
if (agent != null)
{
params.setParameter(HttpMethodParams.USER_AGENT, agent);
}
String preemptiveDefault = null;
try
{
preemptiveDefault = System.getProperty("httpclient.authentication.preemptive");
}
catch (SecurityException ignore)
{
}
if (preemptiveDefault != null)
{
preemptiveDefault = preemptiveDefault.trim().toLowerCase();
if (preemptiveDefault.equals("true"))
{
params.setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, Boolean.TRUE);
}
else if (preemptiveDefault.equals("false"))
{
params.setParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, Boolean.FALSE);
}
}
String defaultCookiePolicy = null;
try
{
defaultCookiePolicy = System.getProperty("apache.commons.httpclient.cookiespec");
}
catch (SecurityException ignore)
{
}
if (defaultCookiePolicy != null)
{
if ("COMPATIBILITY".equalsIgnoreCase(defaultCookiePolicy))
{
params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
}
else if ("NETSCAPE_DRAFT".equalsIgnoreCase(defaultCookiePolicy))
{
params.setCookiePolicy(CookiePolicy.NETSCAPE);
}
else if ("RFC2109".equalsIgnoreCase(defaultCookiePolicy))
{
params.setCookiePolicy(CookiePolicy.RFC_2109);
}
}
return params;
}
}
/**
* @author Kevin Roast
*/
public static class NonBlockingHttpParams extends HttpClientParams
{
private HashMap<String, Object> parameters = new HashMap<String, Object>(8);
private ReadWriteLock paramLock = new ReentrantReadWriteLock();
public NonBlockingHttpParams()
{
super();
}
public NonBlockingHttpParams(HttpParams defaults)
{
super(defaults);
}
@Override
public Object getParameter(final String name)
{
// See if the parameter has been explicitly defined
Object param = null;
paramLock.readLock().lock();
try
{
param = this.parameters.get(name);
}
finally
{
paramLock.readLock().unlock();
}
if (param == null)
{
// If not, see if defaults are available
HttpParams defaults = getDefaults();
if (defaults != null)
{
// Return default parameter value
param = defaults.getParameter(name);
}
}
return param;
}
@Override
public void setParameter(final String name, final Object value)
{
paramLock.writeLock().lock();
try
{
this.parameters.put(name, value);
}
finally
{
paramLock.writeLock().unlock();
}
}
@Override
public boolean isParameterSetLocally(final String name)
{
paramLock.readLock().lock();
try
{
return (this.parameters.get(name) != null);
}
finally
{
paramLock.readLock().unlock();
}
}
@Override
public void clear()
{
paramLock.writeLock().lock();
try
{
this.parameters.clear();
}
finally
{
paramLock.writeLock().unlock();
}
}
@Override
public Object clone() throws CloneNotSupportedException
{
NonBlockingHttpParams clone = (NonBlockingHttpParams)super.clone();
paramLock.readLock().lock();
try
{
clone.parameters = (HashMap) this.parameters.clone();
}
finally
{
paramLock.readLock().unlock();
}
clone.setDefaults(getDefaults());
return clone;
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethod;
/**
*
* @since 4.0
*
*/
public class HttpMethodResponse implements Response
{
protected HttpMethod method;
public HttpMethodResponse(HttpMethod method) throws IOException
{
this.method = method;
}
public void release()
{
method.releaseConnection();
}
public InputStream getContentAsStream() throws IOException
{
return method.getResponseBodyAsStream();
}
public String getContentType()
{
return getHeader("Content-Type");
}
public String getHeader(String name)
{
Header header = method.getResponseHeader(name);
return (header != null) ? header.getValue() : null;
}
public int getStatus()
{
return method.getStatusCode();
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
/**
*
* @since 4.0
*
*/
public class MD5EncryptionParameters
{
private String cipherAlgorithm;
private long messageTimeout;
private String macAlgorithm;
public MD5EncryptionParameters()
{
}
public MD5EncryptionParameters(String cipherAlgorithm,
Long messageTimeout, String macAlgorithm)
{
this.cipherAlgorithm = cipherAlgorithm;
this.messageTimeout = messageTimeout;
this.macAlgorithm = macAlgorithm;
}
public String getCipherAlgorithm()
{
return cipherAlgorithm;
}
public void setCipherAlgorithm(String cipherAlgorithm)
{
this.cipherAlgorithm = cipherAlgorithm;
}
public long getMessageTimeout()
{
return messageTimeout;
}
public String getMacAlgorithm()
{
return macAlgorithm;
}
public void setMessageTimeout(long messageTimeout)
{
this.messageTimeout = messageTimeout;
}
public void setMacAlgorithm(String macAlgorithm)
{
this.macAlgorithm = macAlgorithm;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.UnsupportedEncodingException;
/**
* HTTP POST Request
*
* @since 4.0
*/
public class PostRequest extends Request
{
public PostRequest(String uri, String post, String contentType)
throws UnsupportedEncodingException
{
super("post", uri);
setBody(getEncoding() == null ? post.getBytes() : post.getBytes(getEncoding()));
setType(contentType);
}
public PostRequest(String uri, byte[] post, String contentType)
{
super("post", uri);
setBody(post);
setType(contentType);
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.util.Map;
/**
*
* @since 4.0
*
*/
public class Request
{
private String method;
private String uri;
private Map<String, String> args;
private Map<String, String> headers;
private byte[] body;
private String encoding = "UTF-8";
private String contentType;
public Request(Request req)
{
this.method = req.method;
this.uri= req.uri;
this.args = req.args;
this.headers = req.headers;
this.body = req.body;
this.encoding = req.encoding;
this.contentType = req.contentType;
}
public Request(String method, String uri)
{
this.method = method;
this.uri = uri;
}
public String getMethod()
{
return method;
}
public String getUri()
{
return uri;
}
public String getFullUri()
{
// calculate full uri
String fullUri = uri == null ? "" : uri;
if (args != null && args.size() > 0)
{
char prefix = (uri.indexOf('?') == -1) ? '?' : '&';
for (Map.Entry<String, String> arg : args.entrySet())
{
fullUri += prefix + arg.getKey() + "=" + (arg.getValue() == null ? "" : arg.getValue());
prefix = '&';
}
}
return fullUri;
}
public Request setArgs(Map<String, String> args)
{
this.args = args;
return this;
}
public Map<String, String> getArgs()
{
return args;
}
public Request setHeaders(Map<String, String> headers)
{
this.headers = headers;
return this;
}
public Map<String, String> getHeaders()
{
return headers;
}
public Request setBody(byte[] body)
{
this.body = body;
return this;
}
public byte[] getBody()
{
return body;
}
public Request setEncoding(String encoding)
{
this.encoding = encoding;
return this;
}
public String getEncoding()
{
return encoding;
}
public Request setType(String contentType)
{
this.contentType = contentType;
return this;
}
public String getType()
{
return contentType;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.httpclient;
import java.io.IOException;
import java.io.InputStream;
/**
*
* @since 4.0
*
*/
public interface Response
{
public InputStream getContentAsStream() throws IOException;
public String getHeader(String name);
public String getContentType();
public int getStatus();
// public Long getRequestDuration();
public void release();
}

View File

@@ -0,0 +1,149 @@
package org.alfresco.httpclient;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AlgorithmParameters;
import org.alfresco.encryption.EncryptionUtils;
import org.alfresco.encryption.Encryptor;
import org.alfresco.encryption.KeyProvider;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.util.Pair;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Simple HTTP client to connect to the Alfresco server.
*
* @since 4.0
*/
public class SecureHttpClient //extends AbstractHttpClient
{
// private static final Log logger = LogFactory.getLog(SecureHttpClient.class);
//
// private Encryptor encryptor;
// private EncryptionUtils encryptionUtils;
// private EncryptionService encryptionService;
// private EncryptionParameters encryptionParameters;
//
// /**
// * For testing purposes.
// *
// * @param solrResourceLoader
// * @param alfrescoHost
// * @param alfrescoPort
// * @param encryptionParameters
// */
// public SecureHttpClient(HttpClientFactory httpClientFactory, String host, int port, EncryptionService encryptionService)
// {
// super(httpClientFactory, host, port);
// this.encryptionUtils = encryptionService.getEncryptionUtils();
// this.encryptor = encryptionService.getEncryptor();
// this.encryptionService = encryptionService;
// this.encryptionParameters = encryptionService.getEncryptionParameters();
// }
//
// public SecureHttpClient(HttpClientFactory httpClientFactory, KeyResourceLoader keyResourceLoader, String host, int port,
// EncryptionParameters encryptionParameters)
// {
// super(httpClientFactory, host, port);
// this.encryptionParameters = encryptionParameters;
// this.encryptionService = new EncryptionService(alfrescoHost, alfrescoPort, keyResourceLoader, encryptionParameters);
// this.encryptionUtils = encryptionService.getEncryptionUtils();
// this.encryptor = encryptionService.getEncryptor();
// }
//
// protected HttpMethod createMethod(Request req) throws IOException
// {
// byte[] message = null;
// HttpMethod method = super.createMethod(req);
//
// if(req.getMethod().equalsIgnoreCase("POST"))
// {
// message = req.getBody();
// // encrypt body
// Pair<byte[], AlgorithmParameters> encrypted = encryptor.encrypt(KeyProvider.ALIAS_SOLR, null, message);
// encryptionUtils.setRequestAlgorithmParameters(method, encrypted.getSecond());
//
// ByteArrayRequestEntity requestEntity = new ByteArrayRequestEntity(encrypted.getFirst(), "application/octet-stream");
// ((PostMethod)method).setRequestEntity(requestEntity);
// }
//
// encryptionUtils.setRequestAuthentication(method, message);
//
// return method;
// }
//
// protected HttpMethod sendRemoteRequest(Request req) throws AuthenticationException, IOException
// {
// HttpMethod method = super.sendRemoteRequest(req);
//
// // check that the request returned with an ok status
// if(method.getStatusCode() == HttpStatus.SC_UNAUTHORIZED)
// {
// throw new AuthenticationException(method);
// }
//
// return method;
// }
//
// /**
// * Send Request to the repository
// */
// public Response sendRequest(Request req) throws AuthenticationException, IOException
// {
// HttpMethod method = super.sendRemoteRequest(req);
// return new SecureHttpMethodResponse(method, httpClient.getHostConfiguration(), encryptionUtils);
// }
//
// public static class SecureHttpMethodResponse extends HttpMethodResponse
// {
// protected HostConfiguration hostConfig;
// protected EncryptionUtils encryptionUtils;
// // Need to get as a byte array because we need to read the request twice, once for authentication
// // and again by the web service.
// protected byte[] decryptedBody;
//
// public SecureHttpMethodResponse(HttpMethod method, HostConfiguration hostConfig,
// EncryptionUtils encryptionUtils) throws AuthenticationException, IOException
// {
// super(method);
// this.hostConfig = hostConfig;
// this.encryptionUtils = encryptionUtils;
//
// if(method.getStatusCode() == HttpStatus.SC_OK)
// {
// this.decryptedBody = encryptionUtils.decryptResponseBody(method);
// // authenticate the response
// if(!authenticate())
// {
// throw new AuthenticationException(method);
// }
// }
// }
//
// protected boolean authenticate() throws IOException
// {
// return encryptionUtils.authenticateResponse(method, hostConfig.getHost(), decryptedBody);
// }
//
// public InputStream getContentAsStream() throws IOException
// {
// if(decryptedBody != null)
// {
// return new ByteArrayInputStream(decryptedBody);
// }
// else
// {
// return null;
// }
// }
// }
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.i18n;
import java.util.List;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Resource bundle bootstrap component.
* <p>
* Provides a convenient way to make resource bundles available via Spring config.
*
* @author Roy Wetherall
*/
public class ResourceBundleBootstrapComponent
{
/**
* Set the resource bundles to be registered. This should be a list of resource
* bundle base names whose content will be made available across the repository.
*
* @param resourceBundles the resource bundles
*/
public void setResourceBundles(List<String> resourceBundles)
{
for (String resourceBundle : resourceBundles)
{
I18NUtil.registerResourceBundle(resourceBundle);
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.ibatis;
/**
* Interface for DAOs that offer batching. This should be provided as an optimization
* and DAO implementations that can't supply batching should just do nothing.
*
* @author Derek Hulley
* @since 3.2.1
*/
public interface BatchingDAO
{
/**
* Start a batch of insert or update commands
*
* @throws RuntimeException wrapping a SQLException
*/
void startBatch();
/**
* Write a batch of insert or update commands
*
* @throws RuntimeException wrapping a SQLException
*/
void executeBatch();
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.ibatis;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.alfresco.ibatis.SerializableTypeHandler.DeserializationException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
/**
* MyBatis 3.x TypeHandler for <tt>_byte[]</tt> to <b>BLOB</b> types.
*
* @author sglover
* @since 5.0
*/
public class ByteArrayTypeHandler implements TypeHandler
{
/**
* @throws DeserializationException if the object could not be deserialized
*/
public Object getResult(ResultSet rs, String columnName) throws SQLException
{
byte[] ret = null;
try
{
byte[] bytes = rs.getBytes(columnName);
if(bytes != null && !rs.wasNull())
{
ret = bytes;
}
}
catch (Throwable e)
{
throw new DeserializationException(e);
}
return ret;
}
@Override
public Object getResult(ResultSet rs, int columnIndex) throws SQLException
{
byte[] ret = null;
try
{
byte[] bytes = rs.getBytes(columnIndex);
if(bytes != null && !rs.wasNull())
{
ret = bytes;
}
}
catch (Throwable e)
{
throw new DeserializationException(e);
}
return ret;
}
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException
{
if (parameter == null)
{
ps.setNull(i, Types.BINARY);
}
else
{
try
{
ps.setBytes(i, (byte[])parameter);
}
catch (Throwable e)
{
throw new SerializationException(e);
}
}
}
public Object getResult(CallableStatement cs, int columnIndex) throws SQLException
{
throw new UnsupportedOperationException("Unsupported");
}
/**
* @return Returns the value given
*/
public Object valueOf(String s)
{
return s;
}
/**
* Marker exception to allow deserialization issues to be dealt with by calling code.
* If this exception remains uncaught, it will be very difficult to find and rectify
* the data issue.
*
* @author sglover
* @since 5.0
*/
public static class DeserializationException extends RuntimeException
{
private static final long serialVersionUID = 4673487701048985340L;
public DeserializationException(Throwable cause)
{
super(cause);
}
}
/**
* Marker exception to allow serialization issues to be dealt with by calling code.
* Unlike with {@link DeserializationException deserialization}, it is not important
* to handle this exception neatly.
*
* @author sglover
* @since 5.0
*/
public static class SerializationException extends RuntimeException
{
private static final long serialVersionUID = 962957884262870228L;
public SerializationException(Throwable cause)
{
super(cause);
}
}
}

View File

@@ -0,0 +1,552 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.ibatis;
import java.io.IOException;
import java.util.Properties;
import javax.sql.DataSource;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.resource.HierarchicalResourceLoader;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.NestedIOException;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import static org.springframework.util.Assert.notNull;
import static org.springframework.util.ObjectUtils.isEmpty;
import static org.springframework.util.StringUtils.hasLength;
import static org.springframework.util.StringUtils.tokenizeToStringArray;
/**
* Extends the MyBatis-Spring support by allowing a choice of {@link org.springframework.core.io.ResourceLoader}. The
* {@link #setResourceLoader(HierarchicalResourceLoader) ResourceLoader} will be used to load the <b>SqlMapConfig</b>
* file and use a {@link HierarchicalXMLConfigBuilder} to read the individual MyBatis (3.x) resources.
* <p/>
* Pending a better way to extend/override, much of the implementation is a direct copy of the MyBatis-Spring
* {@link SqlSessionFactoryBean}; some of the <tt>protected</tt> methods do not have access to the object's state
* and can therefore not be overridden successfully.
* <p/>
* This is equivalent to HierarchicalSqlMapClientFactoryBean which extended iBatis (2.x).
* See also: <a href=https://issues.apache.org/jira/browse/IBATIS-589>IBATIS-589</a>
* and: <a href=http://code.google.com/p/mybatis/issues/detail?id=21</a>
*
* @author Derek Hulley, janv
* @since 4.0
*/
//note: effectively replaces SqlSessionFactoryBean to use hierarchical resource loader
public class HierarchicalSqlSessionFactoryBean extends SqlSessionFactoryBean
{
private HierarchicalResourceLoader resourceLoader;
private final Log logger = LogFactory.getLog(getClass());
private Resource configLocation;
private Resource[] mapperLocations;
private DataSource dataSource;
private TransactionFactory transactionFactory;
private Properties configurationProperties;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
private SqlSessionFactory sqlSessionFactory;
private String environment = SqlSessionFactoryBean.class.getSimpleName(); // EnvironmentAware requires spring 3.1
private boolean failFast;
private Interceptor[] plugins;
private TypeHandler<?>[] typeHandlers;
private String typeHandlersPackage;
private Class<?>[] typeAliases;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
private ObjectFactory objectFactory;
private ObjectWrapperFactory objectWrapperFactory;
/**
* Default constructor
*/
public HierarchicalSqlSessionFactoryBean()
{
}
/**
* Set the resource loader to use. To use the <b>&#35;resource.dialect&#35</b> placeholder, use the
* {@link HierarchicalResourceLoader}.
*
* @param resourceLoader the resource loader to use
*/
public void setResourceLoader(HierarchicalResourceLoader resourceLoader)
{
this.resourceLoader = resourceLoader;
}
/**
* Sets the ObjectFactory.
*
* @since 1.1.2
* @param objectFactory ObjectFactory
*/
public void setObjectFactory(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
/**
* Sets the ObjectWrapperFactory.
*
* @since 1.1.2
* @param objectWrapperFactory ObjectWrapperFactory
*/
public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
this.objectWrapperFactory = objectWrapperFactory;
}
/**
* Sets the DatabaseIdProvider.
*
* @since 1.1.0
* @return DatabaseIdProvider
*/
public DatabaseIdProvider getDatabaseIdProvider() {
return databaseIdProvider;
}
/**
* Gets the DatabaseIdProvider
*
* @since 1.1.0
* @param databaseIdProvider DatabaseIdProvider
*/
public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
this.databaseIdProvider = databaseIdProvider;
}
/**
* Mybatis plugin list.
*
* @since 1.0.1
*
* @param plugins list of plugins
*
*/
public void setPlugins(Interceptor[] plugins) {
this.plugins = plugins;
}
/**
* Packages to search for type aliases.
*
* @since 1.0.1
*
* @param typeAliasesPackage package to scan for domain objects
*
*/
public void setTypeAliasesPackage(String typeAliasesPackage) {
this.typeAliasesPackage = typeAliasesPackage;
}
/**
* Super class which domain objects have to extend to have a type alias created.
* No effect if there is no package to scan configured.
*
* @since 1.1.2
*
* @param typeAliasesSuperType super class for domain objects
*
*/
public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
this.typeAliasesSuperType = typeAliasesSuperType;
}
/**
* Packages to search for type handlers.
*
* @since 1.0.1
*
* @param typeHandlersPackage package to scan for type handlers
*
*/
public void setTypeHandlersPackage(String typeHandlersPackage) {
this.typeHandlersPackage = typeHandlersPackage;
}
/**
* Set type handlers. They must be annotated with {@code MappedTypes} and optionally with {@code MappedJdbcTypes}
*
* @since 1.0.1
*
* @param typeHandlers Type handler list
*/
public void setTypeHandlers(TypeHandler<?>[] typeHandlers) {
this.typeHandlers = typeHandlers;
}
/**
* List of type aliases to register. They can be annotated with {@code Alias}
*
* @since 1.0.1
*
* @param typeAliases Type aliases list
*/
public void setTypeAliases(Class<?>[] typeAliases) {
this.typeAliases = typeAliases;
}
/**
* If true, a final check is done on Configuration to assure that all mapped
* statements are fully loaded and there is no one still pending to resolve
* includes. Defaults to false.
*
* @since 1.0.1
*
* @param failFast enable failFast
*/
public void setFailFast(boolean failFast) {
this.failFast = failFast;
}
/**
* Set the location of the MyBatis {@code SqlSessionFactory} config file. A typical value is
* "WEB-INF/mybatis-configuration.xml".
*/
public void setConfigLocation(Resource configLocation) {
this.configLocation = configLocation;
}
/**
* Set locations of MyBatis mapper files that are going to be merged into the {@code SqlSessionFactory}
* configuration at runtime.
*
* This is an alternative to specifying "&lt;sqlmapper&gt;" entries in an MyBatis config file.
* This property being based on Spring's resource abstraction also allows for specifying
* resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml".
*/
public void setMapperLocations(Resource[] mapperLocations) {
this.mapperLocations = mapperLocations;
}
/**
* Set optional properties to be passed into the SqlSession configuration, as alternative to a
* {@code &lt;properties&gt;} tag in the configuration xml file. This will be used to
* resolve placeholders in the config file.
*/
public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
this.configurationProperties = sqlSessionFactoryProperties;
}
/**
* Set the JDBC {@code DataSource} that this instance should manage transactions for. The {@code DataSource}
* should match the one used by the {@code SqlSessionFactory}: for example, you could specify the same
* JNDI DataSource for both.
*
* A transactional JDBC {@code Connection} for this {@code DataSource} will be provided to application code
* accessing this {@code DataSource} directly via {@code DataSourceUtils} or {@code DataSourceTransactionManager}.
*
* The {@code DataSource} specified here should be the target {@code DataSource} to manage transactions for, not
* a {@code TransactionAwareDataSourceProxy}. Only data access code may work with
* {@code TransactionAwareDataSourceProxy}, while the transaction manager needs to work on the
* underlying target {@code DataSource}. If there's nevertheless a {@code TransactionAwareDataSourceProxy}
* passed in, it will be unwrapped to extract its target {@code DataSource}.
*
*/
public void setDataSource(DataSource dataSource) {
if (dataSource instanceof TransactionAwareDataSourceProxy) {
// If we got a TransactionAwareDataSourceProxy, we need to perform
// transactions for its underlying target DataSource, else data
// access code won't see properly exposed transactions (i.e.
// transactions for the target DataSource).
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
} else {
this.dataSource = dataSource;
}
}
/**
* Sets the {@code SqlSessionFactoryBuilder} to use when creating the {@code SqlSessionFactory}.
*
* This is mainly meant for testing so that mock SqlSessionFactory classes can be injected. By
* default, {@code SqlSessionFactoryBuilder} creates {@code DefaultSqlSessionFactory} instances.
*
*/
public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
}
/**
* Set the MyBatis TransactionFactory to use. Default is {@code SpringManagedTransactionFactory}
*
* The default {@code SpringManagedTransactionFactory} should be appropriate for all cases:
* be it Spring transaction management, EJB CMT or plain JTA. If there is no active transaction,
* SqlSession operations will execute SQL statements non-transactionally.
*
* <b>It is strongly recommended to use the default {@code TransactionFactory}.</b> If not used, any
* attempt at getting an SqlSession through Spring's MyBatis framework will throw an exception if
* a transaction is active.
*
* @see SpringManagedTransactionFactory
* @param transactionFactory the MyBatis TransactionFactory
*/
public void setTransactionFactory(TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
}
/**
* <b>NOTE:</b> This class <em>overrides</em> any {@code Environment} you have set in the MyBatis
* config file. This is used only as a placeholder name. The default value is
* {@code SqlSessionFactoryBean.class.getSimpleName()}.
*
* @param environment the environment name
*/
public void setEnvironment(String environment) {
this.environment = environment;
}
@Override
public void afterPropertiesSet() throws Exception {
PropertyCheck.mandatory(this, "resourceLoader", resourceLoader);
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
/**
* Build a {@code SqlSessionFactory} instance.
* <p/>
* The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
* {@code SqlSessionFactory} instance based on an Reader.
*
* @return SqlSessionFactory
* @throws IOException if loading the config file failed
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
HierarchicalXMLConfigBuilder xmlConfigBuilder = null;
if (this.configLocation != null) {
try {
xmlConfigBuilder = new HierarchicalXMLConfigBuilder(resourceLoader, this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
//Commented out to be able to use dummy dataSource in tests.
/*
if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
*/
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Property 'mapperLocations' was not specified, only MyBatis mapper files specified in the config xml were loaded");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
/**
* {@inheritDoc}
*/
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
/**
* {@inheritDoc}
*/
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
/**
* {@inheritDoc}
*/
public boolean isSingleton() {
return true;
}
/**
* {@inheritDoc}
*/
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
}

View File

@@ -0,0 +1,395 @@
/*
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.ibatis;
import java.io.InputStream;
import java.util.Properties;
import javax.sql.DataSource;
import org.alfresco.util.resource.HierarchicalResourceLoader;
import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.loader.ProxyFactory;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.AutoMappingBehavior;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.core.io.Resource;
/**
* Extends the MyBatis XMLConfigBuilder to allow the selection of a {@link org.springframework.core.io.ResourceLoader}
* that will be used to load the resources specified in the <b>mapper</b>'s <b>resource</b>.
* <p>
* By using the <b>resource.dialect</b> placeholder with hierarchical resource loading,
* different resource files can be picked up for different dialects. This reduces duplication
* when supporting multiple database configurations.
* <pre>
* &lt;configuration&gt;
* &lt;mappers&gt;
* &lt;mapper resource=&quot;org/x/y/#resource.dialect#/View1.xml&quot;/&gt;
* &lt;mapper resource=&quot;org/x/y/#resource.dialect#/View2.xml&quot;/&gt;
* &lt;/mappers&gt;
* &lt;/configuration&gt;
* <p/>
*
* Much of the implementation is a direct copy of the MyBatis {@link org.apache.ibatis.builder.xml.XMLConfigBuilder}; some
* of the <tt>protected</tt> methods do not have access to the object's state and can therefore
* not be overridden successfully: <a href=https://issues.apache.org/jira/browse/IBATIS-589>IBATIS-589</a>
* Pending a better way to extend/override, much of the implementation is a direct copy of the MyBatis
* {@link org.mybatis.spring.SqlSessionFactoryBean}; some of the <tt>protected</tt> methods do not have access to the object's state
* and can therefore not be overridden successfully.
*
* This is equivalent to HierarchicalSqlMapConfigParser which extended iBatis (2.x).
* See also: <a href=https://issues.apache.org/jira/browse/IBATIS-589>IBATIS-589</a>
* and: <a href=http://code.google.com/p/mybatis/issues/detail?id=21</a>
*
* @author Derek Hulley, janv
* @since 4.0
*/
// note: effectively extends XMLConfigBuilder to use hierarchical resource loader
public class HierarchicalXMLConfigBuilder extends BaseBuilder
{
private boolean parsed;
private XPathParser parser;
private String environment;
private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
// EXTENDED
final private HierarchicalResourceLoader resourceLoader;
public HierarchicalXMLConfigBuilder(HierarchicalResourceLoader resourceLoader, InputStream inputStream, String environment, Properties props)
{
super(new Configuration());
// EXTENDED
this.resourceLoader = resourceLoader;
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = new XPathParser(inputStream, true, props, new XMLMapperEntityResolver());
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
private void reflectionFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
configuration.setReflectorFactory(factory);
}
}
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
private void settingsElement(XNode context) throws Exception {
if (context != null) {
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
}
}
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// // EXTENDED
// inputStream = Resources.getResourceAsStream(resource);
InputStream inputStream = null;
Resource res = resourceLoader.getResource(resource);
if (res != null && res.exists())
{
inputStream = res.getInputStream();
}
else {
throw new BuilderException("Failed to get resource: "+resource);
}
//InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
private boolean isSpecifiedEnvironment(String id) {
if (environment == null) {
throw new BuilderException("No environment specified.");
} else if (id == null) {
throw new BuilderException("Environment requires an id attribute.");
} else if (environment.equals(id)) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.ibatis;
import java.util.List;
/**
* Entity bean to carry ID-style information
*
* @author Derek Hulley
* @since 3.2
*/
public class IdsEntity
{
private Long idOne;
private Long idTwo;
private Long idThree;
private Long idFour;
private List<Long> ids;
public Long getIdOne()
{
return idOne;
}
public void setIdOne(Long id)
{
this.idOne = id;
}
public Long getIdTwo()
{
return idTwo;
}
public void setIdTwo(Long id)
{
this.idTwo = id;
}
public Long getIdThree()
{
return idThree;
}
public void setIdThree(Long idThree)
{
this.idThree = idThree;
}
public Long getIdFour()
{
return idFour;
}
public void setIdFour(Long idFour)
{
this.idFour = idFour;
}
public List<Long> getIds()
{
return ids;
}
public void setIds(List<Long> ids)
{
this.ids = ids;
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.ibatis;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A helper that runs a unit of work, transparently retrying the unit of work if
* an error occurs.
* <p>
* Defaults:
* <ul>
* <li><b>maxRetries: 5</b></li>
* <li><b>retryWaitMs: 10</b></li>
* </ul>
*
* @author Derek Hulley
* @since 3.4
*/
public class RetryingCallbackHelper
{
private static final Log logger = LogFactory.getLog(RetryingCallbackHelper.class);
/** The maximum number of retries. -1 for infinity. */
private int maxRetries;
/** How much time to wait with each retry. */
private int retryWaitMs;
/**
* Callback interface
* @author Derek Hulley
*/
public interface RetryingCallback<Result>
{
/**
* Perform a unit of work.
*
* @return Return the result of the unit of work
* @throws Throwable This can be anything and will guarantee either a retry or a rollback
*/
public Result execute() throws Throwable;
};
/**
* Default constructor.
*/
public RetryingCallbackHelper()
{
this.maxRetries = 5;
this.retryWaitMs = 10;
}
/**
* Set the maximimum number of retries. -1 for infinity.
*/
public void setMaxRetries(int maxRetries)
{
this.maxRetries = maxRetries;
}
public void setRetryWaitMs(int retryWaitMs)
{
this.retryWaitMs = retryWaitMs;
}
/**
* Execute a callback until it succeeds, fails or until a maximum number of retries have
* been attempted.
*
* @param callback The callback containing the unit of work.
* @return Returns the result of the unit of work.
* @throws RuntimeException all checked exceptions are converted
*/
public <R> R doWithRetry(RetryingCallback<R> callback)
{
// Track the last exception caught, so that we can throw it if we run out of retries.
RuntimeException lastException = null;
for (int count = 0; count == 0 || count < maxRetries; count++)
{
try
{
// Do the work.
R result = callback.execute();
if (logger.isDebugEnabled())
{
if (count != 0)
{
logger.debug("\n" +
"Retrying work succeeded: \n" +
" Thread: " + Thread.currentThread().getName() + "\n" +
" Iteration: " + count);
}
}
return result;
}
catch (Throwable e)
{
lastException = (e instanceof RuntimeException) ?
(RuntimeException) e :
new AlfrescoRuntimeException("Exception in Transaction.", e);
if (logger.isDebugEnabled())
{
logger.debug("\n" +
"Retrying work failed: \n" +
" Thread: " + Thread.currentThread().getName() + "\n" +
" Iteration: " + count + "\n" +
" Exception follows:",
e);
}
else if (logger.isInfoEnabled())
{
String msg = String.format(
"Retrying %s: count %2d; wait: %3dms; msg: \"%s\"; exception: (%s)",
Thread.currentThread().getName(),
count, retryWaitMs,
e.getMessage(),
e.getClass().getName());
logger.info(msg);
}
try
{
Thread.sleep(retryWaitMs);
}
catch (InterruptedException ie)
{
// Do nothing.
}
// Try again
continue;
}
}
// We've worn out our welcome and retried the maximum number of times.
// So, fail.
throw lastException;
}
}

View File

@@ -0,0 +1,253 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.ibatis;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.ibatis.executor.result.DefaultResultContext;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.mybatis.spring.SqlSessionTemplate;
/**
* A {@link ResultHandler} that collapses multiple rows based on a set of properties.
* <p/>
* This class is derived from earlier RollupRowHandler used to workaround the <b>groupBy</b> and nested <b>ResultMap</b>
* behaviour in iBatis (2.3.4.726) <a href=https://issues.apache.org/jira/browse/IBATIS-503>IBATIS-503</a>.
* <p>
* The set of properties given act as a unique key. When the unique key <i>changes</i>, the collection
* values from the nested <i>ResultMap<i> are coalesced and the given {@link ResultHandler} is called. It is
* possible to embed several instances of this handler for deeply-nested <i>ResultMap</i> declarations.
* <p>
* Use this instance as a regular {@link ResultHandler}, but with one big exception: call {@link #processLastResults()}
* after executing the SQL statement. Remove the <b>groupBy</b> attribute from the iBatis <b>ResultMap</b>
* declaration.
* <p>
* Example iBatis 2.x (TODO migrate example to MyBatis 3.x):
* <code><pre>
&lt;resultMap id="result_AuditQueryAllValues"
extends="alfresco.audit.result_AuditQueryNoValues"
class="AuditQueryResult"&gt;
&lt;result property="auditValues" resultMap="alfresco.propval.result_PropertyIdSearchRow"/&gt;
&lt;/resultMap&gt;
* </code></pre>
* Example usage:
* <code><pre>
RowHandler rowHandler = new RowHandler()
{
public void handleRow(Object valueObject)
{
// DO SOMETHING
}
};
RollupRowHandler rollupRowHandler = new RollupRowHandler(
new String[] {"auditEntryId"},
"auditValues",
rowHandler,
maxResults);
if (maxResults > 0)
{
// Calculate the maximum results required
int sqlMaxResults = (maxResults > 0 ? ((maxResults+1) * 20) : Integer.MAX_VALUE);
List<AuditQueryResult> rows = template.queryForList(SELECT_ENTRIES_WITH_VALUES, params, 0, sqlMaxResults);
for (AuditQueryResult row : rows)
{
rollupRowHandler.handleRow(row);
}
// Don't process last result:
// rollupRowHandler.processLastResults();
// The last result may be incomplete
}
else
{
template.queryWithRowHandler(SELECT_ENTRIES_WITH_VALUES, params, rollupRowHandler);
rollupRowHandler.processLastResults();
}
* </pre></code>
* <p>
* This class is not thread-safe; use a new instance for each use.
*
* @author Derek Hulley, janv
* @since 4.0
*/
public class RollupResultHandler implements ResultHandler
{
private final String[] keyProperties;
private final String collectionProperty;
private final ResultHandler resultHandler;
private final int maxResults;
private Object[] lastKeyValues;
private List<Object> rawResults;
private int resultCount;
private Configuration configuration;
/**
* @param keyProperties the properties that make up the unique key
* @param collectionProperty the property mapped using a nested <b>ResultMap</b>
* @param resultHandler the result handler that will receive the rolled-up results
*/
public RollupResultHandler(Configuration configuration, String[] keyProperties, String collectionProperty, ResultHandler resultHandler)
{
this(configuration, keyProperties, collectionProperty, resultHandler, Integer.MAX_VALUE);
}
/**
* @param keyProperties the properties that make up the unique key
* @param collectionProperty the property mapped using a nested <b>ResultMap</b>
* @param resultHandler the result handler that will receive the rolled-up results
* @param maxResults the maximum number of results to retrieve (-1 for no limit).
* Make sure that the query result limit is large enough to produce this
* at least this number of results
*/
public RollupResultHandler(Configuration configuration, String[] keyProperties, String collectionProperty, ResultHandler resultHandler, int maxResults)
{
if (keyProperties == null || keyProperties.length == 0)
{
throw new IllegalArgumentException("RollupRowHandler can only be used with at least one key property.");
}
if (collectionProperty == null)
{
throw new IllegalArgumentException("RollupRowHandler must have a collection property.");
}
this.configuration = configuration;
this.keyProperties = keyProperties;
this.collectionProperty = collectionProperty;
this.resultHandler = resultHandler;
this.maxResults = maxResults;
this.rawResults = new ArrayList<Object>(100);
}
public void handleResult(ResultContext context)
{
// Shortcut if we have processed enough results
if (maxResults > 0 && resultCount >= maxResults)
{
return;
}
Object valueObject = context.getResultObject();
MetaObject probe = configuration.newMetaObject(valueObject);
// Check if the key has changed
if (lastKeyValues == null)
{
lastKeyValues = getKeyValues(probe);
resultCount = 0;
}
// Check if it has changed
Object[] currentKeyValues = getKeyValues(probe);
if (!Arrays.deepEquals(lastKeyValues, currentKeyValues))
{
// Key has changed, so handle the results
Object resultObject = coalesceResults(configuration, rawResults, collectionProperty);
if (resultObject != null)
{
DefaultResultContext resultContext = new DefaultResultContext();
resultContext.nextResultObject(resultObject);
resultHandler.handleResult(resultContext);
resultCount++;
}
rawResults.clear();
lastKeyValues = currentKeyValues;
}
// Add the new value to the results for next time
rawResults.add(valueObject);
// Done
}
/**
* Client code <b>must</b> call this method once the query returns so that the final results
* can be passed to the inner RowHandler. If a query is limited by size, then it is
* possible that the unprocessed results represent an incomplete final object; in this case
* it would be best to ignore the last results. If the query is complete (i.e. all results
* are returned) then this method should be called.
* <p>
* If you want X results and each result is made up of N rows (on average), then set the query
* limit to: <br/>
* L = X * (N+1)<br/>
* and don't call this method.
*/
public void processLastResults()
{
// Shortcut if we have processed enough results
if (maxResults > 0 && resultCount >= maxResults)
{
return;
}
// Handle any outstanding results
Object resultObject = coalesceResults(configuration, rawResults, collectionProperty);
if (resultObject != null)
{
DefaultResultContext resultContext = new DefaultResultContext();
resultContext.nextResultObject(resultObject);
resultHandler.handleResult(resultContext);
resultCount++;
rawResults.clear(); // Stop it from being used again
}
}
@SuppressWarnings("unchecked")
private static Object coalesceResults(Configuration configuration, List<Object> valueObjects, String collectionProperty)
{
// Take the first result as the base value
Object resultObject = null;
MetaObject probe = null;
Collection<Object> collection = null;
for (Object object : valueObjects)
{
if (collection == null)
{
resultObject = object;
probe = configuration.newMetaObject(resultObject);
collection = (Collection<Object>) probe.getValue(collectionProperty);
}
else
{
Collection<?> addedValues = (Collection<Object>) probe.getValue(collectionProperty);
collection.addAll(addedValues);
}
}
// Done
return resultObject;
}
/**
* @return Returns the values for the {@link RollupResultHandler#keyProperties}
*/
private Object[] getKeyValues(MetaObject probe)
{
Object[] keyValues = new Object[keyProperties.length];
for (int i = 0; i < keyProperties.length; i++)
{
keyValues[i] = probe.getValue(keyProperties[i]);
}
return keyValues;
}
}

View File

@@ -0,0 +1,185 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.ibatis;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
/**
* MyBatis 3.x TypeHandler for <tt>java.io.Serializable</tt> to <b>BLOB</b> types.
*
* @author Derek Hulley, janv
* @since 4.0
*/
public class SerializableTypeHandler implements TypeHandler
{
public static final int DEFAULT_SERIALIZABLE_TYPE = Types.LONGVARBINARY;
private static volatile int serializableType = DEFAULT_SERIALIZABLE_TYPE;
/**
* @see Types
*/
public static void setSerializableType(int serializableType)
{
SerializableTypeHandler.serializableType = serializableType;
}
/**
* @return Returns the SQL type to use for serializable columns
*/
public static int getSerializableType()
{
return serializableType;
}
/**
* @throws DeserializationException if the object could not be deserialized
*/
public Object getResult(ResultSet rs, String columnName) throws SQLException
{
final Serializable ret;
try
{
InputStream is = rs.getBinaryStream(columnName);
if (is == null || rs.wasNull())
{
return null;
}
// Get the stream and deserialize
ObjectInputStream ois = new ObjectInputStream(is);
Object obj = ois.readObject();
// Success
ret = (Serializable) obj;
}
catch (Throwable e)
{
throw new DeserializationException(e);
}
return ret;
}
@Override
public Object getResult(ResultSet rs, int columnIndex) throws SQLException
{
final Serializable ret;
try
{
InputStream is = rs.getBinaryStream(columnIndex);
if (is == null || rs.wasNull())
{
return null;
}
// Get the stream and deserialize
ObjectInputStream ois = new ObjectInputStream(is);
Object obj = ois.readObject();
// Success
ret = (Serializable) obj;
}
catch (Throwable e)
{
throw new DeserializationException(e);
}
return ret;
}
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException
{
if (parameter == null)
{
ps.setNull(i, SerializableTypeHandler.serializableType);
}
else
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(parameter);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ps.setBinaryStream(i, bais, bytes.length);
}
catch (Throwable e)
{
throw new SerializationException(e);
}
}
}
public Object getResult(CallableStatement cs, int columnIndex) throws SQLException
{
throw new UnsupportedOperationException("Unsupported");
}
/**
* @return Returns the value given
*/
public Object valueOf(String s)
{
return s;
}
/**
* Marker exception to allow deserialization issues to be dealt with by calling code.
* If this exception remains uncaught, it will be very difficult to find and rectify
* the data issue.
*
* @author Derek Hulley
* @since 3.2
*/
public static class DeserializationException extends RuntimeException
{
private static final long serialVersionUID = 4673487701048985340L;
public DeserializationException(Throwable cause)
{
super(cause);
}
}
/**
* Marker exception to allow serialization issues to be dealt with by calling code.
* Unlike with {@link DeserializationException deserialization}, it is not important
* to handle this exception neatly.
*
* @author Derek Hulley
* @since 3.2
*/
public static class SerializationException extends RuntimeException
{
private static final long serialVersionUID = 962957884262870228L;
public SerializationException(Throwable cause)
{
super(cause);
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.processor;
/**
* Interface for Proccessor classes - such as Template or Scripting Processors.
*
* @author Roy Wetherall
*/
public interface Processor
{
/**
* Get the name of the processor
*
* @return the name of the processor
*/
public String getName();
/**
* The file extension that the processor is associated with, null if none.
*
* @return the extension
*/
public String getExtension();
/**
* Registers a processor extension with the processor
*
* @param processorExtension the process extension
*/
public void registerProcessorExtension(ProcessorExtension processorExtension);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.processor;
/**
* Interface to represent a server side script implementation
*
* @author Roy Wetherall
*/
public interface ProcessorExtension
{
/**
* Returns the name of the extension
*
* @return the name of the extension
*/
String getExtensionName();
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.List;
/**
* Caching support extension for {@link CannedQueryFactory} implementations.
* <p/>
* Depending on the parameters provided, this class may choose to pick up existing results
* and re-use them for later page requests; the client will not have knowledge of the
* shortcuts.
*
* TODO: This is work-in-progress
*
* @author Derek Hulley
* @since 4.0
*/
public abstract class AbstractCachingCannedQueryFactory<R> extends AbstractCannedQueryFactory<R>
{
/**
* Base implementation that provides a caching facade around the query.
*
* @return a decoraded facade query that will cache query results for later paging requests
*/
@Override
public final CannedQuery<R> getCannedQuery(CannedQueryParameters parameters)
{
throw new UnsupportedOperationException();
}
/**
* Derived classes must implement this method to provide the raw query that supports the given
* parameters. All requests must be serviced without any further caching in order to prevent
* duplicate caching.
*
* @param parameters the query parameters as given by the client
* @return the query that will generate the results
*/
protected abstract CannedQuery<R> getCannedQueryImpl(CannedQueryParameters parameters);
private class CannedQueryCacheFacade<R> extends AbstractCannedQuery<R>
{
private final AbstractCannedQuery<R> delegate;
private CannedQueryCacheFacade(CannedQueryParameters params, AbstractCannedQuery<R> delegate)
{
super(params);
this.delegate = delegate;
}
@Override
protected List<R> queryAndFilter(CannedQueryParameters parameters)
{
// Copy the parameters and remove all references to paging.
// The underlying query will return full or filtered results (possibly also sorted)
// but will not apply page limitations
throw new UnsupportedOperationException();
}
}
}

View File

@@ -0,0 +1,347 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
/**
* Basic support for canned query implementations.
*
* @author Derek Hulley
* @since 4.0
*/
public abstract class AbstractCannedQuery<R> implements CannedQuery<R>
{
private final CannedQueryParameters parameters;
private final String queryExecutionId;
private CannedQueryResults<R> results;
/**
* Construct the canned query given the original parameters applied.
* <p/>
* A random GUID query execution ID will be generated.
*
* @param parameters the original query parameters
*/
protected AbstractCannedQuery(CannedQueryParameters parameters)
{
ParameterCheck.mandatory("parameters", parameters);
this.parameters = parameters;
this.queryExecutionId = GUID.generate();
}
@Override
public CannedQueryParameters getParameters()
{
return parameters;
}
@Override
public String toString()
{
return "AbstractCannedQuery [parameters=" + parameters + ", class=" + this.getClass() + "]";
}
@Override
public synchronized final CannedQueryResults<R> execute()
{
// Check that we are not requerying
if (results != null)
{
throw new IllegalStateException(
"This query instance has already by used." +
" It can only be used to query once.");
}
// Get the raw query results
List<R> rawResults = queryAndFilter(parameters);
if (rawResults == null)
{
throw new AlfrescoRuntimeException("Execution returned 'null' results");
}
// Apply sorting
if (isApplyPostQuerySorting())
{
rawResults = applyPostQuerySorting(rawResults, parameters.getSortDetails());
}
// Apply permissions
if (isApplyPostQueryPermissions())
{
// Work out the number of results required
int requestedCount = parameters.getResultsRequired();
rawResults = applyPostQueryPermissions(rawResults, requestedCount);
}
// Get total count
final Pair<Integer, Integer> totalCount = getTotalResultCount(rawResults);
// Apply paging
CannedQueryPageDetails pagingDetails = parameters.getPageDetails();
List<List<R>> pages = Collections.singletonList(rawResults);
if (isApplyPostQueryPaging())
{
pages = applyPostQueryPaging(rawResults, pagingDetails);
}
// Construct results object
final List<List<R>> finalPages = pages;
// Has more items beyond requested pages ? ... ie. at least one more page (with at least one result)
final boolean hasMoreItems = (rawResults.size() > pagingDetails.getResultsRequiredForPaging());
results = new CannedQueryResults<R>()
{
@Override
public CannedQuery<R> getOriginatingQuery()
{
return AbstractCannedQuery.this;
}
@Override
public String getQueryExecutionId()
{
return queryExecutionId;
}
@Override
public Pair<Integer, Integer> getTotalResultCount()
{
if (parameters.getTotalResultCountMax() > 0)
{
return totalCount;
}
else
{
throw new IllegalStateException("Total results were not requested in parameters.");
}
}
@Override
public int getPagedResultCount()
{
int finalPagedCount = 0;
for (List<R> page : finalPages)
{
finalPagedCount += page.size();
}
return finalPagedCount;
}
@Override
public int getPageCount()
{
return finalPages.size();
}
@Override
public R getSingleResult()
{
if (finalPages.size() != 1 && finalPages.get(0).size() != 1)
{
throw new IllegalStateException("There must be exactly one page of one result available.");
}
return finalPages.get(0).get(0);
}
@Override
public List<R> getPage()
{
if (finalPages.size() != 1)
{
throw new IllegalStateException("There must be exactly one page of results available.");
}
return finalPages.get(0);
}
@Override
public List<List<R>> getPages()
{
return finalPages;
}
@Override
public boolean hasMoreItems()
{
return hasMoreItems;
}
};
return results;
}
/**
* Implement the basic query, returning either filtered or all results.
* <p/>
* The implementation may optimally select, filter, sort and apply permissions.
* If not, however, the subsequent post-query methods
* ({@link #applyPostQuerySorting(List, CannedQuerySortDetails)},
* {@link #applyPostQueryPermissions(List, int)} and
* {@link #applyPostQueryPaging(List, CannedQueryPageDetails)}) can
* be used to trim the results as required.
*
* @param parameters the full parameters to be used for execution
*/
protected abstract List<R> queryAndFilter(CannedQueryParameters parameters);
/**
* Override to get post-query calls to do sorting.
*
* @return <tt>true</tt> to get a post-query call to sort (default <tt>false</tt>)
*/
protected boolean isApplyPostQuerySorting()
{
return false;
}
/**
* Called before {@link #applyPostQueryPermissions(List, int)} to allow the results to be sorted prior to permission checks.
* Note that the query implementation may optimally sort results during retrieval, in which case this method does not need to be implemented.
*
* @param results the results to sort
* @param sortDetails details of the sorting requirements
* @return the results according to the new sort order
*/
protected List<R> applyPostQuerySorting(List<R> results, CannedQuerySortDetails sortDetails)
{
throw new UnsupportedOperationException("Override this method if post-query sorting is required.");
}
/**
* Override to get post-query calls to apply permission filters.
*
* @return <tt>true</tt> to get a post-query call to apply permissions (default <tt>false</tt>)
*/
protected boolean isApplyPostQueryPermissions()
{
return false;
}
/**
* Called after the query to filter out results based on permissions.
* Note that the query implementation may optimally only select results
* based on available privileges, in which case this method does not need to be implemented.
* <p/>
* Permission evaluations should continue until the requested number of results are retrieved
* or all available results have been examined.
*
* @param results the results to apply permissions to
* @param requestedCount the minimum number of results to pass the permission checks
* in order to fully satisfy the paging requirements
* @return the remaining results (as a single "page") after permissions have been applied
*/
protected List<R> applyPostQueryPermissions(List<R> results, int requestedCount)
{
throw new UnsupportedOperationException("Override this method if post-query filtering is required.");
}
/**
* Get the total number of available results after querying, filtering, sorting and permission checking.
* <p/>
* The default implementation assumes that the given results are the final total possible.
*
* @param results the results after filtering and sorting, but before paging
* @return pair representing (a) the total number of results and
* (b) the estimated (or actual) number of maximum results
* possible for this query.
*
* @see CannedQueryParameters#getTotalResultCountMax()
*/
protected Pair<Integer, Integer> getTotalResultCount(List<R> results)
{
Integer size = results.size();
return new Pair<Integer, Integer>(size, size);
}
/**
* Override to get post-query calls to do pull out paged results.
*
* @return <tt>true</tt> to get a post-query call to page (default <tt>true</tt>)
*/
protected boolean isApplyPostQueryPaging()
{
return true;
}
/**
* Called after the {@link #applyPostQuerySorting(List, CannedQuerySortDetails) sorting phase} to pull out results specific
* to the required pages. Note that the query implementation may optimally
* create page-specific results, in which case this method does not need to be implemented.
* <p/>
* The base implementation assumes that results are not paged and that the current results
* are all the available results i.e. that paging still needs to be applied.
*
* @param results full results (all or excess pages)
* @param pageDetails details of the paging requirements
* @return the specific page of results as per the query parameters
*/
protected List<List<R>> applyPostQueryPaging(List<R> results, CannedQueryPageDetails pageDetails)
{
int skipResults = pageDetails.getSkipResults();
int pageSize = pageDetails.getPageSize();
int pageCount = pageDetails.getPageCount();
int pageNumber = pageDetails.getPageNumber();
int availableResults = results.size();
int totalResults = pageSize * pageCount;
int firstResult = skipResults + ((pageNumber-1) * pageSize); // first of window
List<List<R>> pages = new ArrayList<List<R>>(pageCount);
// First some shortcuts
if (skipResults == 0 && pageSize > availableResults)
{
return Collections.singletonList(results); // Requesting more results in one page than are available
}
else if (firstResult > availableResults)
{
return pages; // Start of first page is after all results
}
// Build results
Iterator<R> iterator = results.listIterator(firstResult);
int countTotal = 0;
List<R> page = new ArrayList<R>(Math.min(results.size(), pageSize)); // Prevent memory blow-out
pages.add(page);
while (iterator.hasNext() && countTotal < totalResults)
{
if (page.size() == pageSize)
{
// Create a page and add it to the results
page = new ArrayList<R>(pageSize);
pages.add(page);
}
R next = iterator.next();
page.add(next);
countTotal++;
}
// Done
return pages;
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import org.alfresco.util.GUID;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.registry.NamedObjectRegistry;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
/**
* Basic services for {@link CannedQueryFactory} implementations.
*
* @author Derek Hulley
* @since 4.0
*/
public abstract class AbstractCannedQueryFactory<R> implements CannedQueryFactory<R>, InitializingBean, BeanNameAware
{
private String name;
@SuppressWarnings("rawtypes")
private NamedObjectRegistry<CannedQueryFactory> registry;
/**
* Set the name with which to {@link #setRegistry(NamedObjectRegistry) register}
* @param name the name of the bean
*/
public void setBeanName(String name)
{
this.name = name;
}
/**
* Set the registry with which to register
*/
@SuppressWarnings("rawtypes")
public void setRegistry(NamedObjectRegistry<CannedQueryFactory> registry)
{
this.registry = registry;
}
/**
* Registers the instance
*/
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory(this, "name", name);
PropertyCheck.mandatory(this, "registry", registry);
registry.register(name, this);
}
/**
* Helper method to construct a unique query execution ID based on the
* instance of the factory and the parameters provided.
*
* @param parameters the query parameters
* @return a unique query instance ID
*/
protected String getQueryExecutionId(CannedQueryParameters parameters)
{
// Create a GUID
String uuid = name + "-" + GUID.generate();
return uuid;
}
/**
* {@inheritDoc}
*/
@Override
public CannedQuery<R> getCannedQuery(Object parameterBean, int skipResults, int pageSize, String queryExecutionId)
{
return getCannedQuery(new CannedQueryParameters(parameterBean, skipResults, pageSize, queryExecutionId));
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Interface for named query implementations. These are queries that encapsulate varying
* degrees of functionality, but ultimately provide support for paging results.
* <p/>
* Note that each instance of the query is stateful and cannot be reused.
*
* @param <R> the query result type
*
* @author Derek Hulley
* @since 4.0
*/
public interface CannedQuery<R>
{
/**
* Get the original parameters used to generate the query.
*
* @return the parameters used to obtain the named query.
*/
CannedQueryParameters getParameters();
/**
* Execute the named query, which was provided to support the
* {@link #getParameters() parameters} originally provided.
* <p/>
* <b>Note: This method can only be used once</b>; to requery, get a new
* instance from the {@link CannedQueryFactory factory}.
*
* @return the query results
*
* @throws IllegalStateException on second and subsequent calls to this method
*/
CannedQueryResults<R> execute();
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import org.alfresco.error.AlfrescoRuntimeException;
/**
* Exception generated by failures to execute canned queries.
*
* @author Derek Hulley
* @since 4.0
*/
public class CannedQueryException extends AlfrescoRuntimeException
{
private static final long serialVersionUID = -4985399145374964458L;
/**
* @param msg the message
*/
public CannedQueryException(String msg)
{
super(msg);
}
/**
* @param msg the message
* @param cause the exception cause
*/
public CannedQueryException(String msg, Throwable cause)
{
super(msg, cause);
}
/**
* @param msgId the message id
* @param msgParams the message parameters
*/
public CannedQueryException(String msgId, Object[] msgParams)
{
super(msgId, msgParams);
}
/**
* @param msgId the message id
* @param msgParams the message parameters
* @param cause the exception cause
*/
public CannedQueryException(String msgId, Object[] msgParams, Throwable cause)
{
super(msgId, msgParams, cause);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Interface for factory implementations for producing instances of {@link CannedQuery}
* based on all the query parameters.
*
* @param <R> the query result type
*
* @author Derek Hulley, janv
* @since 4.0
*/
public interface CannedQueryFactory<R>
{
/**
* Retrieve an instance of a {@link CannedQuery} based on the full range of
* available parameters.
*
* @param parameters the full query parameters
* @return an implementation that will execute the query
*/
CannedQuery<R> getCannedQuery(CannedQueryParameters parameters);
/**
* Retrieve an instance of a {@link CannedQuery} based on limited parameters.
*
* @param parameterBean the values that the query will be based on or <tt>null</tt>
* if not relevant to the query
* @param skipResults results to skip before page
* @param pageSize the size of page - ie. max items (if skipResults = 0)
* @param queryExecutionId ID of a previously-executed query to be used during follow-up
* page requests - <tt>null</tt> if not available
* @return an implementation that will execute the query
*/
CannedQuery<R> getCannedQuery(Object parameterBean, int skipResults, int pageSize, String queryExecutionId);
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Details for canned queries supporting paged results.
* <p/>
* Results are {@link #skipResults skipped}, chopped into pages of
* {@link #pageSize appropriate size} before the {@link #pageCount start page}
* and {@link #pageNumber number} are returned.
*
* @author Derek Hulley
* @since 4.0
*/
public class CannedQueryPageDetails
{
public static final int DEFAULT_SKIP_RESULTS = 0;
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
public static final int DEFAULT_PAGE_NUMBER = 1;
public static final int DEFAULT_PAGE_COUNT = 1;
private final int skipResults;
private final int pageSize;
private final int pageNumber;
private final int pageCount;
/**
* Construct with defaults
* <ul>
* <li><b>skipResults:</b> {@link #DEFAULT_SKIP_RESULTS}</li>
* <li><b>pageSize:</b> {@link #DEFAULT_PAGE_SIZE}</li>
* <li><b>pageNumber:</b> {@link #DEFAULT_PAGE_NUMBER}</li>
* <li><b>pageCount:</b> {@link #DEFAULT_PAGE_COUNT}</li>
* </ul>
*/
public CannedQueryPageDetails()
{
this(DEFAULT_SKIP_RESULTS, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_COUNT);
}
/**
* Construct with defaults
* <ul>
* <li><b>pageNumber:</b> {@link #DEFAULT_PAGE_NUMBER}</li>
* <li><b>pageCount:</b> {@link #DEFAULT_PAGE_COUNT}</li>
* </ul>
* @param skipResults results to skip before <i>page one</i>
* (default <b>{@link #DEFAULT_SKIP_RESULTS}</b>)
* @param pageSize the size of each page
* (default <b>{@link #DEFAULT_PAGE_SIZE}</b>)
*/
public CannedQueryPageDetails(int skipResults, int pageSize)
{
this (skipResults, pageSize, DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_COUNT);
}
/**
* @param skipResults results to skip before <i>page one</i>
* (default <b>{@link #DEFAULT_SKIP_RESULTS}</b>)
* @param pageSize the size of each page
* (default <b>{@link #DEFAULT_PAGE_SIZE}</b>)
* @param pageNumber the first page number to return
* (default <b>{@link #DEFAULT_PAGE_NUMBER}</b>)
* @param pageCount the number of pages to return
* (default <b>{@link #DEFAULT_PAGE_COUNT}</b>)
*/
public CannedQueryPageDetails(int skipResults, int pageSize, int pageNumber, int pageCount)
{
this.skipResults = skipResults;
this.pageSize = pageSize;
this.pageNumber = pageNumber;
this.pageCount = pageCount;
// Do some checks
if (skipResults < 0)
{
throw new IllegalArgumentException("Cannot skip fewer than 0 results.");
}
if (pageSize < 1)
{
throw new IllegalArgumentException("pageSize must be greater than zero.");
}
if (pageNumber < 1)
{
throw new IllegalArgumentException("pageNumber must be greater than zero.");
}
if (pageCount < 1)
{
throw new IllegalArgumentException("pageCount must be greater than zero.");
}
}
/**
* Helper constructor to transform a paging request into the Canned Query form.
*
* @param pagingRequest the paging details
*/
public CannedQueryPageDetails(PagingRequest pagingRequest)
{
this(pagingRequest.getSkipCount(), pagingRequest.getMaxItems());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("NamedQueryPageDetails ")
.append("[skipResults=").append(skipResults)
.append(", pageSize=").append(pageSize)
.append(", pageCount=").append(pageCount)
.append(", pageNumber=").append(pageNumber)
.append("]");
return sb.toString();
}
/**
* Get the number of query results to skip before applying further page parameters
* @return results to skip before <i>page one</i>
*/
public int getSkipResults()
{
return skipResults;
}
/**
* Get the size of each page
* @return the size of each page
*/
public int getPageSize()
{
return pageSize;
}
/**
* Get the first page number to return
* @return the first page number to return
*/
public int getPageNumber()
{
return pageNumber;
}
/**
* Get the total number of pages to return
* @return the number of pages to return
*/
public int getPageCount()
{
return pageCount;
}
/**
* Calculate the number of results that would be required to satisy this paging request.
* Note that the skip size can significantly increase this number even if the page sizes
* are small.
*
* @return the number of results required for proper paging
*/
public int getResultsRequiredForPaging()
{
int tmp = skipResults + pageCount * pageSize;
if(tmp < 0)
{
// overflow
return Integer.MAX_VALUE;
}
else
{
return tmp;
}
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Parameters defining the {@link CannedQuery named query} to execute.
* <p/>
* The implementations of the underlying queries may be vastly different
* depending on seemingly-minor variations in the parameters; only set the
* parameters that are required.
*
* @author Derek Hulley
* @since 4.0
*/
public class CannedQueryParameters
{
public static final int DEFAULT_TOTAL_COUNT_MAX = 0; // default 0 => don't request total count
private final Object parameterBean;
private final CannedQueryPageDetails pageDetails;
private final CannedQuerySortDetails sortDetails;
private final int totalResultCountMax;
private final String queryExecutionId;
/**
* <ul>
* <li><b>pageDetails</b>: <tt>null</tt></li>
* <li><b>sortDetails</b>: <tt>null</tt></li>
* <li><b>totalResultCountMax</b>: <tt>0</tt></li>
* <li><b>queryExecutionId</b>: <tt>null</tt></li>
* </ul>
*
*/
public CannedQueryParameters(Object parameterBean)
{
this (parameterBean, null, null, DEFAULT_TOTAL_COUNT_MAX, null);
}
/**
* Defaults:
* <ul>
* <li><b>pageDetails.pageNumber</b>: <tt>1</tt></li>
* <li><b>pageDetails.pageCount</b>: <tt>1</tt></li>
* <li><b>totalResultCountMax</b>: <tt>0</tt></li>
* </ul>
*
*/
public CannedQueryParameters(
Object parameterBean,
int skipResults,
int pageSize,
String queryExecutionId)
{
this (
parameterBean,
new CannedQueryPageDetails(skipResults, pageSize, CannedQueryPageDetails.DEFAULT_PAGE_NUMBER, CannedQueryPageDetails.DEFAULT_PAGE_COUNT),
null,
DEFAULT_TOTAL_COUNT_MAX,
queryExecutionId);
}
/**
* Defaults:
* <ul>
* <li><b>totalResultCountMax</b>: <tt>0</tt></li>
* <li><b>queryExecutionId</b>: <tt>null</tt></li>
* </ul>
*
*/
public CannedQueryParameters(
Object parameterBean,
CannedQueryPageDetails pageDetails,
CannedQuerySortDetails sortDetails)
{
this (parameterBean, pageDetails, sortDetails, DEFAULT_TOTAL_COUNT_MAX, null);
}
/**
* Construct all the parameters for executing a named query, using values from the
* {@link PagingRequest}.
*
* @param parameterBean the values that the query will be based on or <tt>null</tt>
* if not relevant to the query
* @param sortDetails the type of sorting to be applied or <tt>null</tt> for none
* @param pagingRequest the type of paging to be applied or <tt>null</tt> for none
*/
public CannedQueryParameters(
Object parameterBean,
CannedQuerySortDetails sortDetails,
PagingRequest pagingRequest)
{
this (
parameterBean,
pagingRequest == null ? null : new CannedQueryPageDetails(pagingRequest),
sortDetails,
pagingRequest == null ? 0 : pagingRequest.getRequestTotalCountMax(),
pagingRequest == null ? null : pagingRequest.getQueryExecutionId());
}
/**
* Construct all the parameters for executing a named query. Note that the allowable values
* for the arguments depend on the specific query being executed.
*
* @param parameterBean the values that the query will be based on or <tt>null</tt>
* if not relevant to the query
* @param pageDetails the type of paging to be applied or <tt>null</tt> for none
* @param sortDetails the type of sorting to be applied or <tt>null</tt> for none
* @param totalResultCountMax greater than zero if the query should not only return the required rows
* but should also return the total number of possible rows up to
* the given maximum.
* @param queryExecutionId ID of a previously-executed query to be used during follow-up
* page requests - <tt>null</tt> if not available
*/
@SuppressWarnings("unchecked")
public CannedQueryParameters(
Object parameterBean,
CannedQueryPageDetails pageDetails,
CannedQuerySortDetails sortDetails,
int totalResultCountMax,
String queryExecutionId)
{
if (totalResultCountMax < 0)
{
throw new IllegalArgumentException("totalResultCountMax cannot be negative.");
}
this.parameterBean = parameterBean;
this.pageDetails = pageDetails == null ? new CannedQueryPageDetails() : pageDetails;
this.sortDetails = sortDetails == null ? new CannedQuerySortDetails() : sortDetails;
this.totalResultCountMax = totalResultCountMax;
this.queryExecutionId = queryExecutionId;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("NamedQueryParameters ")
.append("[parameterBean=").append(parameterBean)
.append(", pageDetails=").append(pageDetails)
.append(", sortDetails=").append(sortDetails)
.append(", requestTotalResultCountMax=").append(totalResultCountMax)
.append(", queryExecutionId=").append(queryExecutionId)
.append("]");
return sb.toString();
}
public String getQueryExecutionId()
{
return queryExecutionId;
}
/**
* @return the sort details (never <tt>null</tt>)
*/
public CannedQuerySortDetails getSortDetails()
{
return sortDetails;
}
/**
* @return the query paging details (never <tt>null</tt>)
*/
public CannedQueryPageDetails getPageDetails()
{
return pageDetails;
}
/**
* @return if > 0 then the query should not only return the required rows but should
* also return the total count (number of possible rows) up to the given max
* if 0 then query does not need to return the total count
*/
public int getTotalResultCountMax()
{
return totalResultCountMax;
}
/**
* Helper method to get the total number of query results that need to be obtained in order
* to satisfy the {@link #getPageDetails() paging requirements}, the
* maximum result count ... and an extra to provide
* 'hasMore' functionality.
*
* @return the minimum number of results required before pages can be created
*/
public int getResultsRequired()
{
int resultsForPaging = pageDetails.getResultsRequiredForPaging();
if (resultsForPaging < Integer.MAX_VALUE) // Add one for 'hasMore'
{
resultsForPaging++;
}
int maxRequired = Math.max(totalResultCountMax, resultsForPaging);
return maxRequired;
}
/**
* @return parameterBean the values that the query will be based on or <tt>null</tt>
* if not relevant to the query
*/
public Object getParameterBean()
{
return parameterBean;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.List;
/**
* Interface for results returned by {@link CannedQuery canned queries}.
*
* @author Derek Hulley, janv
* @since 4.0
*/
public interface CannedQueryResults<R> extends PagingResults<R>
{
/**
* Get the instance of the query that generated these results.
*
* @return the query that generated these results.
*/
CannedQuery<R> getOriginatingQuery();
/**
* Get the total number of results available within the pages of this result.
* The count excludes results chopped out by the paging process i.e. it is only
* the count of results physically obtainable through this instance.
*
* @return number of results available in the pages
*/
int getPagedResultCount();
/**
* Get the number of pages available
*
* @return the number of pages available
*/
int getPageCount();
/**
* Get a single result if there is only one result expected.
*
* @return a single result
* @throws IllegalStateException if the query returned more than one result
*/
R getSingleResult();
/**
* Get the paged results
*
* @return a list of paged results
*/
List<List<R>> getPages();
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.alfresco.util.Pair;
/**
* Details for canned queries supporting sorted results
*
* @author Derek Hulley
* @since 4.0
*/
public class CannedQuerySortDetails
{
/**
* Sort ordering for the sort pairs.
* @author Derek Hulley
* @since 4.0
*/
public static enum SortOrder
{
ASCENDING,
DESCENDING
}
private final List<Pair<? extends Object, SortOrder>> sortPairs;
/**
* Construct the sort details with a variable number of sort pairs.
* <p/>
* Sorting is done by:<br/>
* <b>key:</b> the key type to sort on<br/>
* <b>sortOrder:</b> the ordering of values associated with the key<br/>
*
* @param sortPairs the sort pairs, which will be applied in order
*/
public CannedQuerySortDetails(Pair<? extends Object, SortOrder> ... sortPairs)
{
this.sortPairs = Collections.unmodifiableList(Arrays.asList(sortPairs));
}
/**
* Construct the sort details from a list of sort pairs.
* <p/>
* Sorting is done by:<br/>
* <b>key:</b> the key type to sort on<br/>
* <b>sortOrder:</b> the ordering of values associated with the key<br/>
*
* @param sortPairs the sort pairs, which will be applied in order
*/
public CannedQuerySortDetails(List<Pair<? extends Object, SortOrder>> sortPairs)
{
this.sortPairs = Collections.unmodifiableList(sortPairs);
}
@Override
public String toString()
{
return "CannedQuerySortDetails [sortPairs=" + sortPairs + "]";
}
/**
* Get the sort definitions. The instance will become unmodifiable after this has been called.
*/
public List<Pair<? extends Object, SortOrder>> getSortPairs()
{
return Collections.unmodifiableList(sortPairs);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.Collections;
import java.util.List;
import org.alfresco.util.Pair;
/**
* An always empty {@link CannedQueryResults}, used when you know
* you can short circuit a query when no results are found.
*
* @author Nick Burch
* @since 4.0
*/
public class EmptyCannedQueryResults<R> extends EmptyPagingResults<R> implements CannedQueryResults<R>
{
private CannedQuery<R> query;
public EmptyCannedQueryResults(CannedQuery<R> query)
{
this.query = query;
}
@Override
public CannedQuery<R> getOriginatingQuery() {
return query;
}
@Override
public int getPageCount() {
return 0;
}
@Override
public int getPagedResultCount() {
return 0;
}
@Override
public List<List<R>> getPages() {
return Collections.emptyList();
}
@Override
public R getSingleResult() {
return null;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.Collections;
import java.util.List;
import org.alfresco.util.Pair;
/**
* An always empty {@link PagingResults}, used when you know
* you can short circuit a query when no results are found.
*
* @author Nick Burch
* @since 4.0
*/
public class EmptyPagingResults<R> implements PagingResults<R>
{
/**
* Returns an empty page
*/
public List<R> getPage()
{
return Collections.emptyList();
}
/**
* No more items remain
*/
public boolean hasMoreItems()
{
return false;
}
/**
* There are no results
*/
public Pair<Integer, Integer> getTotalResultCount()
{
return new Pair<Integer,Integer>(0,0);
}
/**
* There is no unique query ID, as no query was done
*/
public String getQueryExecutionId()
{
return null;
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.Collections;
import java.util.List;
import org.alfresco.util.Pair;
/**
* Wraps a list of items as a {@link PagingResults}, used typically when
* migrating from a full listing system to a paged one.
*
* @author Nick Burch
* @since Odin
*/
public class ListBackedPagingResults<R> implements PagingResults<R>
{
private List<R> results;
private int size;
private boolean hasMore;
public ListBackedPagingResults(List<R> list)
{
this.results = Collections.unmodifiableList(list);
// No more items remain, the page is everything
size = list.size();
hasMore = false;
}
public ListBackedPagingResults(List<R> list, PagingRequest paging)
{
// Excerpt
int start = paging.getSkipCount();
int end = Math.min(list.size(), start + paging.getMaxItems());
if (paging.getMaxItems() == 0)
{
end = list.size();
}
this.results = Collections.unmodifiableList(
list.subList(start, end));
this.size = list.size();
this.hasMore = ! (list.size() == end);
}
/**
* Returns the whole set of results as one page
*/
public List<R> getPage()
{
return results;
}
public boolean hasMoreItems()
{
return hasMore;
}
/**
* We know exactly how many results there are
*/
public Pair<Integer, Integer> getTotalResultCount()
{
return new Pair<Integer,Integer>(size, size);
}
/**
* There is no unique query ID, as no query was done
*/
public String getQueryExecutionId()
{
return null;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Stores paging details based on a PagingRequest.
*
* @author steveglover
*
*/
public class PageDetails
{
private boolean hasMoreItems = false;
private int pageSize;
private int skipCount;
private int maxItems;
private int end;
public PageDetails(int pageSize, boolean hasMoreItems, int skipCount, int maxItems, int end)
{
super();
this.hasMoreItems = hasMoreItems;
this.pageSize = pageSize;
this.skipCount = skipCount;
this.maxItems = maxItems;
this.end = end;
}
public int getSkipCount()
{
return skipCount;
}
public int getMaxItems()
{
return maxItems;
}
public int getEnd()
{
return end;
}
public boolean hasMoreItems()
{
return hasMoreItems;
}
public int getPageSize()
{
return pageSize;
}
public static PageDetails getPageDetails(PagingRequest pagingRequest, int totalSize)
{
int skipCount = pagingRequest.getSkipCount();
int maxItems = pagingRequest.getMaxItems();
int end = skipCount + maxItems;
int pageSize = -1;
if(end < 0 || end > totalSize)
{
// overflow or greater than the total
end = totalSize;
pageSize = end - skipCount;
}
else
{
pageSize = maxItems;
}
if(pageSize < 0)
{
pageSize = 0;
}
boolean hasMoreItems = end < totalSize;
return new PageDetails(pageSize, hasMoreItems, skipCount, maxItems, end);
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import org.alfresco.api.AlfrescoPublicApi;
/**
* Simple wrapper for single page request (with optional request for total count up to a given max)
*
* @author janv
* @since 4.0
*/
@AlfrescoPublicApi
public class PagingRequest
{
private int skipCount = CannedQueryPageDetails.DEFAULT_SKIP_RESULTS;
private int maxItems;
private int requestTotalCountMax = 0; // request total count up to a given max (0 => do not request total count)
private String queryExecutionId;
/**
* Construct a page request
*
* @param maxItems the maximum number of items per page
*/
public PagingRequest(int maxItems)
{
this.maxItems = maxItems;
}
/**
* Construct a page request
*
* @param maxItems the maximum number of items per page
* @param skipCount the number of items to skip before the first page
*/
public PagingRequest(int skipCount, int maxItems)
{
this.skipCount = skipCount;
this.maxItems = maxItems;
}
/**
* Construct a page request
*
* @param maxItems the maximum number of items per page
* @param queryExecutionId a query execution ID associated with ealier paged requests
*/
public PagingRequest(int maxItems, String queryExecutionId)
{
setMaxItems(maxItems);
this.queryExecutionId = queryExecutionId;
}
/**
* Construct a page request
*
* @param skipCount the number of items to skip before the first page
* @param maxItems the maximum number of items per page
* @param queryExecutionId a query execution ID associated with ealier paged requests
*/
public PagingRequest(int skipCount, int maxItems, String queryExecutionId)
{
setSkipCount(skipCount);
setMaxItems(maxItems);
this.queryExecutionId = queryExecutionId;
}
/**
* Results to skip before retrieving the page. Usually a multiple of page size (ie. page size * num pages to skip).
* Default is 0.
*
* @return the number of results to skip before the page
*/
public int getSkipCount()
{
return skipCount;
}
/**
* Change the skip count. Must be called before the paging query is run.
*/
protected void setSkipCount(int skipCount)
{
this.skipCount = (skipCount < 0 ? CannedQueryPageDetails.DEFAULT_SKIP_RESULTS : skipCount);
}
/**
* Size of the page - if skip count is 0 then return up to max items.
*
* @return the maximum size of the page
*/
public int getMaxItems()
{
return maxItems;
}
/**
* Change the size of the page. Must be called before the paging query is run.
*/
protected void setMaxItems(int maxItems)
{
this.maxItems = (maxItems < 0 ? CannedQueryPageDetails.DEFAULT_PAGE_SIZE : maxItems);
}
/**
* Get requested total count (up to a given maximum).
*/
public int getRequestTotalCountMax()
{
return requestTotalCountMax;
}
/**
* Set request total count (up to a given maximum). Default is 0 => do not request total count (which allows possible query optimisation).
*
* @param requestTotalCountMax
*/
public void setRequestTotalCountMax(int requestTotalCountMax)
{
this.requestTotalCountMax = requestTotalCountMax;
}
/**
* Get a unique ID associated with these query results. This must be available before and
* after execution i.e. it must depend on the type of query and the query parameters
* rather than the execution results. Client has the option to pass this back as a hint when
* paging.
*
* @return a unique ID associated with the query execution results
*/
public String getQueryExecutionId()
{
return queryExecutionId;
}
/**
* Change the unique query ID for the results. Must be called before the paging query is run.
*/
protected void setQueryExecutionId(String queryExecutionId)
{
this.queryExecutionId = queryExecutionId;
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
import java.util.List;
import org.alfresco.api.AlfrescoPublicApi;
import org.alfresco.util.Pair;
/**
* Marker interface for single page of results
*
* @author janv
* @since 4.0
*/
@AlfrescoPublicApi
public interface PagingResults<R>
{
/**
* Get the page of results.
*
* @return the results - possibly empty but never <tt>null</tt>
*/
public List<R> getPage();
/**
* True if more items on next page.
* <p/>
* Note: could also return true if page was cutoff/trimmed for some reason
* (eg. due to permission checks of large page of requested max items)
*
* @return true if more items (eg. on next page)<br/>
* - true => at least one more page (or incomplete page - if cutoff)<br/>
* - false => last page (or incomplete page - if cutoff)
*/
public boolean hasMoreItems();
/**
* Get the total result count assuming no paging applied. This value will only be available if
* the query supports it and the client requested it. By default, it is not requested.
* <p/>
* Returns result as an approx "range" pair <lower, upper>
* <ul>
* <li>null (or lower is null): unknown total count (or not requested by the client).</li>
* <li>lower = upper : total count should be accurate</li>
* <li>lower < upper : total count is an approximation ("about") - somewhere in the given range (inclusive)</li>
* <li>upper is null : total count is "more than" lower (upper is unknown)</li>
* </ul>
*
* @return Returns the total results as a range (all results, including the paged results returned)
*/
public Pair<Integer, Integer> getTotalResultCount();
/**
* Get a unique ID associated with these query results. This must be available before and
* after execution i.e. it must depend on the type of query and the query parameters
* rather than the execution results. Client has the option to pass this back as a hint when
* paging.
*
* @return a unique ID associated with the query execution results
*/
public String getQueryExecutionId();
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.query;
/**
* Marker interface to show that permissions have already been applied to the results (and possibly cutoff)
*
* @author janv
* @since 4.0
*/
public interface PermissionedResults
{
/**
* @return <tt>true</tt> - if permissions have been applied to the results
*/
public boolean permissionsApplied();
/**
* @return <tt>true</tt> - if permission checks caused results to be cutoff (either due to max count or max time)
*/
public boolean hasMoreItems();
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.scripts;
import org.alfresco.error.AlfrescoRuntimeException;
/**
* @author Kevin Roast
*/
public class ScriptException extends AlfrescoRuntimeException
{
private static final long serialVersionUID = 1739480648583299623L;
/**
* @param msgId String
*/
public ScriptException(String msgId)
{
super(msgId);
}
/**
* @param msgId String
* @param cause Throwable
*/
public ScriptException(String msgId, Throwable cause)
{
super(msgId, cause);
}
/**
* @param msgId String
* @param params Object[]
*/
public ScriptException(String msgId, Object[] params)
{
super(msgId, params);
}
/**
* @param msgId String
* @param msgParams Object[]
* @param cause Throwable
*/
public ScriptException(String msgId, Object[] msgParams, Throwable cause)
{
super(msgId, msgParams, cause);
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.scripts;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
/**
* @author Kevin Roast
*/
public class ScriptResourceHelper
{
private static final String SCRIPT_ROOT = "_root";
private static final String IMPORT_PREFIX = "<import";
private static final String IMPORT_RESOURCE = "resource=\"";
/**
* Resolve the import directives in the specified script. The implementation of the supplied
* ScriptResourceLoader instance is responsible for handling the resource retrieval.
* <p>
* Multiple includes of the same resource are dealt with correctly and nested includes of scripts
* is fully supported.
* <p>
* Note that for performance reasons the script import directive syntax and placement in the file
* is very strict. The import lines <i>must</i> always be first in the file - even before any comments.
* Immediately that the script service detects a non-import line it will assume the rest of the
* file is executable script and no longer attempt to search for any further import directives. Therefore
* all imports should be at the top of the script, one following the other, in the correct syntax and
* with no comments present - the only separators valid between import directives is white space.
*
* @param script The script content to resolve imports in
*
* @return a valid script with all nested includes resolved into a single script instance
*/
public static String resolveScriptImports(String script, ScriptResourceLoader loader, Log logger)
{
// use a linked hashmap to preserve order of includes - the key in the collection is used
// to resolve multiple includes of the same scripts and therefore cyclic includes also
Map<String, String> scriptlets = new LinkedHashMap<String, String>(8, 1.0f);
// perform a recursive resolve of all script imports
recurseScriptImports(SCRIPT_ROOT, script, loader, scriptlets, logger);
if (scriptlets.size() == 1)
{
// quick exit for single script with no includes
if (logger.isTraceEnabled())
logger.trace("Script content resolved to:\r\n" + script);
return script;
}
else
{
// calculate total size of buffer required for the script and all includes
int length = 0;
for (String scriptlet : scriptlets.values())
{
length += scriptlet.length();
}
// append the scripts together to make a single script
StringBuilder result = new StringBuilder(length);
for (String scriptlet : scriptlets.values())
{
result.append(scriptlet);
}
if (logger.isTraceEnabled())
logger.trace("Script content resolved to:\r\n" + result.toString());
return result.toString();
}
}
/**
* Recursively resolve imports in the specified scripts, adding the imports to the
* specific list of scriplets to combine later.
*
* @param location Script location - used to ensure duplicates are not added
* @param script The script to recursively resolve imports for
* @param scripts The collection of scriplets to execute with imports resolved and removed
*/
private static void recurseScriptImports(
String location, String script, ScriptResourceLoader loader, Map<String, String> scripts, Log logger)
{
int index = 0;
// skip any initial whitespace
for (; index<script.length(); index++)
{
if (Character.isWhitespace(script.charAt(index)) == false)
{
break;
}
}
// look for the "<import" directive marker
if (script.startsWith(IMPORT_PREFIX, index))
{
// skip whitespace between "<import" and "resource"
boolean afterWhitespace = false;
index += IMPORT_PREFIX.length() + 1;
for (; index<script.length(); index++)
{
if (Character.isWhitespace(script.charAt(index)) == false)
{
afterWhitespace = true;
break;
}
}
if (afterWhitespace == true && script.startsWith(IMPORT_RESOURCE, index))
{
// found an import line!
index += IMPORT_RESOURCE.length();
int resourceStart = index;
for (; index<script.length(); index++)
{
if (script.charAt(index) == '"' && script.charAt(index + 1) == '>')
{
// found end of import line - so we have a resource path
String resource = script.substring(resourceStart, index);
if (logger.isDebugEnabled())
logger.debug("Found script resource import: " + resource);
if (scripts.containsKey(resource) == false)
{
// load the script resource (and parse any recursive includes...)
String includedScript = loader.loadScriptResource(resource);
if (includedScript != null)
{
if (logger.isDebugEnabled())
logger.debug("Succesfully located script '" + resource + "'");
recurseScriptImports(resource, includedScript, loader, scripts, logger);
}
}
else
{
if (logger.isDebugEnabled())
logger.debug("Note: already imported resource: " + resource);
}
// continue scanning this script for additional includes
// skip the last two characters of the import directive
for (index += 2; index<script.length(); index++)
{
if (Character.isWhitespace(script.charAt(index)) == false)
{
break;
}
}
recurseScriptImports(location, script.substring(index), loader, scripts, logger);
return;
}
}
// if we get here, we failed to find the end of an import line
throw new ScriptException(
"Malformed 'import' line - must be first in file, no comments and strictly of the form:" +
"\r\n<import resource=\"...\">");
}
else
{
throw new ScriptException(
"Malformed 'import' line - must be first in file, no comments and strictly of the form:" +
"\r\n<import resource=\"...\">");
}
}
else
{
// no (further) includes found - include the original script content
if (logger.isDebugEnabled())
logger.debug("Imports resolved, adding resource '" + location);
if (logger.isTraceEnabled())
logger.trace(script);
scripts.put(location, script);
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.scripts;
/**
* @author Kevin Roast
*/
public interface ScriptResourceLoader
{
public String loadScriptResource(String resource);
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import org.alfresco.api.AlfrescoPublicApi;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.util.bean.BooleanBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.quartz.JobDetailAwareTrigger;
/**
* A utility bean to wrap sceduling a job with a scheduler.
*
* @author Andy Hind
*/
@AlfrescoPublicApi
public abstract class AbstractTriggerBean implements InitializingBean, JobDetailAwareTrigger, BeanNameAware, DisposableBean
{
protected static Log logger = LogFactory.getLog(AbstractTriggerBean.class);
private JobDetail jobDetail;
private Scheduler scheduler;
private String beanName;
private Trigger trigger;
private boolean enabled = true;
public AbstractTriggerBean()
{
super();
}
/**
* Get the definition of the job to run.
*/
public JobDetail getJobDetail()
{
return jobDetail;
}
/**
* Set the definition of the job to run.
*
* @param jobDetail
*/
public void setJobDetail(JobDetail jobDetail)
{
this.jobDetail = jobDetail;
}
/**
* Get the scheduler with which the job and trigger are scheduled.
*
* @return The scheduler
*/
public Scheduler getScheduler()
{
return scheduler;
}
/**
* Set the scheduler.
*
* @param scheduler
*/
public void setScheduler(Scheduler scheduler)
{
this.scheduler = scheduler;
}
/**
* Set the scheduler
*/
public void afterPropertiesSet() throws Exception
{
// Check properties are set
if (jobDetail == null)
{
throw new AlfrescoRuntimeException("Job detail has not been set");
}
if (scheduler == null)
{
logger.warn("Job " + getBeanName() + " is not active");
}
else if (!enabled)
{
logger.warn("Job " + getBeanName() + " is not enabled");
}
else
{
logger.info("Job " + getBeanName() + " is active and enabled");
// Register the job with the scheduler
this.trigger = getTrigger();
if (this.trigger == null)
{
logger.error("Job " + getBeanName() + " is not active (invalid trigger)");
}
else
{
logger.info("Job " + getBeanName() + " is active");
String jobName = jobDetail.getKey().getName();
String groupName = jobDetail.getKey().getGroup();
if(scheduler.getJobDetail(jobName, groupName) != null)
{
// Job is already defined delete it
if(logger.isDebugEnabled())
{
logger.debug("job already registered with scheduler jobName:" + jobName);
}
scheduler.deleteJob(jobName, groupName);
}
if(logger.isDebugEnabled())
{
logger.debug("schedule job:" + jobDetail + " using " + this.trigger
+ " startTime: " + this.trigger.getStartTime());
}
scheduler.scheduleJob(jobDetail, this.trigger);
}
}
}
/**
* Ensures that the job is unscheduled with the context is shut down.
*/
public void destroy() throws Exception
{
if (this.trigger != null)
{
if (!this.scheduler.isShutdown())
{
scheduler.unscheduleJob(this.trigger.getName(), this.trigger.getGroup());
}
this.trigger = null;
}
}
/**
* Abstract method for implementations to build their trigger.
*
* @return The trigger
* @throws Exception
*/
public abstract Trigger getTrigger() throws Exception;
/**
* Get the bean name as this trigger is created
*/
public void setBeanName(String name)
{
this.beanName = name;
}
/**
* Get the bean/trigger name.
*
* @return The name of the bean
*/
public String getBeanName()
{
return beanName;
}
public boolean isEnabled()
{
return enabled;
}
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
public void setEnabledFromBean(BooleanBean enabled)
{
this.enabled = enabled.isTrue();
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.util.HashMap;
import java.util.Map;
/**
* Utility class to assist in extracting program arguments.
*
* @author Derek Hulley
* @since V2.1-A
*/
public class ArgumentHelper
{
private String usage;
private Map<String, String> args;
public static Map<String, String> ripArgs(String ... args)
{
Map<String, String> argsMap = new HashMap<String, String>(5);
for (String arg : args)
{
int index = arg.indexOf('=');
if (!arg.startsWith("--") || index < 0 || index == arg.length() - 1)
{
// Ignore it
continue;
}
String name = arg.substring(2, index);
String value = arg.substring(index + 1, arg.length());
argsMap.put(name, value);
}
return argsMap;
}
public ArgumentHelper(String usage, String[] args)
{
this.usage = usage;
this.args = ArgumentHelper.ripArgs(args);
}
/**
* @throws IllegalArgumentException if the argument doesn't match the requirements.
*/
public String getStringValue(String arg, boolean mandatory, boolean nonEmpty)
{
String value = args.get(arg);
if (value == null && mandatory)
{
throw new IllegalArgumentException("Argument '" + arg + "' is required.");
}
else if (value != null && value.length() == 0 && nonEmpty)
{
throw new IllegalArgumentException("Argument '" + arg + "' may not be empty.");
}
return value;
}
/**
* @return Returns the value assigned or the minimum value if the parameter was not present
* @throws IllegalArgumentException if the argument doesn't match the requirements.
*/
public int getIntegerValue(String arg, boolean mandatory, int minValue, int maxValue)
{
String valueStr = args.get(arg);
if (valueStr == null)
{
if (mandatory)
{
throw new IllegalArgumentException("Argument '" + arg + "' is required.");
}
else
{
return minValue;
}
}
// Now convert
try
{
int value = Integer.parseInt(valueStr);
if (value < minValue || value > maxValue)
{
throw new IllegalArgumentException("Argument '" + arg + "' must be in range " + minValue + " to " + maxValue + ".");
}
return value;
}
catch (NumberFormatException e)
{
throw new IllegalArgumentException("Argument '" + arg + "' must be a valid integer.");
}
}
public void printUsage()
{
System.out.println(usage);
}
}

View File

@@ -0,0 +1,445 @@
/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Generic bridge table support with optional reference counting to allow multiple membership for an object via several
* relationships.
*
* @author Andy
*/
public class BridgeTable<T>
{
HashMap<T, HashMap<Integer, HashMap<T, Counter>>> descendants = new HashMap<T, HashMap<Integer, HashMap<T, Counter>>>();
HashMap<T, HashMap<Integer, HashMap<T, Counter>>> ancestors = new HashMap<T, HashMap<Integer, HashMap<T, Counter>>>();
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void addLink(T parent, T child)
{
readWriteLock.writeLock().lock();
try
{
addDescendants(parent, child);
addAncestors(parent, child);
}
finally
{
readWriteLock.writeLock().unlock();
}
}
public void addLink(Pair<T, T> link)
{
addLink(link.getFirst(), link.getSecond());
}
public void addLinks(Collection<Pair<T, T>> links)
{
for (Pair<T, T> link : links)
{
addLink(link);
}
}
public void removeLink(T parent, T child)
{
readWriteLock.writeLock().lock();
try
{
removeDescendants(parent, child);
removeAncestors(parent, child);
}
finally
{
readWriteLock.writeLock().unlock();
}
}
public void removeLink(Pair<T, T> link)
{
removeLink(link.getFirst(), link.getSecond());
}
public void removeLinks(Collection<Pair<T, T>> links)
{
for (Pair<T, T> link : links)
{
removeLink(link);
}
}
public HashSet<T> getDescendants(T node)
{
return getDescendants(node, 1, Integer.MAX_VALUE);
}
public HashSet<T> getDescendants(T node, int position)
{
return getDescendants(node, position, position);
}
public HashSet<T> getDescendants(T node, int start, int end)
{
HashSet<T> answer = new HashSet<T>();
HashMap<Integer, HashMap<T, Counter>> found = descendants.get(node);
if (found != null)
{
for (Integer key : found.keySet())
{
if ((key.intValue() >= start) && (key.intValue() <= end))
{
HashMap<T, Counter> asd = found.get(key);
answer.addAll(asd.keySet());
}
}
}
return answer;
}
public HashSet<T> getAncestors(T node)
{
return getAncestors(node, 1, Integer.MAX_VALUE);
}
public HashSet<T> getAncestors(T node, int position)
{
return getAncestors(node, position, position);
}
public HashSet<T> getAncestors(T node, int start, int end)
{
HashSet<T> answer = new HashSet<T>();
HashMap<Integer, HashMap<T, Counter>> found = ancestors.get(node);
if (found != null)
{
for (Integer key : found.keySet())
{
if ((key.intValue() >= start) && (key.intValue() <= end))
{
HashMap<T, Counter> asd = found.get(key);
answer.addAll(asd.keySet());
}
}
}
return answer;
}
/**
* @param parent T
* @param child T
*/
private void addDescendants(T parent, T child)
{
HashMap<Integer, HashMap<T, Counter>> parentsDescendants = descendants.get(parent);
if (parentsDescendants == null)
{
parentsDescendants = new HashMap<Integer, HashMap<T, Counter>>();
descendants.put(parent, parentsDescendants);
}
HashMap<Integer, HashMap<T, Counter>> childDescendantsToAdd = descendants.get(child);
// add all the childs children to the parents descendants
add(childDescendantsToAdd, Integer.valueOf(0), parentsDescendants, child);
// add childs descendants to all parents ancestors at the correct depth
HashMap<Integer, HashMap<T, Counter>> ancestorsToFixUp = ancestors.get(parent);
if (ancestorsToFixUp != null)
{
for (Integer ancestorPosition : ancestorsToFixUp.keySet())
{
HashMap<T, Counter> ancestorsToFixUpAtPosition = ancestorsToFixUp.get(ancestorPosition);
for (T ancestorToFixUpAtPosition : ancestorsToFixUpAtPosition.keySet())
{
HashMap<Integer, HashMap<T, Counter>> ancestorDescendants = descendants.get(ancestorToFixUpAtPosition);
add(childDescendantsToAdd, ancestorPosition, ancestorDescendants, child);
}
}
}
}
/**
* @param parent T
* @param child T
*/
private void removeDescendants(T parent, T child)
{
HashMap<Integer, HashMap<T, Counter>> parentsDescendants = descendants.get(parent);
if (parentsDescendants == null)
{
return;
}
HashMap<Integer, HashMap<T, Counter>> childDescendantsToRemove = descendants.get(child);
// add all the childs children to the parents descendants
remove(childDescendantsToRemove, Integer.valueOf(0), parentsDescendants, child);
// add childs descendants to all parents ancestors at the correct depth
HashMap<Integer, HashMap<T, Counter>> ancestorsToFixUp = ancestors.get(parent);
if (ancestorsToFixUp != null)
{
for (Integer ancestorPosition : ancestorsToFixUp.keySet())
{
HashMap<T, Counter> ancestorsToFixUpAtPosition = ancestorsToFixUp.get(ancestorPosition);
for (T ancestorToFixUpAtPosition : ancestorsToFixUpAtPosition.keySet())
{
HashMap<Integer, HashMap<T, Counter>> ancestorDescendants = descendants.get(ancestorToFixUpAtPosition);
remove(childDescendantsToRemove, ancestorPosition, ancestorDescendants, child);
}
}
}
}
/**
* @param parent T
* @param child T
*/
private void removeAncestors(T parent, T child)
{
HashMap<Integer, HashMap<T, Counter>> childsAncestors = ancestors.get(child);
if (childsAncestors == null)
{
return;
}
HashMap<Integer, HashMap<T, Counter>> parentAncestorsToRemove = ancestors.get(parent);
// add all the childs children to the parents descendants
remove(parentAncestorsToRemove, Integer.valueOf(0), childsAncestors, parent);
// add childs descendants to all parents ancestors at the correct depth
HashMap<Integer, HashMap<T, Counter>> decendantsToFixUp = descendants.get(child);
if (decendantsToFixUp != null)
{
for (Integer descendantPosition : decendantsToFixUp.keySet())
{
HashMap<T, Counter> decendantsToFixUpAtPosition = decendantsToFixUp.get(descendantPosition);
for (T descendantToFixUpAtPosition : decendantsToFixUpAtPosition.keySet())
{
HashMap<Integer, HashMap<T, Counter>> descendantAncestors = ancestors.get(descendantToFixUpAtPosition);
remove(parentAncestorsToRemove, descendantPosition, descendantAncestors, parent);
}
}
}
}
/**
* @param toAdd HashMap<Integer, HashMap<T, Counter>>
* @param position Integer
* @param target HashMap<Integer, HashMap<T, Counter>>
* @param node T
*/
private void add(HashMap<Integer, HashMap<T, Counter>> toAdd, Integer position, HashMap<Integer, HashMap<T, Counter>> target, T node)
{
// add direct child
Integer directKey = Integer.valueOf(position.intValue() + 1);
HashMap<T, Counter> direct = target.get(directKey);
if (direct == null)
{
direct = new HashMap<T, Counter>();
target.put(directKey, direct);
}
Counter counter = direct.get(node);
if (counter == null)
{
counter = new Counter();
direct.put(node, counter);
}
counter.increment();
if (toAdd != null)
{
for (Integer depth : toAdd.keySet())
{
Integer newKey = Integer.valueOf(position.intValue() + depth.intValue() + 1);
HashMap<T, Counter> toAddAtDepth = toAdd.get(depth);
HashMap<T, Counter> targetAtDepthPlusOne = target.get(newKey);
if (targetAtDepthPlusOne == null)
{
targetAtDepthPlusOne = new HashMap<T, Counter>();
target.put(newKey, targetAtDepthPlusOne);
}
for (T key : toAddAtDepth.keySet())
{
Counter counterToAdd = toAddAtDepth.get(key);
Counter counterToAddTo = targetAtDepthPlusOne.get(key);
if (counterToAddTo == null)
{
counterToAddTo = new Counter();
targetAtDepthPlusOne.put(key, counterToAddTo);
}
counterToAddTo.add(counterToAdd);
}
}
}
}
/**
* @param toRemove HashMap<Integer, HashMap<T, Counter>>
* @param position Integer
* @param target HashMap<Integer, HashMap<T, Counter>>
* @param node T
*/
private void remove(HashMap<Integer, HashMap<T, Counter>> toRemove, Integer position, HashMap<Integer, HashMap<T, Counter>> target, T node)
{
// remove direct child
Integer directKey = Integer.valueOf(position.intValue() + 1);
HashMap<T, Counter> direct = target.get(directKey);
if (direct != null)
{
Counter counter = direct.get(node);
if (counter != null)
{
counter.decrement();
if (counter.getCount() == 0)
{
direct.remove(node);
}
}
}
if (toRemove != null)
{
for (Integer depth : toRemove.keySet())
{
Integer newKey = Integer.valueOf(position.intValue() + depth.intValue() + 1);
HashMap<T, Counter> toRemoveAtDepth = toRemove.get(depth);
HashMap<T, Counter> targetAtDepthPlusOne = target.get(newKey);
if (targetAtDepthPlusOne != null)
{
for (T key : toRemoveAtDepth.keySet())
{
Counter counterToRemove = toRemoveAtDepth.get(key);
Counter counterToRemoveFrom = targetAtDepthPlusOne.get(key);
if (counterToRemoveFrom != null)
{
counterToRemoveFrom.remove(counterToRemove);
if (counterToRemoveFrom.getCount() == 0)
{
targetAtDepthPlusOne.remove(key);
}
}
}
}
}
}
}
/**
* @param parent T
* @param child T
*/
private void addAncestors(T parent, T child)
{
HashMap<Integer, HashMap<T, Counter>> childsAncestors = ancestors.get(child);
if (childsAncestors == null)
{
childsAncestors = new HashMap<Integer, HashMap<T, Counter>>();
ancestors.put(child, childsAncestors);
}
HashMap<Integer, HashMap<T, Counter>> parentAncestorsToAdd = ancestors.get(parent);
// add all the childs children to the parents descendants
add(parentAncestorsToAdd, Integer.valueOf(0), childsAncestors, parent);
// add childs descendants to all parents ancestors at the correct depth
HashMap<Integer, HashMap<T, Counter>> descenantsToFixUp = descendants.get(child);
if (descenantsToFixUp != null)
{
for (Integer descendantPosition : descenantsToFixUp.keySet())
{
HashMap<T, Counter> descenantsToFixUpAtPosition = descenantsToFixUp.get(descendantPosition);
for (T descenantToFixUpAtPosition : descenantsToFixUpAtPosition.keySet())
{
HashMap<Integer, HashMap<T, Counter>> descendatAncestors = ancestors.get(descenantToFixUpAtPosition);
add(parentAncestorsToAdd, descendantPosition, descendatAncestors, parent);
}
}
}
}
public int size()
{
return ancestors.size();
}
private static class Counter
{
int count = 0;
void increment()
{
count++;
}
void decrement()
{
count--;
}
int getCount()
{
return count;
}
void add(Counter other)
{
count += other.count;
}
void remove(Counter other)
{
count -= other.count;
}
}
/**
* @return Set<T>
*/
public Set<T> keySet()
{
return ancestors.keySet();
}
}

View File

@@ -0,0 +1,441 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.WeakHashMap;
import org.alfresco.error.AlfrescoRuntimeException;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.extensions.surf.exception.PlatformRuntimeException;
/**
* Provides <b>thread safe</b> means of obtaining a cached date formatter.
* <p>
* The cached string-date mappings are stored in a <tt>WeakHashMap</tt>.
*
* @see java.text.DateFormat#setLenient(boolean)
*
* @author Derek Hulley
*/
public class CachingDateFormat extends SimpleDateFormat
{
private static final long serialVersionUID = 3258415049197565235L;
/** <pre> yyyy-MM-dd'T'HH:mm:ss </pre> */
public static final String FORMAT_FULL_GENERIC = "yyyy-MM-dd'T'HH:mm:ss";
/** <pre> yyyy-MM-dd'T'HH:mm:ss </pre> */
public static final String FORMAT_CMIS_SQL = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
public static final String FORMAT_SOLR = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
public static final StringAndResolution[] LENIENT_FORMATS;
static
{
ArrayList<StringAndResolution> list = new ArrayList<StringAndResolution> ();
list.add( new StringAndResolution("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Calendar.MILLISECOND));
list.add( new StringAndResolution("yyyy-MM-dd'T'HH:mm:ss.SSS", Calendar.MILLISECOND));
list.add( new StringAndResolution("yyyy-MM-dd'T'HH:mm:ssZ", Calendar.SECOND));
list.add( new StringAndResolution("yyyy-MM-dd'T'HH:mm:ss", Calendar.SECOND));
list.add( new StringAndResolution("yyyy-MM-dd'T'HH:mmZ", Calendar.MINUTE));
list.add( new StringAndResolution("yyyy-MM-dd'T'HH:mm", Calendar.MINUTE));
list.add( new StringAndResolution("yyyy-MM-dd'T'HHZ", Calendar.HOUR_OF_DAY));
list.add( new StringAndResolution("yyyy-MM-dd'T'HH", Calendar.HOUR_OF_DAY));
list.add( new StringAndResolution("yyyy-MM-dd'T'Z", Calendar.DAY_OF_MONTH));
list.add( new StringAndResolution("yyyy-MM-dd'T'", Calendar.DAY_OF_MONTH));
list.add( new StringAndResolution("yyyy-MM-ddZ", Calendar.DAY_OF_MONTH));
list.add( new StringAndResolution("yyyy-MM-dd", Calendar.DAY_OF_MONTH));
list.add( new StringAndResolution("yyyy-MMZ", Calendar.MONTH));
list.add( new StringAndResolution("yyyy-MM", Calendar.MONTH));
// year would duplicate :-) and eat stuff
list.add( new StringAndResolution( "yyyy-MMM-dd'T'HH:mm:ss.SSSZ", Calendar.MILLISECOND));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'HH:mm:ss.SSS", Calendar.MILLISECOND));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'HH:mm:ssZ", Calendar.SECOND));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'HH:mm:ss", Calendar.SECOND));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'HH:mmZ", Calendar.MINUTE));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'HH:mm", Calendar.MINUTE));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'HHZ", Calendar.HOUR_OF_DAY));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'HH", Calendar.HOUR_OF_DAY));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'Z",Calendar.DAY_OF_MONTH));
list.add( new StringAndResolution( "yyyy-MMM-dd'T'",Calendar.DAY_OF_MONTH));
list.add( new StringAndResolution( "yyyy-MMM-ddZ", Calendar.DAY_OF_MONTH));
list.add( new StringAndResolution( "yyyy-MMM-dd", Calendar.DAY_OF_MONTH));
list.add( new StringAndResolution( "yyyy-MMMZ", Calendar.MONTH));
list.add( new StringAndResolution( "yyyy-MMM", Calendar.MONTH));
list.add( new StringAndResolution("yyyyZ", Calendar.YEAR));
list.add( new StringAndResolution("yyyy", Calendar.YEAR));
LENIENT_FORMATS = list.toArray(new StringAndResolution[]{});
}
/** <pre> yyyy-MM-dd </pre> */
public static final String FORMAT_DATE_GENERIC = "yyyy-MM-dd";
/** <pre> HH:mm:ss </pre> */
public static final String FORMAT_TIME_GENERIC = "HH:mm:ss";
private static ThreadLocal<SimpleDateFormat> s_localDateFormat = new ThreadLocal<SimpleDateFormat>();
private static ThreadLocal<SimpleDateFormat> s_localDateOnlyFormat = new ThreadLocal<SimpleDateFormat>();
private static ThreadLocal<SimpleDateFormat> s_localTimeOnlyFormat = new ThreadLocal<SimpleDateFormat>();
private static ThreadLocal<SimpleDateFormat> s_localCmisSqlDatetime = new ThreadLocal<SimpleDateFormat>();
private static ThreadLocal<SimpleDateFormat> s_localSolrDatetime = new ThreadLocal<SimpleDateFormat>();
private static ThreadLocal<SimpleDateFormatAndResolution[]> s_lenientParsers = new ThreadLocal<SimpleDateFormatAndResolution[]>();
transient private Map<String, Date> cacheDates = new WeakHashMap<String, Date>(89);
private CachingDateFormat(String format)
{
super(format);
}
public String toString()
{
return this.toPattern();
}
/**
* @param length
* the type of date format, e.g. {@link CachingDateFormat#LONG }
* @param locale
* the <code>Locale</code> that will be used to determine the
* date pattern
*
* @see #getDateFormat(String, boolean)
* @see CachingDateFormat#SHORT
* @see CachingDateFormat#MEDIUM
* @see CachingDateFormat#LONG
* @see CachingDateFormat#FULL
*/
public static SimpleDateFormat getDateFormat(int length, Locale locale, boolean lenient)
{
SimpleDateFormat dateFormat = (SimpleDateFormat) CachingDateFormat.getDateInstance(length, locale);
// extract the format string
String pattern = dateFormat.toPattern();
// we have a pattern to use
return getDateFormat(pattern, lenient);
}
/**
* @param dateLength
* the type of date format, e.g. {@link CachingDateFormat#LONG }
* @param timeLength
* the type of time format, e.g. {@link CachingDateFormat#LONG }
* @param locale
* the <code>Locale</code> that will be used to determine the
* date pattern
*
* @see #getDateFormat(String, boolean)
* @see CachingDateFormat#SHORT
* @see CachingDateFormat#MEDIUM
* @see CachingDateFormat#LONG
* @see CachingDateFormat#FULL
*/
public static SimpleDateFormat getDateTimeFormat(int dateLength, int timeLength, Locale locale, boolean lenient)
{
SimpleDateFormat dateFormat = (SimpleDateFormat) CachingDateFormat.getDateTimeInstance(dateLength, timeLength, locale);
// extract the format string
String pattern = dateFormat.toPattern();
// we have a pattern to use
return getDateFormat(pattern, lenient);
}
/**
* @param pattern
* the conversion pattern to use
* @param lenient
* true to allow the parser to extract the date in conceivable
* manner
* @return Returns a conversion-cacheing formatter for the given pattern,
* but the instance itself is not cached
*/
public static SimpleDateFormat getDateFormat(String pattern, boolean lenient)
{
// create an alfrescoDateFormat for cacheing purposes
SimpleDateFormat dateFormat = new CachingDateFormat(pattern);
// set leniency
dateFormat.setLenient(lenient);
// done
return dateFormat;
}
/**
* @return Returns a thread-safe formatter for the generic date/time format
*
* @see #FORMAT_FULL_GENERIC
*/
public static SimpleDateFormat getDateFormat()
{
if (s_localDateFormat.get() != null)
{
return s_localDateFormat.get();
}
CachingDateFormat formatter = new CachingDateFormat(FORMAT_FULL_GENERIC);
// it must be strict
formatter.setLenient(false);
// put this into the threadlocal object
s_localDateFormat.set(formatter);
// done
return s_localDateFormat.get();
}
/**
* @return Returns a thread-safe formatter for the cmis sql datetime format
*/
public static SimpleDateFormat getCmisSqlDatetimeFormat()
{
if (s_localCmisSqlDatetime.get() != null)
{
return s_localCmisSqlDatetime.get();
}
CachingDateFormat formatter = new CachingDateFormat(FORMAT_CMIS_SQL);
// it must be strict
formatter.setLenient(false);
// put this into the threadlocal object
s_localCmisSqlDatetime.set(formatter);
// done
return s_localCmisSqlDatetime.get();
}
/**
* @return Returns a thread-safe formatter for the cmis sql datetime format
*/
public static SimpleDateFormat getSolrDatetimeFormat()
{
if (s_localSolrDatetime.get() != null)
{
return s_localSolrDatetime.get();
}
CachingDateFormat formatter = new CachingDateFormat(FORMAT_SOLR);
// it must be strict
formatter.setLenient(false);
// put this into the threadlocal object
s_localSolrDatetime.set(formatter);
// done
return s_localSolrDatetime.get();
}
/**
* @return Returns a thread-safe formatter for the generic date format
*
* @see #FORMAT_DATE_GENERIC
*/
public static SimpleDateFormat getDateOnlyFormat()
{
if (s_localDateOnlyFormat.get() != null)
{
return s_localDateOnlyFormat.get();
}
CachingDateFormat formatter = new CachingDateFormat(FORMAT_DATE_GENERIC);
// it must be strict
formatter.setLenient(false);
// put this into the threadlocal object
s_localDateOnlyFormat.set(formatter);
// done
return s_localDateOnlyFormat.get();
}
/**
* @return Returns a thread-safe formatter for the generic time format
*
* @see #FORMAT_TIME_GENERIC
*/
public static SimpleDateFormat getTimeOnlyFormat()
{
if (s_localTimeOnlyFormat.get() != null)
{
return s_localTimeOnlyFormat.get();
}
CachingDateFormat formatter = new CachingDateFormat(FORMAT_TIME_GENERIC);
// it must be strict
formatter.setLenient(false);
// put this into the threadlocal object
s_localTimeOnlyFormat.set(formatter);
// done
return s_localTimeOnlyFormat.get();
}
/**
* Parses and caches date strings.
*
* @see java.text.DateFormat#parse(java.lang.String,
* java.text.ParsePosition)
*/
public Date parse(String text, ParsePosition pos)
{
Date cached = cacheDates.get(text);
if (cached == null)
{
Date date = super.parse(text, pos);
if ((date != null) && (pos.getIndex() == text.length()))
{
cacheDates.put(text, date);
Date clonedDate = (Date) date.clone();
return clonedDate;
}
else
{
return date;
}
}
else
{
pos.setIndex(text.length());
Date clonedDate = (Date) cached.clone();
return clonedDate;
}
}
public static Pair<Date, Integer> lenientParse(String text, int minimumResolution) throws ParseException
{
DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
try
{
Date parsed = fmt.parseDateTime(text).toDate();
return new Pair<Date, Integer>(parsed, Calendar.MILLISECOND);
}
catch(IllegalArgumentException e)
{
}
SimpleDateFormatAndResolution[] formatters = getLenientFormatters();
for(SimpleDateFormatAndResolution formatter : formatters)
{
if(formatter.resolution >= minimumResolution)
{
ParsePosition pp = new ParsePosition(0);
Date parsed = formatter.simpleDateFormat.parse(text, pp);
if ((pp.getIndex() < text.length()) || (parsed == null))
{
continue;
}
return new Pair<Date, Integer>(parsed, formatter.resolution);
}
}
throw new ParseException("Unknown date format", 0);
}
public static SimpleDateFormatAndResolution[] getLenientFormatters()
{
if (s_lenientParsers.get() != null)
{
return s_lenientParsers.get();
}
int i = 0;
SimpleDateFormatAndResolution[] formatters = new SimpleDateFormatAndResolution[LENIENT_FORMATS.length];
for(StringAndResolution format : LENIENT_FORMATS)
{
CachingDateFormat formatter = new CachingDateFormat(format.string);
// it must be strict
formatter.setLenient(false);
formatters[i++] = new SimpleDateFormatAndResolution(formatter, format.resolution);
}
// put this into the threadlocal object
s_lenientParsers.set(formatters);
// done
return s_lenientParsers.get();
}
public static class StringAndResolution
{
String string;
int resolution;
/**
* @return the resolution
*/
public int getResolution()
{
return resolution;
}
/**
* @param resolution the resolution to set
*/
public void setResolution(int resolution)
{
this.resolution = resolution;
}
StringAndResolution(String string, int resolution)
{
this.string = string;
this.resolution = resolution;
}
}
public static class SimpleDateFormatAndResolution
{
SimpleDateFormat simpleDateFormat;
int resolution;
SimpleDateFormatAndResolution(SimpleDateFormat simpleDateFormat, int resolution)
{
this.simpleDateFormat = simpleDateFormat;
this.resolution = resolution;
}
/**
* @return the simpleDateFormat
*/
public SimpleDateFormat getSimpleDateFormat()
{
return simpleDateFormat;
}
/**
* @return the resolution
*/
public int getResolution()
{
return resolution;
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
/**
* Content
*
* @author dcaruana
*/
public interface Content
{
/**
* Gets content as a string
*
* @return content as a string
* @throws IOException
*/
public String getContent() throws IOException;
/**
* Gets the content mimetype
*
* @return mimetype
*/
public String getMimetype();
/**
* Gets the content encoding
*
* @return encoding
*/
public String getEncoding();
/**
* Gets the content length (in bytes)
*
* @return length
*/
public long getSize();
/**
* Gets the content input stream
*
* @return input stream
*/
public InputStream getInputStream();
/**
* Gets the content reader (which is sensitive to encoding)
*
* @return Reader
*/
public Reader getReader() throws IOException;
}

View File

@@ -0,0 +1,809 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.alfresco.encoding.CharactersetFinder;
import org.alfresco.encoding.GuessEncodingCharsetFinder;
import org.alfresco.util.exec.RuntimeExec;
import org.alfresco.util.exec.RuntimeExec.ExecutionResult;
/**
* Utility to convert text files.
* <p/>
* Check the usage options with the <b>--help</b> option.
* <p/>
* Here are some examples of how to use the <code>main</code> method:
* <ul>
* <li>
* <b>--help</b><br/>
* Produce the help output.
* </li>
* <li>
* <b>--dry-run --encoding=UTF-8 --line-ending=WINDOWS --match="(.java|.xml|.jsp|.properties)$" --ignore="(.svn|classes)" "w:\"</b><br/>
* Find all source (.java, .xml, .jsp and .properties) files in directory "w:\".<br/>
* List files and show which would change when converting to CR-LF (Windows) line endings.<br/>
* Where auto-detection of the file is ambiguous, assume UTF-8.
* </li>
* <li>
* <b>--encoding=UTF-8 --line-ending=WINDOWS --match="(.java|.xml|.jsp|.properties)$" --ignore="(.svn|classes)" "w:\"</b><br/>
* Find all source (.java, .xml, .jsp and .properties) files in directory "w:\". Recurse into subdirectories.<br/>
* Convert files, where necessary, to have CR-LF (Windows) line endings.<br/>
* Where auto-detection of the file encoding is ambiguous, assume UTF-8.<br/>
* Backups (.bak) files will be created.
* </li>
* <li>
* <b>--svn-update --no-backup --encoding=UTF-8 --line-ending=WINDOWS --match="(.java|.xml|.jsp|.properties)$" "w:\"</b><br/>
* Issue a 'svn status' command on directory "w:\" and match the regular expressions given to find files.<br/>
* Convert files, where necessary, to have CR-LF (Windows) line endings.<br/>
* Where auto-detection of the file encoding is ambiguous, assume UTF-8. Write out as UTF-8.<br/>
* No backups files will be created.
* </li>
* </ul>
*
* @author Derek Hulley
*/
public class Convert
{
private static final String OPTION_HELP = "--help";
private static final String OPTION_SVN_STATUS = "--svn-status";
private static final String OPTION_MATCH = "--match=";
private static final String OPTION_IGNORE = "--ignore=";
private static final String OPTION_ENCODING= "--encoding=";
private static final String OPTION_LINE_ENDING = "--line-ending=";
private static final String OPTION_REPLACE_TABS= "--replace-tabs=";
private static final String OPTION_NO_RECURSE = "--no-recurse";
private static final String OPTION_NO_BACKUP = "--no-backup";
private static final String OPTION_DRY_RUN = "--dry-run";
private static final String OPTION_VERBOSE = "--verbose";
private static final String OPTION_QUIET = "--quiet";
private static final Set<String> OPTIONS = new HashSet<String>(13);
static
{
OPTIONS.add(OPTION_HELP);
OPTIONS.add(OPTION_SVN_STATUS);
OPTIONS.add(OPTION_MATCH);
OPTIONS.add(OPTION_IGNORE);
OPTIONS.add(OPTION_ENCODING);
OPTIONS.add(OPTION_LINE_ENDING);
OPTIONS.add(OPTION_REPLACE_TABS);
OPTIONS.add(OPTION_NO_RECURSE);
OPTIONS.add(OPTION_NO_BACKUP);
OPTIONS.add(OPTION_DRY_RUN);
OPTIONS.add(OPTION_VERBOSE);
OPTIONS.add(OPTION_QUIET);
}
/**
* @see GuessEncodingCharsetFinder
*/
private static final CharactersetFinder CHARACTER_ENCODING_FINDER = new GuessEncodingCharsetFinder();
private File startDir = null;
private boolean svnStatus = false;
private boolean dryRun = false;
private Pattern matchPattern = null;
private Pattern ignorePattern = null;
private Charset charset = null;
private String lineEnding = null;
private Integer replaceTabs = null;
private boolean noRecurse = false;
private boolean noBackup = false;
private boolean verbose = false;
private boolean quiet = false;
public static void main(String[] args)
{
if (args.length < 1)
{
printUsage();
}
// Convert args to a list
List<String> argList = new ArrayList<String>(args.length);
List<String> argListFixed = Arrays.asList(args);
argList.addAll(argListFixed);
// Extract all the options
Map<String, String> optionValues = extractOptions(argList);
// Check for help request
if (optionValues.containsKey(OPTION_HELP))
{
printUsage();
System.exit(0);
}
// Check
if (argList.size() != 1)
{
printUsage();
System.exit(1);
}
// Get the directory to start in
File startDir = new File(argList.get(0));
if (!startDir.exists() || !startDir.isDirectory())
{
System.err.println("Convert: ");
System.err.println(" Unable to find directory: " + startDir);
System.err.flush();
printUsage();
System.exit(1);
}
Convert convert = new Convert(optionValues, startDir);
convert.convert();
}
/**
* Private constructor for use by the main method.
*/
private Convert(Map<String, String> optionValues, File startDir)
{
this.startDir = startDir;
svnStatus = optionValues.containsKey(OPTION_SVN_STATUS);
dryRun = optionValues.containsKey(OPTION_DRY_RUN);
String match = optionValues.get(OPTION_MATCH);
String ignore = optionValues.get(OPTION_IGNORE);
String encoding = optionValues.get(OPTION_ENCODING);
lineEnding = optionValues.get(OPTION_LINE_ENDING);
noRecurse = optionValues.containsKey(OPTION_NO_RECURSE);
noBackup = optionValues.containsKey(OPTION_NO_BACKUP);
verbose = optionValues.containsKey(OPTION_VERBOSE);
quiet = optionValues.containsKey(OPTION_QUIET);
// Check that the tab replacement count is correct
String replaceTabsStr = optionValues.get(OPTION_REPLACE_TABS);
if (replaceTabsStr != null)
{
try
{
replaceTabs = Integer.parseInt(replaceTabsStr);
}
catch (NumberFormatException e)
{
System.err.println("Convert: ");
System.err.println(" Unable to determine how many spaces to replace tabs with: " + replaceTabsStr);
System.err.flush();
printUsage();
System.exit(1);
}
}
// Check the match regex expressions
if (match == null)
{
match = ".*";
}
try
{
matchPattern = Pattern.compile(match);
}
catch (Throwable e)
{
System.err.println("Convert: ");
System.err.println(" Unable to parse regular expression: " + match);
System.err.flush();
printUsage();
System.exit(1);
}
// Check the match regex expressions
if (ignore != null)
{
try
{
ignorePattern = Pattern.compile(ignore);
}
catch (Throwable e)
{
System.err.println("Convert: ");
System.err.println(" Unable to parse regular expression: " + ignore);
System.err.flush();
printUsage();
System.exit(1);
}
}
// Check the encoding
if (encoding != null)
{
try
{
charset = Charset.forName(encoding);
}
catch (Throwable e)
{
System.err.println("Convert: ");
System.err.println(" Unknown encoding: " + encoding);
System.err.flush();
printUsage();
System.exit(1);
}
}
// Check line ending
if (lineEnding != null && !lineEnding.equals("WINDOWS") && !lineEnding.equals("UNIX"))
{
System.err.println("Convert: ");
System.err.println(" Line endings can be either WINDOWS or UNIX: " + lineEnding);
System.err.flush();
printUsage();
System.exit(1);
}
// Check quiet/verbose match
if (verbose && quiet)
{
System.err.println("Convert: ");
System.err.println(" Cannot output in verbose and quiet mode.");
System.err.flush();
printUsage();
System.exit(1);
}
}
private void convert()
{
try
{
if (!quiet)
{
System.out.print("Converting files matching " + matchPattern);
System.out.print(ignorePattern == null ? "" : " but not " + ignorePattern);
System.out.println(dryRun ? " [DRY RUN]" : "");
}
if (!svnStatus)
{
// Do a recursive pattern match
convertDir(startDir);
}
else
{
// Use SVN
convertSvn(startDir);
}
}
catch (Throwable e)
{
e.printStackTrace();
System.err.flush();
printUsage();
System.exit(1);
}
finally
{
System.out.flush();
}
}
private void convertSvn(File currentDir) throws Throwable
{
RuntimeExec exec = new RuntimeExec();
exec.setCommand(new String[] {"svn", "status", currentDir.toString()});
ExecutionResult result = exec.execute();
if (!result.getSuccess())
{
System.out.println("svn status command failed:" + exec);
}
// Get the output
String dump = result.getStdOut();
BufferedReader reader = null;
try
{
reader = new BufferedReader(new StringReader(dump));
while (true)
{
String line = reader.readLine();
if (line == null)
{
break;
}
// Only lines that start with "A" or "M"
if (!line.startsWith("A") && !line.startsWith("M"))
{
continue;
}
String filename = line.substring(7).trim();
if (filename.length() < 1)
{
continue;
}
File file = new File(filename);
if (!file.exists())
{
continue;
}
// We found one
convertFile(file);
}
}
finally
{
if (reader != null)
{
try { reader.close(); } catch (Throwable e) {}
}
}
}
/**
* Recursive method to do the conversion work.
*/
private void convertDir(File currentDir) throws Throwable
{
// Get all children of the folder
File[] childFiles = currentDir.listFiles();
for (File childFile : childFiles)
{
if (childFile.isDirectory())
{
if (noRecurse)
{
// Don't enter the directory
continue;
}
// Recurse
convertDir(childFile);
}
else
{
convertFile(childFile);
}
}
}
private void convertFile(File file) throws Throwable
{
// We have a file, but does the pattern match
String filePath = file.getAbsolutePath();
if (matchPattern.matcher(filePath).find())
{
// It matches, but must we ignore it?
if (ignorePattern != null && ignorePattern.matcher(filePath).find())
{
// It is ignorable
return;
}
}
else
{
// It missed the primary positive match
return;
}
// Ignore folders
if (file.isDirectory())
{
return;
}
if (file.length() > (1024 * 1024)) // 1MB. TODO: Make an option
{
System.out.println(" (Too big)");
}
File backupFile = null;
try
{
// Read the source file into memory
byte[] fileBytes = readFileIntoMemory(file);
// Calculate the MD5 for the file
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(fileBytes);
byte[] fileMd5 = md5.digest();
// Guess the charset now
Charset fileCharset = guessCharset(fileBytes, charset);
byte[] convertedBytes = fileBytes;
byte[] sourceBytes = fileBytes;
byte[] convertedMd5 = fileMd5;
// Convert the tabs
if (replaceTabs != null)
{
sourceBytes = convertTabs(sourceBytes, fileCharset, replaceTabs);
}
// Convert the charset
if (charset != null)
{
// TODO
// sourceBytes = convert ...
}
// Convert the line endings
if (lineEnding != null)
{
convertedBytes = convertLineEndings(sourceBytes, fileCharset, lineEnding);
}
boolean changed = false;
if (convertedBytes == fileBytes)
{
// Nothing done
}
else
{
// Recalculate the converted MD5
md5 = MessageDigest.getInstance("MD5");
md5.update(convertedBytes);
convertedMd5 = md5.digest();
// Now compare
changed = !Arrays.equals(fileMd5, convertedMd5);
}
// Make a backup of the file if it changed
if (changed)
{
if (!noBackup && !dryRun)
{
String backupFilename = file.getAbsolutePath() + ".bak";
File backupFilePre = new File(backupFilename);
// Write the original file contents to the backup file
writeMemoryIntoFile(fileBytes, backupFilePre);
// That being successful, we can now reference it
backupFile = backupFilePre;
}
if (!quiet)
{
System.out.println(" " + file + " <Modified>");
}
// Only write to the file if this is not a dry run
if (!dryRun)
{
// Now write the converted buffer to the original file
writeMemoryIntoFile(convertedBytes, file);
}
}
else
{
if (verbose)
{
System.out.println(" " + file + " <No change>");
}
}
}
catch (Throwable e)
{
if (backupFile != null)
{
try
{
file.delete();
backupFile.renameTo(file);
}
catch (Throwable ee)
{
System.err.println("Failed to restore backup file: " + backupFile);
ee.printStackTrace();
}
}
throw e;
}
finally
{
if (!quiet || verbose)
{
System.out.flush();
}
}
}
/**
* Brute force guessing by doing charset conversions.<br/>
*/
private static Charset guessCharset(byte[] bytes, Charset charset) throws Exception
{
Charset guessedCharset = CHARACTER_ENCODING_FINDER.detectCharset(bytes);
if (guessedCharset == null)
{
return charset;
}
else
{
return guessedCharset;
}
}
private static byte[] convertTabs(byte[] bytes, Charset charset, int replaceTabs) throws Exception
{
// The tab character
char tab = '\t';
char space = ' ';
// The output
StringBuilder sb = new StringBuilder(bytes.length);
String charsetName = charset.name();
// Using the charset, convert to a string
String str = new String(bytes, charsetName);
char[] chars = str.toCharArray();
for (char c : chars)
{
if (c == tab)
{
// Replace the tab
for (int i = 0; i < replaceTabs; i++)
{
sb.append(space);
}
}
else
{
sb.append(c);
}
}
// Done
return sb.toString().getBytes(charsetName);
}
private static final String EOF_CHECK = "--EOF-CHECK--";
private static byte[] convertLineEndings(byte[] bytes, Charset charset, String lineEnding) throws Exception
{
String charsetName = charset.name();
// Using the charset, convert to a string
BufferedReader reader = null;
StringBuilder sb = new StringBuilder(bytes.length);
try
{
String str = new String(bytes, charsetName);
str = str + EOF_CHECK;
reader = new BufferedReader(new StringReader(str));
String line = reader.readLine();
while (line != null)
{
// Ignore the newline check
boolean addLine = true;
if (line.equals(EOF_CHECK))
{
break;
}
else if (line.endsWith(EOF_CHECK))
{
int index = line.indexOf(EOF_CHECK);
line = line.substring(0, index);
addLine = false;
}
// Write the line back out
sb.append(line);
if (!addLine)
{
// No newline
}
else if (lineEnding.equalsIgnoreCase("UNIX"))
{
sb.append("\n");
}
else
{
sb.append("\r\n");
}
line = reader.readLine();
}
}
finally
{
if (reader != null)
{
try { reader.close(); } catch (Throwable e) {}
}
}
// Done
return sb.toString().getBytes(charsetName);
}
private static byte[] readFileIntoMemory(File file) throws Exception
{
InputStream is = null;
OutputStream os = null;
try
{
is = new BufferedInputStream(new FileInputStream(file));
ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
os = new BufferedOutputStream(baos);
byte[] buffer = new byte[1024];
while (true)
{
int count = is.read(buffer);
if (count < 0)
{
break;
}
os.write(buffer, 0, count);
}
os.flush();
byte[] memory = baos.toByteArray();
return memory;
}
finally
{
if (is != null)
{
try { is.close(); } catch (Throwable e) {}
}
if (os != null)
{
try { os.close(); } catch (Throwable e) {}
}
}
}
private static void writeMemoryIntoFile(byte[] bytes, File file) throws Exception
{
InputStream is = null;
OutputStream os = null;
try
{
is = new ByteArrayInputStream(bytes);
os = new BufferedOutputStream(new FileOutputStream(file));
byte[] buffer = new byte[1024];
while (true)
{
int count = is.read(buffer);
if (count < 0)
{
break;
}
os.write(buffer, 0, count);
}
os.flush();
}
finally
{
if (is != null)
{
try { is.close(); } catch (Throwable e) {}
}
if (os != null)
{
try { os.close(); } catch (Throwable e) {}
}
}
}
/**
* Extract all the options from the list of arguments.
* @param args the program arguments. This list will be modified.
* @return Returns a map of arguments and their values. Where the arguments have
* no values, an empty string is returned.
*/
private static Map<String, String> extractOptions(List<String> args)
{
Map<String, String> optionValues = new HashMap<String, String>(13);
// Iterate until we find a non-option
Iterator<String> iterator = args.iterator();
while (iterator.hasNext())
{
String arg = iterator.next();
boolean foundOption = false;
for (String option : OPTIONS)
{
if (!arg.startsWith(option))
{
// It is a non-option
continue;
}
foundOption = true;
// We can remove the argument
iterator.remove();
// Check if the option needs a value
if (option.endsWith("="))
{
// Extract the option value
int index = arg.indexOf("=");
if (index == arg.length() - 1)
{
// There is nothing there, so we don't keep a value
}
else
{
String value = arg.substring(index + 1);
optionValues.put(option, value);
}
}
else
{
// Add the value to the map
String value = "";
optionValues.put(option, value);
}
}
if (!foundOption)
{
// It is not an option
break;
}
}
// Done
return optionValues;
}
public static void printUsage()
{
StringBuilder sb = new StringBuilder(1024);
sb.append("Usage: \n")
.append(" Convert [options] directory \n")
.append(" \n")
.append(" options: \n")
.append(" --help \n")
.append(" Print this help. \n")
.append(" --svn-status \n")
.append(" Execute a 'svn status' command against the directory and use the output for the file list. \n")
.append(" --match=?: \n")
.append(" A regular expression that all filenames must match. \n")
.append(" This argument can be escaped with double quotes, ie.g \"[a-zA-z0-9 ]\". \n")
.append(" The regular expression will be applied to the full path of the file. \n")
.append(" Name seperators will be '/' on Unix and ''\\'' on Windows systems. \n")
.append(" The default is \"--match=.*\", or match all files. \n")
.append(" --ignore=?: \n")
.append(" A regular expression that all filenames must not match. \n")
.append(" This argument can be escaped with double quotes, ie.g \"[a-zA-z0-9 ]\". \n")
.append(" The regular expression will be applied to the full path of the file. \n")
.append(" Name seperators will be '/' on Unix and ''\\'' on Windows systems. \n")
.append(" This option is not present by default. \n")
.append(" --encoding=? \n")
.append(" If not specified, the encoding of the files is left unchanged. \n")
.append(" Typical values would be UTF-8, UTF-16 or any java-recognized encoding string. \n")
.append(" --line-ending=? \n")
.append(" This can either be WINDOWS or UNIX. \n")
.append(" If not set, the line ending style is left unchanged. \n")
.append(" --replace-tabs=? \n")
.append(" Specify the number of spaces to insert in place of a tab. \n")
.append(" --no-recurse \n")
.append(" Do not recurse into subdirectories. \n")
.append(" --no-backup \n")
.append(" The default is to make a backup of all files prior to modification. \n")
.append(" With this option, no backups are made. \n")
.append(" --dry-run \n")
.append(" Do not modify or backup any files. \n")
.append(" No filesystem modifications are made. \n")
.append(" --verbose \n")
.append(" Dump all files checked to std.out. \n")
.append(" --quiet \n")
.append(" Don't dump anything to std.out. \n")
.append(" directory: \n")
.append(" The directory to start searching in. \n")
.append(" If the directory has spaces in it, then escape it with double quotes, e.g. \"C:\\Program Files\" \n")
.append(" \n")
.append("Details of the modifications being made are written to std.out. \n")
.append("Errors are written to std.err. \n")
.append("When used without any options, this program will behave like a FIND. \n");
System.out.println(sb);
System.out.flush();
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.util.Date;
import org.alfresco.api.AlfrescoPublicApi;
import org.alfresco.error.AlfrescoRuntimeException;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.Trigger;
/**
* A utility bean to wrap scheduling a cron job with a given scheduler.
*
* @author Andy Hind
*/
@AlfrescoPublicApi
public class CronTriggerBean extends AbstractTriggerBean
{
private static final long MILLISECONDS_PER_MINUTE = 60L * 1000L;
/*
* Milliseconds delay before the job will be triggered.
*/
private long startDelay = 0;
/*
* The cron expression to trigger execution.
*/
String cronExpression;
/**
* Default constructor
*
*/
public CronTriggerBean()
{
super();
}
/**
* Get the cron expression that determines when this job is run.
*
* @return The cron expression
*/
public String getCronExpression()
{
return cronExpression;
}
/**
* Set the cron expression that determines when this job is run.
*
* @param cronExpression
*/
public void setCronExpression(String cronExpression)
{
this.cronExpression = cronExpression;
}
/**
* Build the cron trigger
*
* @return The trigger
* @throws Exception
*/
public Trigger getTrigger() throws Exception
{
Trigger trigger = new CronTrigger(getBeanName(), Scheduler.DEFAULT_GROUP, getCronExpression());
if (this.startDelay > 0)
{
trigger.setStartTime(new Date(System.currentTimeMillis() + this.startDelay));
}
JobDetail jd = super.getJobDetail();
if (jd != null)
{
String jobName = super.getJobDetail().getKey().getName();
if (jobName != null && !jobName.isEmpty())
{
trigger.setJobName(jobName);
}
String jobGroup = super.getJobDetail().getKey().getGroup();
if (jobGroup != null && !jobGroup.isEmpty())
{
trigger.setJobGroup(jobGroup);
}
}
return trigger;
}
public long getStartDelay()
{
return startDelay;
}
public void setStartDelay(long startDelay)
{
this.startDelay = startDelay;
}
public void setStartDelayMinutes(long startDelayMinutes)
{
this.startDelay = startDelayMinutes * MILLISECONDS_PER_MINUTE;
}
public void afterPropertiesSet() throws Exception
{
if ((cronExpression == null) || (cronExpression.trim().length() == 0))
{
throw new AlfrescoRuntimeException(
"The cron expression has not been set, is zero length, or is all white space");
}
super.afterPropertiesSet();
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.net.URL;
/**
* Class containing debugging utility methods
*
* @author gavinc
*/
public class Debug
{
/**
* Returns the location of the file that will be loaded for the given class name
*
* @param className The class to load
* @return The location of the file that will be loaded
* @throws ClassNotFoundException
*/
public static String whichClass(String className) throws ClassNotFoundException
{
String path = className;
// prepare the resource path
if (path.startsWith("/") == false)
{
path = "/" + path;
}
path = path.replace('.', '/');
path = path + ".class";
// get the location
URL url = Debug.class.getResource(path);
if (url == null)
{
throw new ClassNotFoundException(className);
}
// format the result
String location = url.toExternalForm();
if (location.startsWith("jar"))
{
location = location.substring(10, location.lastIndexOf("!"));
}
else if (location.startsWith("file:"))
{
location = location.substring(6);
}
return location;
}
/**
* Returns the class loader that will load the given class name
*
* @param className The class to load
* @return The class loader the class will be loaded in
* @throws ClassNotFoundException
*/
public static String whichClassLoader(String className) throws ClassNotFoundException
{
String result = "Could not determine class loader for " + className;
Class clazz = Class.forName(className);
ClassLoader loader = clazz.getClassLoader();
if (loader != null)
{
result = clazz.getClassLoader().toString();
}
return result;
}
/**
* Returns the class loader hierarchy that will load the given class name
*
* @param className The class to load
* @return The hierarchy of class loaders used to load the class
* @throws ClassNotFoundException
*/
public static String whichClassLoaderHierarchy(String className) throws ClassNotFoundException
{
StringBuffer buffer = new StringBuffer();
Class clazz = Class.forName(className);
ClassLoader loader = clazz.getClassLoader();
if (loader != null)
{
buffer.append(loader.toString());
ClassLoader parent = loader.getParent();
while (parent != null)
{
buffer.append("\n-> ").append(parent.toString());
parent = parent.getParent();
}
}
else
{
buffer.append("Could not determine class loader for " + className);
}
return buffer.toString();
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.io.File;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility to delete a file or directory recursively.
* @author britt
*/
public class Deleter
{
private static final Log log = LogFactory.getLog(Deleter.class);
/**
* Delete by path.
* @param path
*/
public static void Delete(String path)
{
File toDelete = new File(path);
Delete(toDelete);
}
/**
* Delete by File.
* @param toDelete
*/
public static void Delete(File toDelete)
{
if (toDelete.isDirectory())
{
File[] listing = toDelete.listFiles();
for (File file : listing)
{
Delete(file);
}
}
toDelete.delete();
}
/**
* Recursively deletes the parents of the specified file stopping when <code>rootDir</code> is reached.
* The file itself must have been deleted before calling this method - since only empty
* directories can be deleted.
* <p>
* For example: <code>deleteEmptyParents(new File("/tmp/a/b/c/d.txt"), "/tmp/a")</code>
* <p>
* Will delete directories c and b assuming that they are both empty. It will leave /tmp/a even if it is
* empty as this is the <code>rootDir</code>
*
* @param file The path of the file whose parent directories should be deleted.
* @param rootDir Top level directory where deletion should stop. <strong>Must be the canonical path
* to ensure correct comparisons.</strong>
*/
public static void deleteEmptyParents(File file, String rootDir)
{
File parent = file.getParentFile();
boolean deleted = false;
do
{
try
{
if (parent.isDirectory() && !parent.getCanonicalPath().equals(rootDir))
{
// Only an empty directory will successfully be deleted.
deleted = parent.delete();
}
}
catch (IOException error)
{
log.error("Unable to construct canonical path for " + parent.getAbsolutePath());
break;
}
parent = parent.getParentFile();
}
while(deleted);
}
/**
* Same behaviour as for {@link Deleter#deleteEmptyParents(File, String)} but with the
* <code>rootDir</code> parameter specified as a {@link java.io.File} object.
*
* @see Deleter#deleteEmptyParents(File, String)
* @param file
* @param rootDir
*/
public static void deleteEmptyParents(File file, File rootDir)
{
try
{
deleteEmptyParents(file, rootDir.getCanonicalPath());
}
catch (IOException e)
{
String msg = "Unable to convert rootDir to canonical form [rootDir=" + rootDir + "]";
throw new RuntimeException(msg, e);
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.util;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This is an instance of {@link java.util.concurrent.ThreadPoolExecutor} which
* behaves how one would expect it to, even when faced with an unlimited
* queue. Unlike the default {@link java.util.concurrent.ThreadPoolExecutor}, it
* will add new Threads up to {@link #setMaximumPoolSize(int) maximumPoolSize}
* when there is lots of pending work, rather than only when the queue is full
* (which it often never will be, especially for unlimited queues)
*
* @author Nick Burch
*/
public class DynamicallySizedThreadPoolExecutor extends ThreadPoolExecutor
{
private static Log logger = LogFactory.getLog(DynamicallySizedThreadPoolExecutor.class);
private final ReentrantLock lock = new ReentrantLock();
private int realCorePoolSize;
public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
{
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
this.realCorePoolSize = corePoolSize;
}
public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
{
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
this.realCorePoolSize = corePoolSize;
}
public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
{
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
this.realCorePoolSize = corePoolSize;
}
public DynamicallySizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue)
{
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.realCorePoolSize = corePoolSize;
}
@Override
public void setCorePoolSize(int corePoolSize)
{
this.realCorePoolSize = corePoolSize;
super.setCorePoolSize(corePoolSize);
}
@Override
public void execute(Runnable command)
{
// Do we want to add another thread?
int threadCount = getPoolSize();
if(logger.isDebugEnabled())
{
logger.debug("Current pool size is " + threadCount + ", real core=" + realCorePoolSize +
", current core=" + getCorePoolSize() + ", max=" + getMaximumPoolSize());
}
if(threadCount < getMaximumPoolSize())
{
// We're not yet at the full thread count
// Does the queue size warrant adding one?
// (If there are more than the maximum pool size of jobs pending,
// it's time to add another thread)
int queueSize = getQueue().size() + 1;// New job not yet added
if(queueSize >= getMaximumPoolSize())
{
lock.lock();
int currentCoreSize = getCorePoolSize();
if(currentCoreSize < getMaximumPoolSize())
{
super.setCorePoolSize(currentCoreSize+1);
if(logger.isInfoEnabled())
{
logger.info("Increased pool size to " + getCorePoolSize() + " from " +
currentCoreSize + " due to queue size of " + queueSize);
}
}
lock.unlock();
}
}
// Now run the actual work
super.execute(command);
}
@Override
protected void afterExecute(Runnable r, Throwable t)
{
// If the queue is looking empty, allow the pool to
// get rid of idle threads when it wants to
int threadCount = getPoolSize();
if(threadCount == getMaximumPoolSize() && threadCount > realCorePoolSize)
{
int queueSize = getQueue().size();
int currentCoreSize = getCorePoolSize();
if(queueSize < 2 && currentCoreSize > realCorePoolSize)
{
// Almost out of work, allow the pool to reduce threads when
// required. Double checks the sizing inside a lock to avoid
// race conditions taking us below the real core size.
lock.lock();
currentCoreSize = getCorePoolSize();
if(currentCoreSize > realCorePoolSize)
{
super.setCorePoolSize(currentCoreSize-1);
if(logger.isInfoEnabled())
{
logger.info("Decreased pool size to " + getCorePoolSize() + " from " +
currentCoreSize + " (real core size is " + realCorePoolSize +
") due to queue size of " + queueSize);
}
}
lock.unlock();
}
}
}
}

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