Index: docs/reference/src/main/docbook/en-US/content/connectors/file_system.xml =================================================================== --- docs/reference/src/main/docbook/en-US/content/connectors/file_system.xml (revision 2307) +++ docs/reference/src/main/docbook/en-US/content/connectors/file_system.xml (working copy) @@ -36,20 +36,34 @@ workspace or the name of subdirectory within a root directory (see the workspaceRootPath property below). Each connector can define whether it allows new workspaces to be created. If the directory for a workspace does not exist, this connector will attempt to create the directory (and any missing parent directories). - - - The file nodes returned by this connector will have a primary type of nt:file and a child node named jcr:content. - The jcr:content node will have a primary type of mode:resource. The mode:resource node type is equivalent - to the built-in nt:resource node type in all ways except one: it does not extend mix:referenceable. This is because - ModeShape cannot assign a persistent UUID to the files in the file system or guarantee that no other process will move or delete the files outside of ModeShape. - The mix:referenceable node type cannot be implemented if either of these conditions cannot be met. - - Additional properties (including mixin types) can be added by setting the customPropertiesFactory property to point to an implementation of - the &CustomPropertiesFactory; interface. - - + By default, this connector is not capable of storing extra properties other than those defined on the nt:file, nt:folder + and nt:resource node types. This is because such properties cannot be represented natively on the file system. + When the connector is asked to store such properties, the default behavior is to log warnings and then to ignore these extra properties. + Obviously this is probably not sufficient for production (unless only the standard properties are to be used). To explicitly turn on this + behavior, set the "extraPropertiesBehavior" to "log". + + + However, the connector can be configured differently. If the "extraPropertiesBehavior" is set to "ignore", then these extra properties will + simply be silently ignored and lost: none will be stored, none will be loaded, and no warnings will be logged. If the "extraPropertiesBehavior" + is set to "error", the connector will throw an exception if any extra properties are used. + + + Perhaps the best setting for general use, however, is to set the "extraPropertiesBehavior" to "store". In this mode, any extra properties + are written to files on the file system that are adjacent to the actual file or folder. For example, given a "nt:folder" node that represents + the "folder1" directory, all extra properties will be stored in a text file named "folder1.modeshape" in the same parent + directory as the "folder1" directory. Similarly, given a "nt:file" node that represents the "file1" file on the file system, all + extra properties will be stored in a text file named "file1.modeshape" located next to the "file1" file. Note that the "nt:resource" node for + our "nt:file" node also is stored in the same location, so we can't use the "file1.modeshape" file (it's already used for the "nt:file" node), + so the connector uses the "file1.content.modeshape" file instead. + + + + The "store" behavior may result in the creation of many "*.modeshape" files, and because of this the "store" behavior is not the default. + + + The &FileSystemSource; class provides a number of JavaBean properties that control its behavior: @@ -81,6 +95,17 @@ available on each node. This property can be set either from an object that implements the &CustomPropertiesFactory; interface or from the name of a class with a public, no-argument constructor that implements the &CustomPropertiesFactory; interface. In the latter case, a the named class will be instantiated and used as the custom properties factory implementation. + See also the "extraPropertiesBehavior" setting. + + + + extraPropertiesBehavior + + Optional setting that specifies how to handle the extra properties on "nt:file", "nt:folder", and "nt:resource" nodes that + cannot be represented on the native files themselves. Set this to "log" if warnings are to be sent to the log + (the default), or "error" if setting such properties should cause an error, or "store" if they should be stored in ancillary + files next to the files and folders, or "ignore" if they should be silently ignored. The "log" value will be used by default + or an invalid value is specified. This setting will be ignored if a "customPropertiesFactory" class name is specified. Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/BasePropertiesFactory.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/BasePropertiesFactory.java (working copy) @@ -0,0 +1,89 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import java.io.FilenameFilter; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.modeshape.graph.JcrLexicon; +import org.modeshape.graph.ModeShapeIntLexicon; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; + +/** + * A base class for {@link CustomPropertiesFactory} implementations that handle "extra" or "custom" properties for 'nt:file', + * 'nt:folder', or 'nt:resource' nodes. + */ +public abstract class BasePropertiesFactory implements CustomPropertiesFactory { + private static final long serialVersionUID = 1L; + + /** + * Only certain properties are tolerated when writing content (dna:resource or jcr:resource) nodes. These properties are + * implicitly stored (primary type, data) or silently ignored (encoded, mimetype, last modified). The silently ignored + * properties must be accepted to stay compatible with the JCR specification. + */ + protected final Set STANDARD_PROPERTIES_FOR_CONTENT = Collections.unmodifiableSet(new HashSet( + Arrays.asList(new Name[] { + JcrLexicon.PRIMARY_TYPE, + JcrLexicon.DATA, + JcrLexicon.ENCODING, + JcrLexicon.MIMETYPE, + JcrLexicon.LAST_MODIFIED, + JcrLexicon.LAST_MODIFIED_BY, + JcrLexicon.UUID, + ModeShapeIntLexicon.NODE_DEFINITON}))); + /** + * Only certain properties are tolerated when writing files (nt:file) or folders (nt:folder) nodes. These properties are + * implicitly stored in the file or folder (primary type, created). + */ + protected final Set STANDARD_PROPERTIES_FOR_FILE_OR_FOLDER = Collections.unmodifiableSet(new HashSet( + Arrays.asList(new Name[] { + JcrLexicon.PRIMARY_TYPE, + JcrLexicon.CREATED, + JcrLexicon.CREATED_BY, + JcrLexicon.UUID, + ModeShapeIntLexicon.NODE_DEFINITON}))); + + protected static final Collection NO_PROPERTIES_COLLECTION = Collections.emptyList(); + protected static final Set NO_NAMES = Collections.emptySet(); + + /** + * Create an instance of this factory. + */ + protected BasePropertiesFactory() { + } + + /** + * Create a filename filter that will ignore any files needed by this implementation. + * + * @param exclusionFilter the default filter, which should be included; may be null if there is no such filter + * @return the filter + */ + public FilenameFilter getFilenameFilter( FilenameFilter exclusionFilter ) { + return exclusionFilter; + } +} Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemI18n.java =================================================================== --- extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemI18n.java (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemI18n.java (working copy) @@ -73,6 +73,12 @@ public final class FileSystemI18n { public static I18n determineMimeTypeUsingContentPropertyDescription; public static I18n determineMimeTypeUsingContentPropertyLabel; public static I18n determineMimeTypeUsingContentPropertyCategory; + public static I18n extraPropertiesPropertyDescription; + public static I18n extraPropertiesPropertyLabel; + public static I18n extraPropertiesPropertyCategory; + public static I18n customPropertiesFactoryPropertyDescription; + public static I18n customPropertiesFactoryPropertyLabel; + public static I18n customPropertiesFactoryPropertyCategory; // Writable messages public static I18n parentIsReadOnly; @@ -89,6 +95,8 @@ public final class FileSystemI18n { public static I18n deleteFailed; public static I18n getCanonicalPathFailed; public static I18n maxPathLengthExceeded; + public static I18n couldNotStoreProperty; + public static I18n couldNotStoreProperties; static { try { Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemSource.java =================================================================== --- extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemSource.java (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemSource.java (working copy) @@ -26,15 +26,11 @@ package org.modeshape.connector.filesystem; import java.io.File; import java.io.FilenameFilter; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.HashSet; +import java.util.HashMap; import java.util.Hashtable; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.regex.Pattern; import javax.naming.Context; import javax.naming.Reference; @@ -46,12 +42,10 @@ import org.modeshape.common.annotation.Description; import org.modeshape.common.annotation.Label; import org.modeshape.common.i18n.I18n; import org.modeshape.common.util.CheckArg; +import org.modeshape.common.util.Logger; import org.modeshape.common.util.StringUtil; import org.modeshape.connector.filesystem.FileSystemRepository.FileSystemTransaction; import org.modeshape.graph.ExecutionContext; -import org.modeshape.graph.JcrLexicon; -import org.modeshape.graph.Location; -import org.modeshape.graph.ModeShapeIntLexicon; import org.modeshape.graph.connector.RepositoryConnection; import org.modeshape.graph.connector.RepositorySource; import org.modeshape.graph.connector.RepositorySourceCapabilities; @@ -60,9 +54,6 @@ import org.modeshape.graph.connector.base.AbstractRepositorySource; import org.modeshape.graph.connector.base.Connection; import org.modeshape.graph.connector.base.PathNode; import org.modeshape.graph.property.Binary; -import org.modeshape.graph.property.Name; -import org.modeshape.graph.property.NamespaceRegistry; -import org.modeshape.graph.property.Property; import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; /** @@ -74,12 +65,6 @@ import org.modeshape.graph.request.CreateWorkspaceRequest.CreateConflictBehavior public class FileSystemSource extends AbstractRepositorySource implements ObjectFactory { /** - * An immutable {@link CustomPropertiesFactory} implementation that is used by default when none is provided. Note that this - * implementation does restrict the properties that can be placed on file, folder and resource nodes. - */ - protected static CustomPropertiesFactory DEFAULT_PROPERTIES_FACTORY = new StandardPropertiesFactory(); - - /** * The first serialized version of this source. Version {@value} . */ private static final long serialVersionUID = 1L; @@ -100,6 +85,7 @@ public class FileSystemSource extends AbstractRepositorySource implements Object protected static final String CUSTOM_PROPERTY_FACTORY = "customPropertyFactory"; protected static final String EAGER_FILE_LOADING = "eagerFileLoading"; protected static final String DETERMINE_MIME_TYPE_USING_CONTENT = "determineMimeTypeUsingContent"; + protected static final String EXTRA_PROPERTIES = "extraProperties"; /** * This source supports events. @@ -119,6 +105,11 @@ public class FileSystemSource extends AbstractRepositorySource implements Object public static final boolean DEFAULT_SUPPORTS_UPDATES = false; /** + * The default behavior for dealing with extra properties on 'nt:file', 'nt:folder' and 'nt:resource' nodes is to log them. + */ + public static final String DEFAULT_EXTRA_PROPERTIES = "log"; + + /** * This source supports creating references. */ protected static final boolean SUPPORTS_REFERENCES = false; @@ -137,6 +128,17 @@ public class FileSystemSource extends AbstractRepositorySource implements Object public static final String DEFAULT_EXCLUSION_PATTERN = null; public static final FilenameFilter DEFAULT_FILENAME_FILTER = null; + protected static Map EXTRA_PROPERTIES_CLASSNAME_BY_KEY; + + static { + Map byKey = new HashMap(); + byKey.put(DEFAULT_EXTRA_PROPERTIES, new LogProperties(Logger.getLogger(FileSystemSource.class))); + byKey.put("store", new StoreProperties()); + byKey.put("error", new ThrowProperties()); + byKey.put("ignore", new IgnoreProperties()); + EXTRA_PROPERTIES_CLASSNAME_BY_KEY = Collections.unmodifiableMap(byKey); + } + @Description( i18n = FileSystemI18n.class, value = "defaultWorkspaceNamePropertyDescription" ) @Label( i18n = FileSystemI18n.class, value = "defaultWorkspaceNamePropertyLabel" ) @Category( i18n = FileSystemI18n.class, value = "defaultWorkspaceNamePropertyCategory" ) @@ -172,6 +174,11 @@ public class FileSystemSource extends AbstractRepositorySource implements Object @Category( i18n = FileSystemI18n.class, value = "determineMimeTypeUsingContentPropertyCategory" ) private volatile boolean determineMimeTypeUsingContent = DEFAULT_DETERMINE_MIME_TYPE_USING_CONTENT; + @Description( i18n = FileSystemI18n.class, value = "extraPropertiesPropertyDescription" ) + @Label( i18n = FileSystemI18n.class, value = "extraPropertiesPropertyLabel" ) + @Category( i18n = FileSystemI18n.class, value = "extraPropertiesPropertyCategory" ) + private volatile String extraProperties = DEFAULT_EXTRA_PROPERTIES; + private volatile FilenameFilter filenameFilter = DEFAULT_FILENAME_FILTER; private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities( SUPPORTS_SAME_NAME_SIBLINGS, @@ -306,9 +313,10 @@ public class FileSystemSource extends AbstractRepositorySource implements Object this.exclusionPattern = null; } - FilenameFilter filenameFilter() { + FilenameFilter filenameFilter( boolean hideFilesForCustomProperties ) { if (this.filenameFilter != null) return this.filenameFilter; + // Otherwise, create one that take into account the exclusion pattern ... FilenameFilter filenameFilter = null; final String filterPattern = exclusionPattern; if (filterPattern != null) { @@ -321,6 +329,14 @@ public class FileSystemSource extends AbstractRepositorySource implements Object } }; } + + if (hideFilesForCustomProperties) { + // And the properties factory ... + CustomPropertiesFactory customPropsFactory = customPropertiesFactory(); + if (customPropsFactory instanceof BasePropertiesFactory) { + filenameFilter = ((BasePropertiesFactory)customPropsFactory).getFilenameFilter(filenameFilter); + } + } return filenameFilter; } @@ -468,6 +484,32 @@ public class FileSystemSource extends AbstractRepositorySource implements Object } /** + * Get the desired behavior for handling extra properties on "nt:foldeR", "nt:file", and "nt:resource" nodes. + * + * @return one of "log", "ignore", "error", or "store" + * @see #getCustomPropertiesFactory() + */ + public String getExtraPropertiesBehavior() { + return extraProperties; + } + + /** + * Set the desired behavior for handling extra properties on "nt:foldeR", "nt:file", and "nt:resource" nodes. + * + * @param behavior "log", "ignore", "error", or "store" + * @see #setCustomPropertiesFactory(CustomPropertiesFactory) + * @see #setCustomPropertiesFactory(String) + */ + public void setExtraPropertiesBehavior( String behavior ) { + if (behavior != null) behavior = behavior.trim().toLowerCase(); + if (EXTRA_PROPERTIES_CLASSNAME_BY_KEY.containsKey(behavior)) { + this.extraProperties = behavior; + } else { + this.extraProperties = DEFAULT_EXTRA_PROPERTIES; + } + } + + /** * Get the factory that is used to create custom properties on "nt:folder", "nt:file", and "nt:resource" nodes. * * @return the factory, or null if no custom properties are to be created @@ -477,13 +519,21 @@ public class FileSystemSource extends AbstractRepositorySource implements Object } CustomPropertiesFactory customPropertiesFactory() { - return customPropertiesFactory != null ? customPropertiesFactory : DEFAULT_PROPERTIES_FACTORY; + if (customPropertiesFactory == null) { + customPropertiesFactory = EXTRA_PROPERTIES_CLASSNAME_BY_KEY.get(extraProperties); + if (customPropertiesFactory == null) { + customPropertiesFactory = EXTRA_PROPERTIES_CLASSNAME_BY_KEY.get(DEFAULT_EXTRA_PROPERTIES); + } + assert customPropertiesFactory != null; + } + return customPropertiesFactory; } /** * Set the factory that is used to create custom properties on "nt:folder", "nt:file", and "nt:resource" nodes. * * @param customPropertiesFactory the factory reference, or null if no custom properties will be created + * @see #setExtraPropertiesBehavior(String) */ public synchronized void setCustomPropertiesFactory( CustomPropertiesFactory customPropertiesFactory ) { this.customPropertiesFactory = customPropertiesFactory; @@ -503,6 +553,7 @@ public class FileSystemSource extends AbstractRepositorySource implements Object * reason. * @throws ClassCastException if the class named by {@code customPropertiesFactoryClassName} does not implement the {@code * CustomPropertiesFactory} interface + * @see #setExtraPropertiesBehavior(String) */ public synchronized void setCustomPropertiesFactory( String customPropertiesFactoryClassName ) throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException { @@ -550,6 +601,7 @@ public class FileSystemSource extends AbstractRepositorySource implements Object ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDefaultWorkspaceName())); ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed()))); ref.add(new StringRefAddr(MAX_PATH_LENGTH, String.valueOf(maxPathLength))); + ref.add(new StringRefAddr(EXTRA_PROPERTIES, String.valueOf(extraProperties))); String[] workspaceNames = getPredefinedWorkspaceNames(); if (workspaceNames != null && workspaceNames.length != 0) { ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES, StringUtil.combineLines(workspaceNames))); @@ -586,6 +638,7 @@ public class FileSystemSource extends AbstractRepositorySource implements Object String filenameFilterClassName = (String)values.get(FILENAME_FILTER); String maxPathLength = (String)values.get(DEFAULT_MAX_PATH_LENGTH); String customPropertiesFactoryClassName = (String)values.get(CUSTOM_PROPERTY_FACTORY); + String extraPropertiesBehavior = (String)values.get(EXTRA_PROPERTIES); String eagerFileLoading = (String)values.get(EAGER_FILE_LOADING); String useContentForMimeType = (String)values.get(DETERMINE_MIME_TYPE_USING_CONTENT); @@ -605,6 +658,7 @@ public class FileSystemSource extends AbstractRepositorySource implements Object if (exclusionPattern != null) source.setExclusionPattern(exclusionPattern); if (filenameFilterClassName != null) source.setFilenameFilter(filenameFilterClassName); if (maxPathLength != null) source.setMaxPathLength(Integer.valueOf(maxPathLength)); + if (extraPropertiesBehavior != null) source.setExtraPropertiesBehavior(extraPropertiesBehavior); if (customPropertiesFactoryClassName != null) source.setCustomPropertiesFactory(customPropertiesFactoryClassName); if (eagerFileLoading != null) source.setEagerFileLoading(Boolean.parseBoolean(eagerFileLoading)); if (useContentForMimeType != null) source.setContentUsedToDetermineMimeType(Boolean.parseBoolean(useContentForMimeType)); @@ -642,130 +696,4 @@ public class FileSystemSource extends AbstractRepositorySource implements Object } return new Connection(this, repository); } - - protected static class StandardPropertiesFactory implements CustomPropertiesFactory { - private static final long serialVersionUID = 1L; - private final Collection empty = Collections.emptyList(); - - /** - * Only certain properties are tolerated when writing content (dna:resource or jcr:resource) nodes. These properties are - * implicitly stored (primary type, data) or silently ignored (encoded, mimetype, last modified). The silently ignored - * properties must be accepted to stay compatible with the JCR specification. - */ - private final Set ALLOWABLE_PROPERTIES_FOR_CONTENT = Collections.unmodifiableSet(new HashSet( - Arrays.asList(new Name[] { - JcrLexicon.PRIMARY_TYPE, - JcrLexicon.DATA, - JcrLexicon.ENCODED, - JcrLexicon.MIMETYPE, - JcrLexicon.LAST_MODIFIED, - JcrLexicon.LAST_MODIFIED_BY, - JcrLexicon.UUID, - ModeShapeIntLexicon.NODE_DEFINITON}))); - /** - * Only certain properties are tolerated when writing files (nt:file) or folders (nt:folder) nodes. These properties are - * implicitly stored in the file or folder (primary type, created). - */ - private final Set ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER = Collections.unmodifiableSet(new HashSet( - Arrays.asList(new Name[] { - JcrLexicon.PRIMARY_TYPE, - JcrLexicon.CREATED, - JcrLexicon.CREATED_BY, - JcrLexicon.UUID, - ModeShapeIntLexicon.NODE_DEFINITON}))); - - public Collection getDirectoryProperties( ExecutionContext context, - Location location, - File directory ) { - return empty; - } - - public Collection getFileProperties( ExecutionContext context, - Location location, - File file ) { - return empty; - } - - public Collection getResourceProperties( ExecutionContext context, - Location location, - File file, - String mimeType ) { - return empty; - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordDirectoryProperties(org.modeshape.graph.ExecutionContext, - * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) - */ - public Set recordDirectoryProperties( ExecutionContext context, - String sourceName, - Location location, - File file, - Map properties ) throws RepositorySourceException { - ensureValidProperties(context, sourceName, properties.values(), ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER); - return null; - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordFileProperties(org.modeshape.graph.ExecutionContext, - * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) - */ - public Set recordFileProperties( ExecutionContext context, - String sourceName, - Location location, - File file, - Map properties ) throws RepositorySourceException { - ensureValidProperties(context, sourceName, properties.values(), ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER); - return null; - } - - /** - * {@inheritDoc} - * - * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordResourceProperties(org.modeshape.graph.ExecutionContext, - * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) - */ - public Set recordResourceProperties( ExecutionContext context, - String sourceName, - Location location, - File file, - Map properties ) throws RepositorySourceException { - ensureValidProperties(context, sourceName, properties.values(), ALLOWABLE_PROPERTIES_FOR_CONTENT); - return null; - } - - /** - * Checks that the collection of {@code properties} only contains properties with allowable names. - * - * @param context - * @param sourceName - * @param properties - * @param validPropertyNames - * @throws RepositorySourceException if {@code properties} contains a - * @see #ALLOWABLE_PROPERTIES_FOR_CONTENT - * @see #ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER - */ - protected void ensureValidProperties( ExecutionContext context, - String sourceName, - Collection properties, - Set validPropertyNames ) { - List invalidNames = new LinkedList(); - NamespaceRegistry registry = context.getNamespaceRegistry(); - - for (Property property : properties) { - if (!validPropertyNames.contains(property.getName())) { - invalidNames.add(property.getName().getString(registry)); - } - } - - if (!invalidNames.isEmpty()) { - throw new RepositorySourceException(sourceName, FileSystemI18n.invalidPropertyNames.text(invalidNames.toString())); - } - } - - } } Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemWorkspace.java =================================================================== --- extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemWorkspace.java (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/FileSystemWorkspace.java (working copy) @@ -49,6 +49,7 @@ import org.modeshape.graph.request.Request; * Workspace implementation for the file system connector. */ class FileSystemWorkspace extends PathWorkspace { + private static final Map NO_PROPERTIES = Collections.emptyMap(); private static final String DEFAULT_MIME_TYPE = "application/octet"; private static final Set VALID_PRIMARY_TYPES = new HashSet(Arrays.asList(new Name[] {JcrNtLexicon.FOLDER, JcrNtLexicon.FILE, JcrNtLexicon.RESOURCE, ModeShapeLexicon.RESOURCE})); @@ -60,6 +61,8 @@ class FileSystemWorkspace extends PathWorkspace { private final boolean eagerLoading; private final boolean contentUsedToDetermineMimeType; private final Logger logger; + private final ValueFactory stringFactory; + private final NameFactory nameFactory; public FileSystemWorkspace( String name, FileSystemWorkspace originalToClone, @@ -73,6 +76,8 @@ class FileSystemWorkspace extends PathWorkspace { this.eagerLoading = this.source.isEagerFileLoading(); this.contentUsedToDetermineMimeType = this.source.isContentUsedToDetermineMimeType(); this.logger = Logger.getLogger(getClass()); + this.stringFactory = context.getValueFactories().getStringFactory(); + this.nameFactory = context.getValueFactories().getNameFactory(); cloneWorkspace(originalToClone); } @@ -87,6 +92,8 @@ class FileSystemWorkspace extends PathWorkspace { this.eagerLoading = this.source.isEagerFileLoading(); this.contentUsedToDetermineMimeType = this.source.isContentUsedToDetermineMimeType(); this.logger = Logger.getLogger(getClass()); + this.stringFactory = context.getValueFactories().getStringFactory(); + this.nameFactory = context.getValueFactories().getNameFactory(); } private void cloneWorkspace( FileSystemWorkspace original ) { @@ -94,7 +101,7 @@ class FileSystemWorkspace extends PathWorkspace { File newRoot = repository.getWorkspaceDirectory(this.getName()); try { - FileUtil.copy(originalRoot, newRoot, source.filenameFilter()); + FileUtil.copy(originalRoot, newRoot, source.filenameFilter(false)); } catch (IOException ioe) { throw new IllegalStateException(ioe); } @@ -105,22 +112,78 @@ class FileSystemWorkspace extends PathWorkspace { PathNode newNode ) { PathFactory pathFactory = context.getValueFactories().getPathFactory(); Path newPath = pathFactory.create(newNode.getParent(), newNode.getName()); - - File originalFile = fileFor(pathFactory.create(node.getParent(), node.getName())); + Path oldPath = pathFactory.create(node.getParent(), node.getName()); + File originalFile = fileFor(oldPath); File newFile = fileFor(newPath, false); if (newFile.exists()) { newFile.delete(); } + // Read the custom properties ... + CustomPropertiesFactory customPropertiesFactory = source.customPropertiesFactory(); + Collection existingProps = null; + Collection existingResourceProps = null; + String sourceName = source.getName(); + Location originalLocation = Location.create(oldPath); + if (originalFile.isDirectory()) { + existingProps = customPropertiesFactory.getDirectoryProperties(context, originalLocation, originalFile); + customPropertiesFactory.recordDirectoryProperties(context, sourceName, originalLocation, originalFile, NO_PROPERTIES); + } else { + Path resourcePath = pathFactory.create(oldPath, JcrLexicon.CONTENT); + Location originalResourceLocation = Location.create(resourcePath); + existingProps = customPropertiesFactory.getFileProperties(context, originalLocation, originalFile); + existingResourceProps = customPropertiesFactory.getResourceProperties(context, + originalResourceLocation, + originalFile, + null); + customPropertiesFactory.recordFileProperties(context, sourceName, originalLocation, originalFile, NO_PROPERTIES); + customPropertiesFactory.recordResourceProperties(context, + sourceName, + originalResourceLocation, + originalFile, + NO_PROPERTIES); + } + originalFile.renameTo(newFile); + // Set the custom properties on the new location ... + Location newLocation = Location.create(newPath); + if (originalFile.isDirectory()) { + customPropertiesFactory.recordDirectoryProperties(context, + sourceName, + newLocation, + newFile, + extraFolder(mapOf(existingProps))); + } else { + Path resourcePath = pathFactory.create(newPath, JcrLexicon.CONTENT); + Location resourceLocation = Location.create(resourcePath); + customPropertiesFactory.recordFileProperties(context, + sourceName, + newLocation, + newFile, + extraFile(mapOf(existingProps))); + customPropertiesFactory.recordResourceProperties(context, + sourceName, + resourceLocation, + newFile, + extraResource(mapOf(existingResourceProps))); + } + return getNode(newPath); } + protected Map mapOf( Collection properties ) { + if (properties == null || properties.isEmpty()) return Collections.emptyMap(); + Map result = new HashMap(); + for (Property property : properties) { + result.put(property.getName(), property); + } + return result; + } + @Override public PathNode putNode( PathNode node ) { - NameFactory nameFactory = context.getValueFactories().getNameFactory(); PathFactory pathFactory = context.getValueFactories().getPathFactory(); NamespaceRegistry registry = context.getNamespaceRegistry(); CustomPropertiesFactory customPropertiesFactory = source.customPropertiesFactory(); @@ -135,7 +198,7 @@ class FileSystemWorkspace extends PathWorkspace { source.getName(), rootLocation, workspaceRoot, - node.getProperties()); + extraFolder(node.getProperties())); return getNode(rootPath); } @@ -183,7 +246,11 @@ class FileSystemWorkspace extends PathWorkspace { ioe.getMessage()), ioe); } - customPropertiesFactory.recordFileProperties(context, source.getName(), Location.create(newPath), newFile, properties); + customPropertiesFactory.recordFileProperties(context, + source.getName(), + Location.create(newPath), + newFile, + extraFile(properties)); } else if (JcrNtLexicon.RESOURCE.equals(primaryType) || ModeShapeLexicon.RESOURCE.equals(primaryType)) { assert parentFile != null; @@ -206,7 +273,7 @@ class FileSystemWorkspace extends PathWorkspace { // Copy over data into a temp file, then move it to the correct location FileOutputStream fos = null; try { - File temp = File.createTempFile("dna", null); + File temp = File.createTempFile("modeshape", null); fos = new FileOutputStream(temp); Property dataProp = properties.get(JcrLexicon.DATA); @@ -248,8 +315,8 @@ class FileSystemWorkspace extends PathWorkspace { customPropertiesFactory.recordResourceProperties(context, source.getName(), Location.create(parentPath), - newFile, - properties); + parentFile, + extraResource(properties)); } else if (JcrNtLexicon.FOLDER.equals(primaryType) || primaryType == null) { ensureValidPathLength(newFile); @@ -266,7 +333,7 @@ class FileSystemWorkspace extends PathWorkspace { source.getName(), Location.create(newPath), newFile, - properties); + extraFolder(properties)); } else { // Set error and return @@ -286,8 +353,17 @@ class FileSystemWorkspace extends PathWorkspace { public PathNode removeNode( Path nodePath ) { File nodeFile; + CustomPropertiesFactory customPropertiesFactory = source.customPropertiesFactory(); + if (!nodePath.isRoot() && JcrLexicon.CONTENT.equals(nodePath.getLastSegment().getName())) { nodeFile = fileFor(nodePath.getParent()); + + // Have the custom property factory remote all properties ... + customPropertiesFactory.recordResourceProperties(context, + source.getName(), + Location.create(nodePath), + nodeFile, + NO_PROPERTIES); if (!nodeFile.exists()) return null; FileOutputStream fos = null; @@ -306,6 +382,12 @@ class FileSystemWorkspace extends PathWorkspace { } } else { nodeFile = fileFor(nodePath); + // Have the custom property factory remote all properties ... + customPropertiesFactory.recordResourceProperties(context, + source.getName(), + Location.create(nodePath), + nodeFile, + NO_PROPERTIES); if (!nodeFile.exists()) return null; FileUtil.delete(nodeFile); @@ -338,49 +420,53 @@ class FileSystemWorkspace extends PathWorkspace { if (!path.isRoot() && JcrLexicon.CONTENT.equals(path.getLastSegment().getName())) { File file = fileFor(path.getParent()); if (file == null) return null; - // Discover the mime type ... - - String mimeType = null; - InputStream contents = null; - try { - // First try the file name (so we don't have to create an input stream, - // which may have too much latency if a remote network file) ... - mimeType = mimeTypeDetector.mimeTypeOf(file.getName(), null); - if (mimeType == null && contentUsedToDetermineMimeType) { - // Try to find the mime type using the content ... - contents = new BufferedInputStream(new FileInputStream(file)); - mimeType = mimeTypeDetector.mimeTypeOf(null, contents); - } - if (mimeType == null) mimeType = DEFAULT_MIME_TYPE; - properties.put(JcrLexicon.MIMETYPE, factory.create(JcrLexicon.MIMETYPE, mimeType)); - } catch (IOException e) { - I18n msg = FileSystemI18n.couldNotReadData; - throw new RepositorySourceException(source.getName(), msg.text(source.getName(), - getName(), - path.getString(registry))); - } finally { - if (contents != null) { - try { - contents.close(); - } catch (IOException e) { - } - } - } // First add any custom properties ... - Collection customProps = customPropertiesFactory.getResourceProperties(context, - location, - file, - mimeType); + Collection customProps = customPropertiesFactory.getResourceProperties(context, location, file, null); for (Property customProp : customProps) { properties.put(customProp.getName(), customProp); } + if (!properties.containsKey(JcrLexicon.MIMETYPE)) { + // Discover the mime type ... + String mimeType = null; + InputStream contents = null; + try { + // First try the file name (so we don't have to create an input stream, + // which may have too much latency if a remote network file) ... + mimeType = mimeTypeDetector.mimeTypeOf(file.getName(), null); + if (mimeType == null && contentUsedToDetermineMimeType) { + // Try to find the mime type using the content ... + contents = new BufferedInputStream(new FileInputStream(file)); + mimeType = mimeTypeDetector.mimeTypeOf(null, contents); + } + if (mimeType == null) mimeType = DEFAULT_MIME_TYPE; + properties.put(JcrLexicon.MIMETYPE, factory.create(JcrLexicon.MIMETYPE, mimeType)); + } catch (IOException e) { + I18n msg = FileSystemI18n.couldNotReadData; + throw new RepositorySourceException(source.getName(), msg.text(source.getName(), + getName(), + path.getString(registry))); + } finally { + if (contents != null) { + try { + contents.close(); + } catch (IOException e) { + } + } + } + } + // The request is to get properties of the "jcr:content" child node ... // ... use the dna:resource node type. This is the same as nt:resource, but is not referenceable // since we cannot assume that we control all access to this file and can track its movements - nodeType = JcrNtLexicon.RESOURCE; - properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.RESOURCE)); + Property primaryType = properties.get(JcrLexicon.PRIMARY_TYPE); + if (primaryType == null) { + nodeType = JcrNtLexicon.RESOURCE; + properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.RESOURCE)); + } else { + nodeType = nameValueFor(primaryType); + } properties.put(JcrLexicon.LAST_MODIFIED, factory.create(JcrLexicon.LAST_MODIFIED, dateFactory.create(file.lastModified()))); @@ -399,7 +485,7 @@ class FileSystemWorkspace extends PathWorkspace { if (file == null) return null; if (file.isDirectory()) { - String[] childNames = file.list(source.filenameFilter()); + String[] childNames = file.list(source.filenameFilter(true)); Arrays.sort(childNames); List childSegments = new ArrayList(childNames.length); @@ -420,8 +506,13 @@ class FileSystemWorkspace extends PathWorkspace { childSegments); } - nodeType = JcrNtLexicon.FOLDER; - properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FOLDER)); + Property primaryType = properties.get(JcrLexicon.PRIMARY_TYPE); + if (primaryType == null) { + nodeType = JcrNtLexicon.FOLDER; + properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FOLDER)); + } else { + nodeType = nameValueFor(primaryType); + } // return new DefaultPathNode(path, source.getRootNodeUuidObject(), properties, childSegments); return new PathNode(null, path.getParent(), path.getLastSegment(), properties, childSegments); @@ -432,9 +523,17 @@ class FileSystemWorkspace extends PathWorkspace { properties.put(customProp.getName(), customProp); } - nodeType = JcrNtLexicon.FILE; - properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FILE)); - properties.put(JcrLexicon.CREATED, factory.create(JcrLexicon.CREATED, dateFactory.create(file.lastModified()))); + Property primaryType = properties.get(JcrLexicon.PRIMARY_TYPE); + if (primaryType == null) { + nodeType = JcrNtLexicon.FILE; + properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FILE)); + } else { + nodeType = nameValueFor(primaryType); + } + if (!properties.containsKey(JcrLexicon.CREATED)) { + properties.put(JcrLexicon.CREATED, factory.create(JcrLexicon.CREATED, dateFactory.create(file.lastModified()))); + } + // node = new DefaultPathNode(path, null, properties, // Collections.singletonList(pathFactory.createSegment(JcrLexicon.CONTENT))); @@ -444,7 +543,6 @@ class FileSystemWorkspace extends PathWorkspace { if (nodeType != null && logger.isTraceEnabled()) { long stopTime = System.nanoTime(); long ms = TimeUnit.MICROSECONDS.convert(stopTime - startTime, TimeUnit.NANOSECONDS); - ValueFactory stringFactory = context.getValueFactories().getStringFactory(); String pathStr = stringFactory.create(path); String typeStr = stringFactory.create(nodeType); logger.trace("Loaded '{0}' node '{1}' in {2}microsec", typeStr, pathStr, ms); @@ -563,7 +661,6 @@ class FileSystemWorkspace extends PathWorkspace { // Don't validate the root node if (node.getParent() == null) return; - NameFactory nameFactory = context.getValueFactories().getNameFactory(); Map properties = node.getProperties(); Property primaryTypeProp = properties.get(JcrLexicon.PRIMARY_TYPE); Name primaryType = primaryTypeProp == null ? JcrNtLexicon.FOLDER : nameFactory.create(primaryTypeProp.getFirstValue()); @@ -612,7 +709,7 @@ class FileSystemWorkspace extends PathWorkspace { } if (root.isDirectory()) { - for (File child : root.listFiles(source.filenameFilter())) { + for (File child : root.listFiles(source.filenameFilter(false))) { ensureValidPathLength(child, delta); } @@ -622,4 +719,68 @@ class FileSystemWorkspace extends PathWorkspace { } } + /** + * Determine the 'extra' properties for a folder that should be stored by the CustomPropertiesFactory. + * + * @param properties + * @return the extra properties, or null if the supplied properties reference is null + */ + protected Map extraFolder( Map properties ) { + if (properties == null) return null; + if (properties.isEmpty()) return properties; + Map extra = new HashMap(); + for (Property property : properties.values()) { + Name name = property.getName(); + if (name.equals(JcrLexicon.PRIMARY_TYPE) && primaryTypeIs(property, JcrNtLexicon.FOLDER)) continue; + extra.put(name, property); + } + return extra; + } + + /** + * Determine the 'extra' properties for a file node that should be stored by the CustomPropertiesFactory. + * + * @param properties + * @return the extra properties, or null if the supplied properties reference is null + */ + protected Map extraFile( Map properties ) { + if (properties == null) return null; + if (properties.isEmpty()) return properties; + Map extra = new HashMap(); + for (Property property : properties.values()) { + Name name = property.getName(); + if (name.equals(JcrLexicon.PRIMARY_TYPE) && primaryTypeIs(property, JcrNtLexicon.FILE)) continue; + extra.put(name, property); + } + return extra; + } + + /** + * Determine the 'extra' properties for a resource node that should be stored by the CustomPropertiesFactory. + * + * @param properties + * @return the extra properties, or null if the supplied properties reference is null + */ + protected Map extraResource( Map properties ) { + if (properties == null) return null; + if (properties.isEmpty()) return properties; + Map extra = new HashMap(); + for (Property property : properties.values()) { + Name name = property.getName(); + if (name.equals(JcrLexicon.PRIMARY_TYPE) && primaryTypeIs(property, JcrNtLexicon.RESOURCE)) continue; + else if (name.equals(JcrLexicon.DATA)) continue; + extra.put(name, property); + } + return extra; + } + + protected boolean primaryTypeIs( Property property, + Name primaryType ) { + Name actualPrimaryType = nameValueFor(property); + return actualPrimaryType.equals(primaryType); + } + + protected Name nameValueFor( Property property ) { + return nameFactory.create(property.getFirstValue()); + } } Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/IgnoreProperties.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/IgnoreProperties.java (working copy) @@ -0,0 +1,134 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import java.io.File; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Location; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; + +/** + * A {@link CustomPropertiesFactory} implementation that ignores the "extra" or "custom" properties for 'nt:file', 'nt:folder', + * and 'nt:resource' nodes. + */ +public class IgnoreProperties extends BasePropertiesFactory { + + private static final long serialVersionUID = 1L; + + /** + * Create an instance of this factory. + */ + public IgnoreProperties() { + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getDirectoryProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File) + */ + @Override + public Collection getDirectoryProperties( ExecutionContext context, + Location location, + File directory ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getFileProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File) + */ + @Override + public Collection getFileProperties( ExecutionContext context, + Location location, + File file ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getResourceProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File, java.lang.String) + */ + @Override + public Collection getResourceProperties( ExecutionContext context, + Location location, + File file, + String mimeType ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordDirectoryProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordDirectoryProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + return NO_NAMES; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordFileProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordFileProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + return NO_NAMES; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordResourceProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordResourceProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + return NO_NAMES; + } +} Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/LogProperties.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/LogProperties.java (working copy) @@ -0,0 +1,164 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import java.io.File; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.modeshape.common.util.Logger; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Location; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.ValueFactory; + +/** + * A {@link CustomPropertiesFactory} implementation that logs information about the "extra" or "custom" properties for 'nt:file', + * 'nt:folder', and 'nt:resource' nodes. + */ +public class LogProperties extends BasePropertiesFactory { + private static final long serialVersionUID = 1L; + + private final Logger logger; + + /** + * Create an instance of this factory. + * + * @param logger the logger that should be used, or null if the default logger should be used + */ + public LogProperties( Logger logger ) { + this.logger = logger != null ? logger : Logger.getLogger(getClass()); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getDirectoryProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File) + */ + @Override + public Collection getDirectoryProperties( ExecutionContext context, + Location location, + File directory ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getFileProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File) + */ + @Override + public Collection getFileProperties( ExecutionContext context, + Location location, + File file ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getResourceProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File, java.lang.String) + */ + @Override + public Collection getResourceProperties( ExecutionContext context, + Location location, + File file, + String mimeType ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordDirectoryProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordDirectoryProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + if (!properties.isEmpty()) { + ValueFactory strings = context.getValueFactories().getStringFactory(); + for (Property property : properties.values()) { + if (STANDARD_PROPERTIES_FOR_FILE_OR_FOLDER.contains(property.getName())) continue; + String name = strings.create(property.getName()); + logger.warn(FileSystemI18n.couldNotStoreProperty, name, file.getPath(), sourceName); + } + } + return NO_NAMES; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordFileProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordFileProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + if (!properties.isEmpty()) { + ValueFactory strings = context.getValueFactories().getStringFactory(); + for (Property property : properties.values()) { + if (STANDARD_PROPERTIES_FOR_FILE_OR_FOLDER.contains(property.getName())) continue; + String name = strings.create(property.getName()); + logger.warn(FileSystemI18n.couldNotStoreProperty, name, file.getPath(), sourceName); + } + } + return NO_NAMES; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordResourceProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordResourceProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + if (!properties.isEmpty()) { + ValueFactory strings = context.getValueFactories().getStringFactory(); + for (Property property : properties.values()) { + if (STANDARD_PROPERTIES_FOR_CONTENT.contains(property.getName())) continue; + String name = strings.create(property.getName()); + logger.warn(FileSystemI18n.couldNotStoreProperty, name, file.getPath(), sourceName); + } + } + return NO_NAMES; + } +} Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/StoreProperties.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/StoreProperties.java (working copy) @@ -0,0 +1,405 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import java.io.File; +import java.io.FileWriter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.modeshape.common.text.QuoteEncoder; +import org.modeshape.common.text.TextDecoder; +import org.modeshape.common.text.TextEncoder; +import org.modeshape.common.text.XmlNameEncoder; +import org.modeshape.common.util.IoUtil; +import org.modeshape.common.util.StringUtil; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.JcrLexicon; +import org.modeshape.graph.Location; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.property.Binary; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.PropertyFactory; +import org.modeshape.graph.property.PropertyType; +import org.modeshape.graph.property.ValueFactories; +import org.modeshape.graph.property.ValueFactory; + +/** + * A {@link CustomPropertiesFactory} implementation that stores "extra" or "custom" properties for 'nt:file', 'nt:folder', and + * 'nt:resource' nodes in a separate file that is named the same as the original but with a different extension. + */ +public class StoreProperties extends BasePropertiesFactory { + + private static final long serialVersionUID = 1L; + + /** + * The regex pattern string used to parse properties. The capture groups are as follows: + *
    + *
  1. property name (encoded)
  2. + *
  3. property type string
  4. + *
  5. a '[' if the value is multi-valued
  6. + *
  7. the single value, or comma-separated values
  8. + *
+ *

+ * The expression is: ([\S]+)\s*[(](\w+)[)]\s*([\[]?)?([^\]]+)[\]]? + *

+ */ + protected static final String PROPERTY_PATTERN_STRING = "([\\S]+)\\s*[(](\\w+)[)]\\s*([\\[]?)?([^\\]]+)[\\]]?"; + protected static final Pattern PROPERTY_PATTERN = Pattern.compile(PROPERTY_PATTERN_STRING); + + /** + * The regex pattern string used to parse quoted string property values. This is a repeating expression, and group(0) captures + * the characters within the quotes (including escaped double quotes). + *

+ * The expression is: \"((((?<=\\)\")|[^"])*)\" + *

+ */ + protected static final String STRING_VALUE_PATTERN_STRING = "\\\"((((?<=\\\\)\\\")|[^\"])*)\\\""; + protected static final Pattern STRING_VALUE_PATTERN = Pattern.compile(STRING_VALUE_PATTERN_STRING); + + /** + * The regex pattern string used to parse non-string property values (including hexadecimal-encoded binary values). This is a + * repeating expression, and group(1) captures the individual values. + *

+ * The expression is: ([^\s,]+)\s*[,]*\s* + *

+ */ + protected static final String VALUE_PATTERN_STRING = "([^\\s,]+)\\s*[,]*\\s*"; + protected static final Pattern VALUE_PATTERN = Pattern.compile(VALUE_PATTERN_STRING); + + public static final String DEFAULT_EXTENSION = ".modeshape"; + public static final String DEFAULT_RESOURCE_EXTENSION = ".content.modeshape"; + protected static final Map NO_PROPERTIES_MAP = Collections.emptyMap(); + + private final String extension; + private final String resourceExtension; + private String sourceName; + private TextEncoder encoder = new XmlNameEncoder(); + private TextDecoder decoder = new XmlNameEncoder(); + private QuoteEncoder quoter = new QuoteEncoder(); + + /** + * + */ + public StoreProperties() { + extension = DEFAULT_EXTENSION; + resourceExtension = DEFAULT_RESOURCE_EXTENSION; + } + + @Override + public FilenameFilter getFilenameFilter( final FilenameFilter exclusionFilter ) { + final Pattern extensionFilter = Pattern.compile(extension.replaceAll("\\.", "\\\\.") + "$"); + final Pattern resourceExtensionFilter = Pattern.compile(resourceExtension.replaceAll("\\.", "\\\\.") + "$"); + return new FilenameFilter() { + + public boolean accept( File dir, + String name ) { + if (extensionFilter.matcher(name).matches()) return false; + if (resourceExtensionFilter.matcher(name).matches()) return false; + if (exclusionFilter != null && !exclusionFilter.accept(dir, name)) return false; + return true; + } + }; + + } + + /** + * @return sourceName + */ + public String getSourceName() { + return sourceName; + } + + /** + * @param sourceName Sets sourceName to the specified value. + */ + public void setSourceName( String sourceName ) { + this.sourceName = sourceName; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getDirectoryProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File) + */ + @Override + public Collection getDirectoryProperties( ExecutionContext context, + Location location, + File directory ) { + File parent = directory.getParentFile(); + if (parent == null) return NO_PROPERTIES_COLLECTION; + return load(propertiesFileFor(directory), context).values(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getFileProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File) + */ + @Override + public Collection getFileProperties( ExecutionContext context, + Location location, + File file ) { + return load(propertiesFileFor(file), context).values(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getResourceProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File, java.lang.String) + */ + @Override + public Collection getResourceProperties( ExecutionContext context, + Location location, + File file, + String mimeType ) { + return load(propertiesFileForResource(file), context).values(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordDirectoryProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordDirectoryProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + return write(propertiesFileFor(file), context, properties); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordFileProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordFileProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + return write(propertiesFileFor(file), context, properties); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordResourceProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordResourceProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + return write(propertiesFileForResource(file), context, properties); + } + + protected File propertiesFileFor( File fileOrDirectory ) { + return new File(fileOrDirectory.getPath() + extension); + } + + protected File propertiesFileForResource( File fileOrDirectory ) { + return new File(fileOrDirectory.getPath() + resourceExtension); + } + + protected Map load( File propertiesFile, + ExecutionContext context ) throws RepositorySourceException { + if (!propertiesFile.exists() || !propertiesFile.canRead()) return NO_PROPERTIES_MAP; + try { + String content = IoUtil.read(propertiesFile); + ValueFactories factories = context.getValueFactories(); + PropertyFactory propFactory = context.getPropertyFactory(); + Map result = new HashMap(); + for (String line : StringUtil.splitLines(content)) { + // Parse each line ... + Property property = parse(line, factories, propFactory); + if (property != null) { + result.put(property.getName(), property); + } + } + return result; + } catch (IOException e) { + throw new RepositorySourceException(sourceName, e); + } + } + + protected Set write( File propertiesFile, + ExecutionContext context, + Map properties ) throws RepositorySourceException { + if (properties.isEmpty()) { + if (propertiesFile.exists()) { + // Delete the file ... + propertiesFile.delete(); + } + return Collections.emptySet(); + } + Set names = new HashSet(); + try { + ValueFactory strings = context.getValueFactories().getStringFactory(); + Writer fileWriter = new FileWriter(propertiesFile); + try { + // Write the primary type first ... + Property primaryType = properties.get(JcrLexicon.PRIMARY_TYPE); + if (primaryType != null) { + write(primaryType, fileWriter, strings); + names.add(primaryType.getName()); + } + // Then write the mixin types ... + Property mixinTypes = properties.get(JcrLexicon.MIXIN_TYPES); + if (mixinTypes != null) { + write(mixinTypes, fileWriter, strings); + names.add(mixinTypes.getName()); + } + // Then write the UUID ... + Property uuid = properties.get(JcrLexicon.UUID); + if (uuid != null) { + write(uuid, fileWriter, strings); + names.add(uuid.getName()); + } + // Then all the others ... + for (Property property : properties.values()) { + if (property == primaryType || property == mixinTypes || property == uuid) continue; + write(property, fileWriter, strings); + names.add(property.getName()); + } + } finally { + fileWriter.close(); + } + } catch (IOException e) { + throw new RepositorySourceException(sourceName, e); + } + return names; + } + + protected void write( Property property, + Writer stream, + ValueFactory strings ) throws IOException { + String name = strings.create(property.getName()); + stream.append(encoder.encode(name)); + if (property.isEmpty()) return; + stream.append(" ("); + PropertyType type = PropertyType.discoverType(property.getFirstValue()); + stream.append(type.getName().toLowerCase()); + stream.append(") "); + if (property.isMultiple()) { + stream.append('['); + } + boolean first = true; + boolean quote = type == PropertyType.STRING; + for (Object value : property) { + if (first) first = false; + else stream.append(", "); + String str = null; + if (value instanceof Binary) { + str = StringUtil.getHexString(((Binary)value).getBytes()); + } else { + str = strings.create(value); + } + if (quote) { + stream.append('"'); + stream.append(quoter.encode(str)); + stream.append('"'); + } else { + stream.append(str); + } + } + if (property.isMultiple()) { + stream.append(']'); + } + stream.append('\n'); + stream.flush(); + } + + protected Property parse( String line, + ValueFactories factories, + PropertyFactory propFactory ) { + if (line.length() == 0) return null; // blank line + char firstChar = line.charAt(0); + if (firstChar == '#') return null; // comment line + if (firstChar == ' ') return null; // ignore line + Matcher matcher = PROPERTY_PATTERN.matcher(line); + if (!matcher.matches()) { + // It should be an empty multi-valued property, and the line consists only of the name ... + Name name = factories.getNameFactory().create(decoder.decode(line)); + return propFactory.create(name); + } + + String nameString = decoder.decode(matcher.group(1)); + String typeString = matcher.group(2); + String valuesString = matcher.group(4); + + Name name = factories.getNameFactory().create(nameString); + PropertyType type = PropertyType.valueFor(typeString); + + Pattern pattern = VALUE_PATTERN; + ValueFactory valueFactory = factories.getValueFactory(type); + boolean binary = false; + boolean decode = false; + if (type == PropertyType.STRING) { + // Parse the double-quoted value(s) ... + pattern = STRING_VALUE_PATTERN; + decode = true; + } else if (type == PropertyType.BINARY) { + binary = true; + } + Matcher valuesMatcher = pattern.matcher(valuesString); + List values = new ArrayList(); + while (valuesMatcher.find()) { + String valueString = valuesMatcher.group(1); + if (binary) { + // The value is a hexadecimal-encoded byte array ... + byte[] binaryValue = StringUtil.fromHexString(valueString); + Object value = valueFactory.create(binaryValue); + values.add(value); + } else { + if (decode) valueString = quoter.decode(valueString); + Object value = valueFactory.create(valueString); + values.add(value); + } + } + if (values.isEmpty()) return null; + return propFactory.create(name, type, values); + } +} Index: extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/ThrowProperties.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/java/org/modeshape/connector/filesystem/ThrowProperties.java (working copy) @@ -0,0 +1,195 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import java.io.File; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Location; +import org.modeshape.graph.connector.RepositorySourceException; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.ValueFactory; + +/** + * A {@link CustomPropertiesFactory} implementation that throws an exception when attempting to store the "extra" or "custom" + * properties for 'nt:file', 'nt:folder', and 'nt:resource' nodes. + */ +public class ThrowProperties extends BasePropertiesFactory { + + private static final long serialVersionUID = 1L; + + /** + * Create an instance of this factory. + */ + public ThrowProperties() { + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getDirectoryProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File) + */ + @Override + public Collection getDirectoryProperties( ExecutionContext context, + Location location, + File directory ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getFileProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File) + */ + @Override + public Collection getFileProperties( ExecutionContext context, + Location location, + File file ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#getResourceProperties(org.modeshape.graph.ExecutionContext, + * org.modeshape.graph.Location, java.io.File, java.lang.String) + */ + @Override + public Collection getResourceProperties( ExecutionContext context, + Location location, + File file, + String mimeType ) { + return NO_PROPERTIES_COLLECTION; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordDirectoryProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordDirectoryProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + int numProperties = properties.size(); + if (numProperties != 0) { + ValueFactory strings = context.getValueFactories().getStringFactory(); + StringBuilder names = new StringBuilder(); + boolean first = true; + for (Property property : properties.values()) { + if (STANDARD_PROPERTIES_FOR_FILE_OR_FOLDER.contains(property.getName())) continue; + if (first) first = false; + else names.append(","); + String name = strings.create(property.getName()); + names.append(name); + } + String msg = null; + if (properties.size() == 1) { + msg = FileSystemI18n.couldNotStoreProperties.text(names, file.getPath(), sourceName); + } else { + msg = FileSystemI18n.couldNotStoreProperty.text(names, file.getPath(), sourceName); + } + throw new RepositorySourceException(sourceName, msg); + } + return NO_NAMES; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordFileProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordFileProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + int numProperties = properties.size(); + if (numProperties != 0) { + ValueFactory strings = context.getValueFactories().getStringFactory(); + StringBuilder names = new StringBuilder(); + boolean first = true; + for (Property property : properties.values()) { + if (STANDARD_PROPERTIES_FOR_FILE_OR_FOLDER.contains(property.getName())) continue; + if (first) first = false; + else names.append(","); + String name = strings.create(property.getName()); + names.append(name); + } + String msg = null; + if (properties.size() == 1) { + msg = FileSystemI18n.couldNotStoreProperties.text(names, file.getPath(), sourceName); + } else { + msg = FileSystemI18n.couldNotStoreProperty.text(names, file.getPath(), sourceName); + } + throw new RepositorySourceException(sourceName, msg); + } + return NO_NAMES; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.connector.filesystem.CustomPropertiesFactory#recordResourceProperties(org.modeshape.graph.ExecutionContext, + * java.lang.String, org.modeshape.graph.Location, java.io.File, java.util.Map) + */ + @Override + public Set recordResourceProperties( ExecutionContext context, + String sourceName, + Location location, + File file, + Map properties ) throws RepositorySourceException { + int numProperties = properties.size(); + if (numProperties != 0) { + ValueFactory strings = context.getValueFactories().getStringFactory(); + StringBuilder names = new StringBuilder(); + boolean first = true; + for (Property property : properties.values()) { + if (STANDARD_PROPERTIES_FOR_CONTENT.contains(property.getName())) continue; + if (first) first = false; + else names.append(","); + String name = strings.create(property.getName()); + names.append(name); + } + String msg = null; + if (properties.size() == 1) { + msg = FileSystemI18n.couldNotStoreProperties.text(names, file.getPath(), sourceName); + } else { + msg = FileSystemI18n.couldNotStoreProperty.text(names, file.getPath(), sourceName); + } + throw new RepositorySourceException(sourceName, msg); + } + return NO_NAMES; + } +} Index: extensions/modeshape-connector-filesystem/src/main/resources/org/modeshape/connector/filesystem/FileSystemI18n.properties =================================================================== --- extensions/modeshape-connector-filesystem/src/main/resources/org/modeshape/connector/filesystem/FileSystemI18n.properties (revision 2307) +++ extensions/modeshape-connector-filesystem/src/main/resources/org/modeshape/connector/filesystem/FileSystemI18n.properties (working copy) @@ -61,7 +61,12 @@ eagerFileLoadingPropertyCategory = Advanced determineMimeTypeUsingContentPropertyDescription = Optional flag that defines whether the MIME type for 'nt:resource' nodes should be determined from the filename and the content, or just the filename. The default is to use the filename and content. determineMimeTypeUsingContentPropertyLabel = Determine MIME type using content determineMimeTypeUsingContentPropertyCategory = Advanced - +extraPropertiesPropertyDescription = Optional setting that specifies how to handle the extra properties on "nt:file", "nt:folder", and "nt:resource" nodes that cannot be represented on the native files themselves. Set this to "log" if warnings are to be sent to the log, or "error" if setting such properties should cause an error, or "store" if they should be stored in ancillary files next to the files and folders, or "ignore" if they should be silently ignored. The "log" value will be used by default or an invalid value is specified. This setting will be ignored if a "Extra Properties Factory Classname" value is specified. +extraPropertiesPropertyLabel = Extra Properties Behavior +extraPropertiesPropertyCategory = Advanced +customPropertiesFactoryPropertyDescription = The name of the CustomPropertiesFactory implementation that will be used to handle any extra properties on "nt:file", "nt:folder", and "nt:resource" nodes that cannot be represented on the native files themselves. Setting this will override any value set on the "Extra Properties" setting. +customPropertiesFactoryPropertyLabel = Extra Properties Factory Classname +customPropertiesFactoryPropertyCategory = Advanced # Writable tests parentIsReadOnly = The parent node at path "{0}" in workspace "{1}" in {2} cannot be written to. See java.io.File\#canWrite(). @@ -79,3 +84,5 @@ missingRequiredProperty = Missing required property "{3}" at path "{0}" in works deleteFailed = Could not delete file at path "{0}" in workspace "{1}" in {2} getCanonicalPathFailed = Could not determine canonical path maxPathLengthExceeded = The maximum absolute path length ({0}) for source "{1}" was exceeded by the node at: {2} ({3}) +couldNotStoreProperty = Unable to store "{0}" property for the file "{1}" in the "{2}" file system source. Check the "extraProperties" setting for this source. +couldNotStoreProperties = Unable to store the extra properties {0} for the file "{1}" in the "{2}" file system source. Check the "extraProperties" setting for this source. Index: extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWithIgnorePropertiesWritableTest.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWithIgnorePropertiesWritableTest.java (working copy) @@ -0,0 +1,36 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import org.modeshape.graph.connector.RepositorySource; + +public class FileSystemConnectorWithIgnorePropertiesWritableTest extends FileSystemConnectorWritableTest { + + @Override + protected RepositorySource setUpSource() throws Exception { + FileSystemSource source = (FileSystemSource)super.setUpSource(); + source.setExtraPropertiesBehavior("ignore"); + return source; + } +} Index: extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWithLogPropertiesWritableTest.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWithLogPropertiesWritableTest.java (working copy) @@ -0,0 +1,36 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import org.modeshape.graph.connector.RepositorySource; + +public class FileSystemConnectorWithLogPropertiesWritableTest extends FileSystemConnectorWritableTest { + + @Override + protected RepositorySource setUpSource() throws Exception { + FileSystemSource source = (FileSystemSource)super.setUpSource(); + source.setExtraPropertiesBehavior("log"); + return source; + } +} Index: extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWithStorePropertiesWritableTest.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWithStorePropertiesWritableTest.java (working copy) @@ -0,0 +1,43 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import org.junit.Test; +import org.modeshape.graph.connector.RepositorySource; + +public class FileSystemConnectorWithStorePropertiesWritableTest extends FileSystemConnectorWritableTest { + + @Override + protected RepositorySource setUpSource() throws Exception { + FileSystemSource source = (FileSystemSource)super.setUpSource(); + source.setExtraPropertiesBehavior("store"); + return source; + } + + @Override + @Test + public void shouldBeAbleToCopyFile() { + super.shouldBeAbleToCopyFile(); + } +} Index: extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWithThrowPropertiesWritableTest.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/FileSystemConnectorWithThrowPropertiesWritableTest.java (working copy) @@ -0,0 +1,36 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import org.modeshape.graph.connector.RepositorySource; + +public class FileSystemConnectorWithThrowPropertiesWritableTest extends FileSystemConnectorWritableTest { + + @Override + protected RepositorySource setUpSource() throws Exception { + FileSystemSource source = (FileSystemSource)super.setUpSource(); + source.setExtraPropertiesBehavior("throw"); + return source; + } +} Index: extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/SimplePropertiesFactoryTest.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ extensions/modeshape-connector-filesystem/src/test/java/org/modeshape/connector/filesystem/SimplePropertiesFactoryTest.java (working copy) @@ -0,0 +1,167 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.connector.filesystem; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.modeshape.common.util.FileUtil; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Location; +import org.modeshape.graph.property.Name; +import org.modeshape.graph.property.Property; + +/** + * + */ +public class SimplePropertiesFactoryTest { + + private static final String JAVA_TYPE = "text/x-java-source"; + + private StoreProperties factory; + private File testArea; + private ExecutionContext context; + private Collection properties; + private Location location; + private String source; + private Map newProperties; + + @Before + public void beforeEach() throws IOException { + testArea = new File("target/test/properties"); + FileUtil.delete(testArea); + testArea.mkdirs(); + FileUtil.copy(new File("src/main/java/org"), new File(testArea, "org")); + factory = new StoreProperties(); + newProperties = new HashMap(); + context = new ExecutionContext(); + context.getNamespaceRegistry().register("test", "http://www.modeshape.org/test/1.0"); + } + + @After + public void afterEach() { + // FileUtil.delete(testArea); + testArea = null; + } + + @Test + public void shouldHaveCopiedFilesIntoTestArea() throws Exception { + assertFolder("org"); + assertFolder("org/modeshape"); + assertFolder("org/modeshape/connector"); + assertFolder("org/modeshape/connector/filesystem"); + assertFile("org/modeshape/connector/filesystem/CustomPropertiesFactory.java"); + assertFile("org/modeshape/connector/filesystem/FileSystemI18n.java"); + assertFile("org/modeshape/connector/filesystem/FileSystemRepository.java"); + assertFile("org/modeshape/connector/filesystem/FileSystemSource.java"); + assertFile("org/modeshape/connector/filesystem/FileSystemWorkspace.java"); + assertFile("org/modeshape/connector/filesystem/package-info.java"); + assertFile("org/modeshape/connector/filesystem/StoreProperties.java"); + } + + @Test + public void shouldReadPropertiesForDirectoryWhenExtraPropertiesFileDoesNotExist() throws Exception { + properties = factory.getDirectoryProperties(context, location, fileAt("org")); + assertThat(properties.isEmpty(), is(true)); + properties = factory.getDirectoryProperties(context, location, fileAt("org/modeshape")); + assertThat(properties.isEmpty(), is(true)); + properties = factory.getDirectoryProperties(context, location, fileAt("org/modeshape/connector")); + assertThat(properties.isEmpty(), is(true)); + properties = factory.getDirectoryProperties(context, location, fileAt("org/modeshape/connector/filesystem")); + assertThat(properties.isEmpty(), is(true)); + } + + @Test + public void shouldReadPropertiesForFileWhenExtraPropertiesFileDoesNotExist() throws Exception { + File file = fileAt("org/modeshape/connector/filesystem/CustomPropertiesFactory.java"); + properties = factory.getFileProperties(context, location, file); + assertThat(properties.isEmpty(), is(true)); + } + + @Test + public void shouldReadPropertiesForResourceWhenExtraPropertiesFileDoesNotExist() throws Exception { + File file = fileAt("org/modeshape/connector/filesystem/CustomPropertiesFactory.java"); + properties = factory.getResourceProperties(context, location, file, JAVA_TYPE); + assertThat(properties.isEmpty(), is(true)); + } + + @Test + public void shouldWritePropertiesForDirectory() throws Exception { + File dir = fileAt("org"); + addProperties(); + factory.recordDirectoryProperties(context, source, location, dir, newProperties); + properties = factory.getDirectoryProperties(context, location, dir); + assertPropertiesMatch(); + } + + protected File fileAt( String path ) { + return new File(testArea, path); + } + + protected void addProperty( String name, + Object... values ) { + Name propName = context.getValueFactories().getNameFactory().create(name); + Property property = context.getPropertyFactory().create(propName, values); + newProperties.put(property.getName(), property); + } + + protected void addProperties() { + addProperty("test:stringProp", "val1"); + addProperty("test:stringPropWithOddChars", "val1 has spaces and \"quotes\" and \n new line characters"); + addProperty("test:longProp", 2L); + addProperty("test:doubleProp", 3.523); + addProperty("test:booleanProp", true); + addProperty("test:binaryProp", context.getValueFactories().getBinaryFactory().create("This is the content".getBytes())); + } + + protected void assertPropertiesMatch() { + assertThat(properties.size(), is(newProperties.size())); + for (Property prop : properties) { + assertThat(newProperties.containsKey(prop.getName()), is(true)); + assertThat(newProperties.get(prop.getName()), is(prop)); + } + } + + protected void assertFolder( String path ) { + File file = new File(testArea, path); + assertThat(file.exists(), is(true)); + assertThat(file.canRead(), is(true)); + assertThat(file.isDirectory(), is(true)); + } + + protected void assertFile( String path ) { + File file = new File(testArea, path); + assertThat(file.exists(), is(true)); + assertThat(file.canRead(), is(true)); + assertThat(file.isFile(), is(true)); + assertThat(file.length() > 0L, is(true)); + } +} Index: extensions/modeshape-connector-filesystem/testFolder/testFile deleted file mode 100644 =================================================================== --- extensions/modeshape-connector-filesystem/testFolder/testFile (revision 2307) +++ /dev/null (working copy) @@ -1 +0,0 @@ -Test content \ No newline at end of file Index: extensions/modeshape-connector-svn/src/main/java/org/modeshape/connector/svn/SvnWorkspace.java =================================================================== --- extensions/modeshape-connector-svn/src/main/java/org/modeshape/connector/svn/SvnWorkspace.java (revision 2307) +++ extensions/modeshape-connector-svn/src/main/java/org/modeshape/connector/svn/SvnWorkspace.java (working copy) @@ -66,7 +66,7 @@ public class SvnWorkspace extends PathWorkspace { Arrays.asList(new Name[] { JcrLexicon.PRIMARY_TYPE, JcrLexicon.DATA, - JcrLexicon.ENCODED, + JcrLexicon.ENCODING, JcrLexicon.MIMETYPE, JcrLexicon.LAST_MODIFIED, JcrLexicon.LAST_MODIFIED_BY, Index: extensions/modeshape-sequencer-zip/src/main/java/org/modeshape/sequencer/zip/ZipSequencer.java =================================================================== --- extensions/modeshape-sequencer-zip/src/main/java/org/modeshape/sequencer/zip/ZipSequencer.java (revision 2307) +++ extensions/modeshape-sequencer-zip/src/main/java/org/modeshape/sequencer/zip/ZipSequencer.java (working copy) @@ -105,7 +105,7 @@ public class ZipSequencer implements StreamSequencer { output.setProperty(contentPath, JcrLexicon.DATA, binaryFactory.create(bytes)); // all other nt:file properties should be generated by other sequencers (mimetype, encoding,...) but we'll // default them here - output.setProperty(contentPath, JcrLexicon.ENCODED, "binary"); + output.setProperty(contentPath, JcrLexicon.ENCODING, "binary"); output.setProperty(contentPath, JcrLexicon.LAST_MODIFIED, dateFactory.create(entry.getTime()).toString()); output.setProperty(contentPath, JcrLexicon.MIMETYPE, "application/octet-stream"); Index: modeshape-common/src/main/java/org/modeshape/common/text/QuoteEncoder.java new file mode 100644 =================================================================== --- /dev/null (revision 2307) +++ modeshape-common/src/main/java/org/modeshape/common/text/QuoteEncoder.java (working copy) @@ -0,0 +1,110 @@ +/* + * ModeShape (http://www.modeshape.org) + * See the COPYRIGHT.txt file distributed with this work for information + * regarding copyright ownership. Some portions may be licensed + * to Red Hat, Inc. under one or more contributor license agreements. + * See the AUTHORS.txt file in the distribution for a full listing of + * individual contributors. + * + * ModeShape is free software. Unless otherwise indicated, all code in ModeShape + * is licensed to you under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * ModeShape is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.modeshape.common.text; + +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.BitSet; + +/** + * + */ +public class QuoteEncoder implements TextDecoder, TextEncoder { + + private static final BitSet ESCAPE_CHARACTERS = new BitSet(256); + + public static final char ESCAPE_CHARACTER = '\\'; + + static { + ESCAPE_CHARACTERS.set('"'); + ESCAPE_CHARACTERS.set('\\'); + ESCAPE_CHARACTERS.set('\n'); + ESCAPE_CHARACTERS.set('\t'); + } + + /** + * + */ + public QuoteEncoder() { + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.common.text.TextDecoder#decode(java.lang.String) + */ + public String decode( String encodedText ) { + if (encodedText == null) return null; + if (encodedText.length() == 0) return ""; + final StringBuilder result = new StringBuilder(); + final CharacterIterator iter = new StringCharacterIterator(encodedText); + for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { + if (c == ESCAPE_CHARACTER) { + // Eat this escape character, and process the next character ... + char nextChar = iter.next(); + if (nextChar == 'n') { + result.append('\n'); + } else if (nextChar == 't') { + result.append('\t'); + } else if (nextChar == 'r') { + result.append('\r'); + } else if (nextChar == 'f') { + result.append('\f'); + } else { + result.append(nextChar); + } + } else { + result.append(c); + } + } + return result.toString(); + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.common.text.TextEncoder#encode(java.lang.String) + */ + public String encode( String text ) { + final StringBuilder result = new StringBuilder(); + final CharacterIterator iter = new StringCharacterIterator(text); + for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { + if (ESCAPE_CHARACTERS.get(c)) { + result.append(ESCAPE_CHARACTER); + if (c == '\n') { + c = 'n'; + } else if (c == '\t') { + c = 't'; + } else if (c == '\r') { + c = 'r'; + } else if (c == '\f') { + c = 'f'; + } + } + result.append(c); + } + return result.toString(); + } + +} Index: modeshape-common/src/main/java/org/modeshape/common/util/StringUtil.java =================================================================== --- modeshape-common/src/main/java/org/modeshape/common/util/StringUtil.java (revision 2307) +++ modeshape-common/src/main/java/org/modeshape/common/util/StringUtil.java (working copy) @@ -466,6 +466,19 @@ public class StringUtil { } } + public static byte[] fromHexString( String hexadecimal ) { + int len = hexadecimal.length(); + if (len % 2 != 0) { + hexadecimal = "0" + hexadecimal; + } + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte)((Character.digit(hexadecimal.charAt(i), 16) << 4) + Character.digit(hexadecimal.charAt(i + 1), + 16)); + } + return data; + } + private StringUtil() { // Prevent construction } Index: modeshape-graph/src/main/java/org/modeshape/graph/JcrLexicon.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/JcrLexicon.java (revision 2307) +++ modeshape-graph/src/main/java/org/modeshape/graph/JcrLexicon.java (working copy) @@ -45,7 +45,7 @@ public class JcrLexicon { public static final Name CONTENT = new BasicName(Namespace.URI, "content"); public static final Name CREATED = new BasicName(Namespace.URI, "created"); public static final Name CREATED_BY = new BasicName(Namespace.URI, "createdBy"); - public static final Name ENCODED = new BasicName(Namespace.URI, "encoded"); + public static final Name ENCODING = new BasicName(Namespace.URI, "encoding"); public static final Name MIMETYPE = new BasicName(Namespace.URI, "mimeType"); public static final Name DATA = new BasicName(Namespace.URI, "data"); public static final Name LAST_MODIFIED = new BasicName(Namespace.URI, "lastModified"); Index: modeshape-graph/src/main/java/org/modeshape/graph/property/PropertyType.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/property/PropertyType.java (revision 2307) +++ modeshape-graph/src/main/java/org/modeshape/graph/property/PropertyType.java (working copy) @@ -29,9 +29,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +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.UUID; import net.jcip.annotations.Immutable; @@ -85,12 +87,16 @@ public enum PropertyType { } private static final List ALL_PROPERTY_TYPES; + private static final Map PROPERTY_TYPE_BY_LOWERCASE_NAME; static { List types = new ArrayList(); + Map byLowerCaseName = new HashMap(); for (PropertyType type : PropertyType.values()) { types.add(type); + byLowerCaseName.put(type.getName().toLowerCase(), type); } ALL_PROPERTY_TYPES = Collections.unmodifiableList(types); + PROPERTY_TYPE_BY_LOWERCASE_NAME = Collections.unmodifiableMap(byLowerCaseName); } private final String name; @@ -227,4 +233,9 @@ public enum PropertyType { public static Iterator iterator() { return ALL_PROPERTY_TYPES.iterator(); } + + public static PropertyType valueFor( String typeNameInAnyCase ) { + PropertyType type = PROPERTY_TYPE_BY_LOWERCASE_NAME.get(typeNameInAnyCase); + return type != null ? type : PropertyType.STRING; + } }