/**
* JHOVE2 - Next-generation architecture for format-aware characterization
*
* Copyright (c) 2009 by The Regents of the University of California, Ithaka
* Harbors, Inc., and The Board of Trustees of the Leland Stanford Junior
* University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of the University of California/California Digital
* Library, Ithaka Harbors/Portico, or Stanford University, nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jhove2.config.spring;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeSet;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.Assert;
/**
* Class to find all JHOVE2 messages.properties files on the classpath
* Path to these files should conform to the pattern
* "classpath/0 or more directories/messages/0 or more directories/someFileName_BASENAME.properties"
* In the Spring config file, the basenames property for a bean of this class conventionally would
* be set to "messages"
*
* This enables creators of new modules, by following the naming conventions for message properties files,
* and by putting such files on the classpath, to expose these files to discovery without
* any changes required to the Spring configuration files
* @author smorrissey
*/
public class ResourceBundleMessageSource
extends org.springframework.context.support.ResourceBundleMessageSource
{
public static final String CLASSPATH_DIRS_PATH = "classpath*:";
public static final String MESSAGE_FILE_BASE_MASK = "classpath*:/messages/**/*_";
public static final String EXTENSION = ".properties";
/**
* Constructs array of basenames for properties files, consisting of dotted path
* notation to the files. For example, messages/org/jhove2/core/JHOVE2_messages.properties
* would be converted to the basename messages.org.jhove2.core.JHOVE2_messages
*
* Method finds all files on classpath that match the MESSAGE_FILE_BASE_MASK + each basename
* + EXTENSION; then strips off classpath portion of the file's path, and the EXTENSION,
* replacing all file separator characters with "."
*
* @param String[] of basenames to be converted
*/
@Override
public void setBasenames(String[] basenames) {
ArrayList<String> trimmedBasenames = null;
String [] filteredBasenames = null;
if (basenames != null){
trimmedBasenames = new ArrayList<String>();
for (int i = 0; i < basenames.length; i++) {
String basename = basenames[i];
Assert.hasText(basename, "Basename must not be empty");
trimmedBasenames.add(basename.trim());
}
TreeSet<URI> classpathDirs = this.findClassPathDirectories();
TreeSet<URI> messageFiles = this.findMessageFiles(trimmedBasenames);
ArrayList<String> dottedPaths =
this.fileURIs2Classpath(classpathDirs, messageFiles);
int mfSize = messageFiles.size();
int dfSize = dottedPaths.size();
boolean isEqualSize = (mfSize==dfSize);
Assert.isTrue(isEqualSize,
"Failed to find a directory prefix for one or more message files");
filteredBasenames = dottedPaths.toArray(new String[0]);
}
else {
filteredBasenames = new String[0];
}
super.setBasenames(filteredBasenames);
}
/**
* Strips classpath prefix in any file;s path to get relative path to file; strips
* extension, and replaces "/" with "."
* @param messageDirs TreeSet of directories on classpath that match pattern
* @param messageFiles TreeSet of properites files on classpath that match pattern
* @return ArrayList<String> of dotted-notation relative paths to message.properties files
*/
public ArrayList<String> fileURIs2Classpath(
TreeSet<URI> messageDirs, TreeSet<URI> messageFiles){
ArrayList<String> dottedPaths = new ArrayList<String>();
Iterator<URI> fIter = messageFiles.descendingIterator();
while (fIter.hasNext()){
URI fileURI = fIter.next();
String fileStr = fileURI.toString();
Iterator<URI> dIter = messageDirs.descendingIterator();
boolean foundPrefix = false;
String dirPrefix = null;
while (dIter.hasNext()){
URI dirURI = dIter.next();
String dirStr = dirURI.toString();
if (fileStr.startsWith(dirStr)){
foundPrefix = true;
dirPrefix = dirStr;
break;
}
}
Assert.isTrue(foundPrefix,
"Failed to find directory prefix for " + fileStr);
String relFilePath = fileStr.substring(dirPrefix.length());
relFilePath = relFilePath.replace("/", ".");
int i= relFilePath.lastIndexOf(EXTENSION);
if (i>0){
relFilePath = relFilePath.substring(0, i);
}
dottedPaths.add(relFilePath);
}
return dottedPaths;
}
/**
* Finds any basename.properties files on the masked path
* @param baseNames ArrayList<String> of basenames to be discoverd
* @return TreeSet<URI> of all matching properites files
*/
public TreeSet<URI> findMessageFiles (ArrayList<String> baseNames){
TreeSet<URI> files = new TreeSet<URI>();
PathMatchingResourcePatternResolver resolver =
new PathMatchingResourcePatternResolver();
for (String baseName:baseNames){
String resourceMask = MESSAGE_FILE_BASE_MASK + baseName + EXTENSION;
try {
Resource[] resources = resolver.getResources(resourceMask);
for (Resource resource:resources){
URI uri = resource.getURI();
files.add(uri);
}
} catch (IOException e) {
Assert.isTrue(false, "IO exception when attempting to resolve resources for " +
resourceMask);
}
}
return files;
}
/**
* Gets list of all directories on the classpath
* @return list of all directories on the classpath
*/
public TreeSet<URI> findClassPathDirectories(){
TreeSet<URI> messageDirs = new TreeSet<URI>();
PathMatchingResourcePatternResolver resolver =
new PathMatchingResourcePatternResolver();
try {
Resource[] resources = resolver.getResources(CLASSPATH_DIRS_PATH);
for (Resource resource: resources){
URI uri = resource.getURI();
File file = null;
try {
file = resource.getFile();
if (file.isDirectory()){
messageDirs.add(uri);
}
}catch (IOException e) {
// not a file, or not an accessible one}
}
}
} catch (IOException e) {
Assert.isTrue(false, "IO exception when attempting to resolve resources for " +
CLASSPATH_DIRS_PATH);
}
return messageDirs;
}
}