diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index b725fa9bcf..e2da4e6b4a 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -123,3 +123,8 @@ patch.wcmFolders.webprojects.result.exists=The Web Projects folder already exist patch.wcmFolders.webprojects.result.created=The Web Projects folder was successfully created: {0} patch.wcmFolders.webforms.result.exists=The Web Forms folder already exists: {0} patch.wcmFolders.webforms.result.created=The Web Forms folder was successfully created: {0} + +patch.linkNodeExtension.description=Fixes link node file extensions to have a .url extension. +patch.linkNodeExtension.result=Fixed {0} link node file extensions. See file {1} for details. +patch.linkNodeExtension.err.unable_to_fix=Auto-fixing of link node file extensions failed. See file {0} for details. +patch.linkNodeExtension.rewritten=Name ''{0}'' rewritten to ''{1}'' diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/network-protocol-context.xml index cd7c8b8328..b0ced99745 100644 --- a/config/alfresco/network-protocol-context.xml +++ b/config/alfresco/network-protocol-context.xml @@ -61,6 +61,7 @@ + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index e009b0a2dd..75dc5f982d 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -585,5 +585,20 @@ + + + patch.LinkNodeFileExtension + patch.invalidNameEnding.description + 0 + 33 + 34 + + + + + + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index f95eff8cbb..7da60169dc 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=33 +version.schema=34 diff --git a/source/java/org/alfresco/filesys/alfresco/AlfrescoContext.java b/source/java/org/alfresco/filesys/alfresco/AlfrescoContext.java index 0c8895a0e7..a51d047f42 100644 --- a/source/java/org/alfresco/filesys/alfresco/AlfrescoContext.java +++ b/source/java/org/alfresco/filesys/alfresco/AlfrescoContext.java @@ -334,6 +334,16 @@ public abstract class AlfrescoContext extends DiskDeviceContext */ protected abstract IOControlHandler createIOHandler( DiskInterface filesysDriver); + /** + * Set the I/O control handler + * + * @param ioctlHandler IOControlHandler + */ + protected void setIOHandler( IOControlHandler ioctlHandler) + { + m_ioHandler = ioctlHandler; + } + /** * Close the filesystem context */ diff --git a/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java b/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java index aa5a889f2d..0b5d72268d 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java @@ -29,7 +29,6 @@ import java.util.StringTokenizer; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.server.filesys.FileAttribute; import org.alfresco.filesys.server.filesys.FileExistsException; -import org.alfresco.filesys.server.filesys.FileInfo; import org.alfresco.filesys.server.filesys.FileName; import org.alfresco.filesys.util.WildCard; import org.alfresco.model.ContentModel; @@ -175,14 +174,12 @@ public class CifsHelper * @return Returns the existing node reference * @throws FileNotFoundException */ - public FileInfo getFileInformation(NodeRef pathRootNodeRef, String path) throws FileNotFoundException + public ContentFileInfo getFileInformation(NodeRef pathRootNodeRef, String path) throws FileNotFoundException { // get the node being referenced NodeRef nodeRef = getNodeRef(pathRootNodeRef, path); - FileInfo fileInfo = getFileInformation(nodeRef); - - return fileInfo; + return getFileInformation(nodeRef); } /** @@ -196,13 +193,14 @@ public class CifsHelper * @return Returns the file information pertinent to the node * @throws FileNotFoundException if the path refers to a non-existent file */ - public FileInfo getFileInformation(NodeRef nodeRef) throws FileNotFoundException + public ContentFileInfo getFileInformation(NodeRef nodeRef) throws FileNotFoundException { // get the file info org.alfresco.service.cmr.model.FileInfo fileFolderInfo = fileFolderService.getFileInfo(nodeRef); // retrieve required properties and create file info - FileInfo fileInfo = new FileInfo(); + ContentFileInfo fileInfo = new ContentFileInfo(); + fileInfo.setNodeRef(nodeRef); // unset all attribute flags int fileAttributes = 0; @@ -251,6 +249,11 @@ public class CifsHelper fileInfo.setFileAttributes( attr); } + + // Check if it is a link node + + if ( fileFolderInfo.isLink()) + fileInfo.setLinkNodeRef( fileFolderInfo.getLinkNodeRef()); } // created diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java index 3c536420f1..b80b389580 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentContext.java @@ -55,6 +55,10 @@ public class ContentContext extends AlfrescoContext m_rootPath = rootPath; m_rootNodeRef = rootNodeRef; + + // Create the I/O control handler + + setIOHandler( createIOHandler( null)); } /** diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java index 5585c52ac7..439b8429bb 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentDiskDriver.java @@ -26,7 +26,6 @@ import javax.transaction.UserTransaction; import org.alfresco.config.ConfigElement; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; -import org.alfresco.filesys.avm.AVMNetworkFile; import org.alfresco.filesys.server.SrvSession; import org.alfresco.filesys.server.core.DeviceContext; import org.alfresco.filesys.server.core.DeviceContextException; @@ -56,6 +55,7 @@ import org.alfresco.filesys.util.WildCard; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.service.cmr.lock.NodeLockedException; +import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -101,6 +101,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa private SearchService searchService; private ContentService contentService; private PermissionService permissionService; + private FileFolderService fileFolderService; private AuthenticationComponent authComponent; private AuthenticationService authService; @@ -253,6 +254,16 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa { this.authService = authService; } + + /** + * Set the file folder server + * + * @param fileService FileFolderService + */ + public void setFileFolderService(FileFolderService fileService) + { + fileFolderService = fileService; + } /** * Parse and validate the parameter string and create a device context object for this instance @@ -466,6 +477,11 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa context.enableStateTable( true, getStateReaper()); + // Initialize the I/O control handler + + if ( context.hasIOHandler()) + context.getIOHandler().initialize( this, context); + // Return the context for this shared filesystem return context; @@ -1200,9 +1216,53 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa } } - // Create the network file + // Check if the node is a link node - NetworkFile netFile = ContentNetworkFile.createFile(transactionService, nodeService, contentService, cifsHelper, nodeRef, params); + NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION); + NetworkFile netFile = null; + + if ( linkRef == null) + { + // Create the network file + + netFile = ContentNetworkFile.createFile(transactionService, nodeService, contentService, cifsHelper, nodeRef, params); + } + else + { + // Convert the target node to a path, convert to URL format + + String path = getPathForNode( tree, linkRef); + path = path.replace( FileName.DOS_SEPERATOR, '/'); + + // Build the URL file data + + StringBuilder urlStr = new StringBuilder(); + + urlStr.append("[InternetShortcut]\r\n"); + urlStr.append("URL=file://"); + urlStr.append( sess.getServer().getServerName()); + urlStr.append("/"); + urlStr.append( tree.getSharedDevice().getName()); + urlStr.append( path); + urlStr.append("\r\n"); + + // Create the in memory pseudo file for the URL link + + byte[] urlData = urlStr.toString().getBytes(); + + // Get the file information for the link node + + FileInfo fInfo = cifsHelper.getFileInformation( nodeRef); + + // Set the file size to the actual data length + + fInfo.setFileSize( urlData.length); + + // Create the network file using the in-memory file data + + netFile = new LinkMemoryNetworkFile( fInfo.getFileName(), urlData, fInfo, nodeRef); + netFile.setFullName( params.getPath()); + } // Generate a file id for the file @@ -1619,12 +1679,12 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if (file.hasDeleteOnClose()) { - // Check if the file is a content file + // Check if the file is a noderef based file - if ( file instanceof ContentNetworkFile) + if ( file instanceof NodeRefNetworkFile) { - ContentNetworkFile contentNetFile = (ContentNetworkFile) file; - NodeRef nodeRef = contentNetFile.getNodeRef(); + NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file; + NodeRef nodeRef = nodeNetFile.getNodeRef(); // We don't know how long the network file has had the reference, so check for existence @@ -1776,14 +1836,22 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa ContentContext ctx = (ContentContext) tree.getContext(); // Get the file/folder to move + NodeRef nodeToMoveRef = getNodeForPath(tree, oldName); + // Check if the node is a link node + + if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null) + throw new AccessDeniedException("Cannot rename link nodes"); + // Get the new target folder - it must be a folder + String[] splitPaths = FileName.splitPath(newName); NodeRef targetFolderRef = getNodeForPath(tree, splitPaths[0]); - String name = splitPaths[1]; // the new file or folder name + String name = splitPaths[1]; // Update the state table + boolean relinked = false; if ( ctx.hasStateTable()) { @@ -2195,6 +2263,44 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa return cifsHelper.getNodeRef(ctx.getRootNode(), path); } + /** + * Convert a node into a share relative path + * + * @param tree TreeConnection + * @param nodeRef NodeRef + * @return String + * @exception FileNotFoundException + */ + public String getPathForNode( TreeConnection tree, NodeRef nodeRef) + throws FileNotFoundException + { + // Convert the target node to a path + + ContentContext ctx = (ContentContext) tree.getContext(); + List linkPaths = null; + + try { + linkPaths = fileFolderService.getNamePath( ctx.getRootNode(), nodeRef); + } + catch ( org.alfresco.service.cmr.model.FileNotFoundException ex) + { + throw new FileNotFoundException(); + } + + // Build the share relative path to the node + + StringBuilder pathStr = new StringBuilder(); + + for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) { + pathStr.append( FileName.DOS_SEPERATOR); + pathStr.append( fInfo.getName()); + } + + // Return the share relative path + + return pathStr.toString(); + } + /** * Get the file state for the specified path * diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentFileInfo.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentFileInfo.java new file mode 100644 index 0000000000..ba53545d84 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentFileInfo.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo; + +import org.alfresco.filesys.server.filesys.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Content Disk Driver File Info Class + * + *

Adds fields for the file/folder NodeRef, and linked NodeRef for a link node. + * + * @author gkspencer + */ +public class ContentFileInfo extends FileInfo { + + // Version id + + private static final long serialVersionUID = 2518699645372408663L; + + // File/folder node + + private NodeRef m_nodeRef; + + // Linked node + + private NodeRef m_linkRef; + + /** + * Return the file/folder node + * + * @return NodeRef + */ + public final NodeRef getNodeRef() + { + return m_nodeRef; + } + + /** + * Check if this is a link node + * + * @return boolean + */ + public final boolean isLinkNode() + { + return m_linkRef != null ? true : false; + } + + /** + * Return the link node, or null if this is not a link + * + * @return NodeRef + */ + public final NodeRef getLinkNodeRef() + { + return m_linkRef; + } + + /** + * Set the node for this file/folder + * + * @param node NodeRef + */ + public final void setNodeRef(NodeRef node) + { + m_nodeRef = node; + } + + /** + * Set the link node + * + * @param link NodeRef + */ + public final void setLinkNodeRef(NodeRef link) + { + m_linkRef = link; + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java index fd8495cd57..90f1df8089 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentIOControlHandler.java @@ -175,6 +175,20 @@ public class ContentIOControlHandler implements IOControlHandler int devType = NTIOCtl.getDeviceType(ctrlCode); int ioFunc = NTIOCtl.getFunctionCode(ctrlCode); + // Check for I/O controls that require a success status + + if ( devType == NTIOCtl.DeviceFileSystem) + { + // I/O control requests that require a success status + // + // Create or get object id + + if ( ioFunc == NTIOCtl.FsCtlCreateOrGetObjectId) + return null; + } + + // Check if the I/O control looks like a custom I/O control request + if ( devType != NTIOCtl.DeviceFileSystem || dataBuf == null) throw new IOControlNotImplementedException(); @@ -260,7 +274,7 @@ public class ContentIOControlHandler implements IOControlHandler retBuffer = procRunAction(sess, tree, dataBuf, folderNode, netFile); break; - + // Unknown I/O control code default: diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentNetworkFile.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentNetworkFile.java index 1ee735c41d..5a9fee53e6 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentNetworkFile.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentNetworkFile.java @@ -50,14 +50,13 @@ import org.apache.commons.logging.LogFactory; * * @author Derek Hulley */ -public class ContentNetworkFile extends NetworkFile +public class ContentNetworkFile extends NodeRefNetworkFile { private static final Log logger = LogFactory.getLog(ContentNetworkFile.class); private TransactionService transactionService; private NodeService nodeService; private ContentService contentService; - private NodeRef nodeRef; // File channel to file content @@ -175,12 +174,11 @@ public class ContentNetworkFile extends NetworkFile NodeRef nodeRef, String name) { - super(name); + super(name, nodeRef); setFullName(name); this.transactionService = transactionService; this.nodeService = nodeService; this.contentService = contentService; - this.nodeRef = nodeRef; } /** @@ -193,7 +191,7 @@ public class ContentNetworkFile extends NetworkFile StringBuilder str = new StringBuilder(); str.append( "["); - str.append( nodeRef.getId()); + str.append( getNodeRef().getId()); str.append( ",channel="); str.append( channel); if ( channel != null) @@ -205,14 +203,6 @@ public class ContentNetworkFile extends NetworkFile return str.toString(); } - /** - * @return Returns the node reference representing this file - */ - public NodeRef getNodeRef() - { - return nodeRef; - } - /** * @return Returns true if the channel should be writable * @@ -304,7 +294,7 @@ public class ContentNetworkFile extends NetworkFile { // Get a writeable channel to the content - content = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, false); + content = contentService.getWriter( getNodeRef(), ContentModel.PROP_CONTENT, false); // Indicate that we have a writable channel to the file @@ -318,14 +308,14 @@ public class ContentNetworkFile extends NetworkFile { // Get a read-only channel to the content - content = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + content = contentService.getReader( getNodeRef(), ContentModel.PROP_CONTENT); // Ensure that the content we are going to read is valid content = FileContentReader.getSafeContentReader( (ContentReader) content, I18NUtil.getMessage(FileContentReader.MSG_MISSING_CONTENT), - nodeRef, content); + getNodeRef(), content); // Indicate that we only have a read-only channel to the data @@ -372,7 +362,7 @@ public class ContentNetworkFile extends NetworkFile // Update node properties ContentData contentData = content.getContentData(); - nodeService.setProperty(nodeRef, ContentModel.PROP_CONTENT, contentData); + nodeService.setProperty( getNodeRef(), ContentModel.PROP_CONTENT, contentData); } else { diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentSearchContext.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentSearchContext.java index 4a8898dbc2..2dbf99d8ac 100644 --- a/source/java/org/alfresco/filesys/smb/server/repo/ContentSearchContext.java +++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentSearchContext.java @@ -19,6 +19,7 @@ package org.alfresco.filesys.smb.server.repo; import java.io.FileNotFoundException; import java.util.List; +import org.alfresco.filesys.server.filesys.FileAttribute; import org.alfresco.filesys.server.filesys.FileInfo; import org.alfresco.filesys.server.filesys.FileName; import org.alfresco.filesys.server.filesys.SearchContext; @@ -37,8 +38,18 @@ import org.apache.commons.logging.LogFactory; */ public class ContentSearchContext extends SearchContext { + // Debug logging + private static final Log logger = LogFactory.getLog(ContentSearchContext.class); + // Constants + // + // Link file size, actual size will be set if/when the link is opened + + public final static int LinkFileSize = 512; + + // List of nodes returned from the folder search + private CifsHelper cifsHelper; private List results; private int index = -1; @@ -193,7 +204,7 @@ public class ContentSearchContext extends SearchContext { // Get the file information and copy across to the callers file info - FileInfo nextInfo = cifsHelper.getFileInformation(nextNodeRef, ""); + ContentFileInfo nextInfo = cifsHelper.getFileInformation(nextNodeRef, ""); info.copyFrom(nextInfo); // Generate a file id for the current file @@ -203,6 +214,20 @@ public class ContentSearchContext extends SearchContext info.setFileId( pathStr.toString().hashCode()); + // Check if this is a link node + + if ( nextInfo.isLinkNode()) + { + // Set a dummy file size for the link data that will be generated if/when the file is opened + + info.setFileSize( LinkFileSize); + + // Make the link read-only + + if ( info.isReadOnly() == false) + info.setFileAttributes( info.getFileAttributes() + FileAttribute.ReadOnly); + } + // Indicate that the file information is valid return true; diff --git a/source/java/org/alfresco/filesys/smb/server/repo/LinkMemoryNetworkFile.java b/source/java/org/alfresco/filesys/smb/server/repo/LinkMemoryNetworkFile.java new file mode 100644 index 0000000000..2e5d781160 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/LinkMemoryNetworkFile.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.filesys.smb.server.repo; + +import java.io.IOException; + +import org.alfresco.filesys.server.filesys.FileInfo; +import org.alfresco.filesys.smb.SeekType; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Link Node In Memory Network File Class + * + *

In memory network file implementation that uses a memory buffer for the file data. + * + * @author gkspencer + */ +public class LinkMemoryNetworkFile extends NodeRefNetworkFile +{ + // Current file position + + private long m_filePos; + + // File data + + private byte[] m_data; + + /** + * Class constructor. + * + * @param name String + * @param localPath String + * @param finfo FileInfo + * @param nodeRef NodeRef + */ + public LinkMemoryNetworkFile(String name, byte[] data, FileInfo finfo, NodeRef nodeRef) + { + super( name, nodeRef); + + // Set the file data + + m_data = data; + if ( m_data == null) + m_data = new byte[0]; + + // Set the file size + + setFileSize( m_data.length); + + // Set the creation and modification date/times + + setModifyDate( finfo.getModifyDateTime()); + setCreationDate( finfo.getCreationDateTime()); + + // Set the file id and relative path + + if ( finfo.getPath() != null) + { + setFileId( finfo.getPath().hashCode()); + setFullName( finfo.getPath()); + } + } + + /** + * Close the network file. + */ + public void closeFile() throws java.io.IOException + { + // Nothing to do + } + + /** + * Return the current file position. + * + * @return long + */ + public long currentPosition() + { + return m_filePos; + } + + /** + * Flush the file. + * + * @exception IOException + */ + public void flushFile() throws IOException + { + // Nothing to do + } + + /** + * Determine if the end of file has been reached. + * + * @return boolean + */ + public boolean isEndOfFile() throws java.io.IOException + { + // Check if we reached end of file + + if ( m_filePos == m_data.length) + return true; + return false; + } + + /** + * Open the file. + * + * @param createFlag boolean + * @exception IOException + */ + public void openFile(boolean createFlag) throws java.io.IOException + { + // Indicate that the file is open + + setClosed(false); + } + + /** + * Read from the file. + * + * @param buf byte[] + * @param len int + * @param pos int + * @param fileOff long + * @return Length of data read. + * @exception IOException + */ + public int readFile(byte[] buf, int len, int pos, long fileOff) throws java.io.IOException + { + // Check if the read is within the file data range + + long fileLen = (long) m_data.length; + + if ( fileOff >= fileLen) + return 0; + + // Calculate the actual read length + + if (( fileOff + len) > fileLen) + len = (int) ( fileLen - fileOff); + + // Copy the data to the user buffer + + System.arraycopy( m_data, (int) fileOff, buf, pos, len); + + // Update the current file position + + m_filePos = fileOff + len; + + // Return the actual length of data read + + return len; + } + + /** + * Seek to the specified file position. + * + * @param pos long + * @param typ int + * @return long + * @exception IOException + */ + public long seekFile(long pos, int typ) throws IOException + { + // Seek to the required file position + + switch (typ) + { + // From start of file + + case SeekType.StartOfFile: + if (currentPosition() != pos) + m_filePos = pos; + break; + + // From current position + + case SeekType.CurrentPos: + m_filePos += pos; + break; + + // From end of file + + case SeekType.EndOfFile: + m_filePos += pos; + if ( m_filePos < 0) + m_filePos = 0L; + break; + } + + // Return the new file position + + return currentPosition(); + } + + /** + * Truncate the file + * + * @param siz long + * @exception IOException + */ + public void truncateFile(long siz) throws IOException + { + // Allow the truncate, do not alter the pseduo file data + } + + /** + * Write a block of data to the file. + * + * @param buf byte[] + * @param len int + * @exception IOException + */ + public void writeFile(byte[] buf, int len, int pos) throws java.io.IOException + { + // Allow the write, just do not do anything + } + + /** + * Write a block of data to the file. + * + * @param buf byte[] + * @param len int + * @param pos int + * @param offset long + * @exception IOException + */ + public void writeFile(byte[] buf, int len, int pos, long offset) throws java.io.IOException + { + // Allow the write, just do not do anything + } +} diff --git a/source/java/org/alfresco/filesys/smb/server/repo/NodeRefNetworkFile.java b/source/java/org/alfresco/filesys/smb/server/repo/NodeRefNetworkFile.java new file mode 100644 index 0000000000..f9995494da --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/repo/NodeRefNetworkFile.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.smb.server.repo; + +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * NodeRef Based Network File Class + * + * @author gkspencer + */ +public abstract class NodeRefNetworkFile extends NetworkFile { + + // Associated node ref + + protected NodeRef m_nodeRef; + + /** + * Create a network file object with the specified file/directory name. + * + * @param name File name string. + */ + public NodeRefNetworkFile(String name) + { + super( name); + } + + /** + * Create a network file object with the specified file/directory name. + * + * @param name File name string. + * @param node NodeRef + */ + public NodeRefNetworkFile(String name, NodeRef node) + { + super( name); + + m_nodeRef = node; + } + + /** + * Return the node ref + * + * @return NodeRef + */ + public NodeRef getNodeRef() + { + return m_nodeRef; + } + + /** + * set the node ref + * + * @param nodeRef NodeRef + */ + public void setNodeRef( NodeRef nodeRef) + { + m_nodeRef = nodeRef; + } +} diff --git a/source/java/org/alfresco/repo/admin/patch/impl/LinkNodeFileExtensionPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/LinkNodeFileExtensionPatch.java new file mode 100644 index 0000000000..914242bfcf --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/LinkNodeFileExtensionPatch.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.admin.patch.impl; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Date; +import java.util.List; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.domain.hibernate.NodeImpl; +import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.service.cmr.admin.PatchException; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +/** + * Checks that all names do not end with ' ' or '.' + * + * @author David Caruana + */ +public class LinkNodeFileExtensionPatch extends AbstractPatch +{ + private static final String MSG_SUCCESS = "patch.linkNodeExtension.result"; + private static final String MSG_REWRITTEN = "patch.linkNodeExtension.rewritten"; + private static final String ERR_UNABLE_TO_FIX = "patch.linkNodeExtension.err.unable_to_fix"; + + private SessionFactory sessionFactory; + private NodeDaoService nodeDaoService; + + /** + * Default constructor + * + */ + public LinkNodeFileExtensionPatch() + { + } + + /** + * Set the session factory + * + * @param sessionFactory SessionFactory + */ + public void setSessionFactory(SessionFactory sessionFactory) + { + this.sessionFactory = sessionFactory; + } + + /** + * @param nodeDaoService The service that generates the CRC values + */ + public void setNodeDaoService(NodeDaoService nodeDaoService) + { + this.nodeDaoService = nodeDaoService; + } + + @Override + protected void checkProperties() + { + super.checkProperties(); + checkPropertyNotNull(sessionFactory, "sessionFactory"); + checkPropertyNotNull(nodeDaoService, "nodeDaoService"); + } + + @Override + protected String applyInternal() throws Exception + { + // Initialise the helper + + HibernateHelper helper = new HibernateHelper(); + helper.setSessionFactory(sessionFactory); + + try + { + // Fix the link node file names + + return helper.fixNames(); + } + finally + { + helper.closeWriter(); + } + } + + private class HibernateHelper extends HibernateDaoSupport + { + private File logFile; + private FileChannel channel; + + private HibernateHelper() throws IOException + { + // Open a log file + + logFile = new File("./LinkNodeExtensionPatch.log"); + RandomAccessFile outputFile = new RandomAccessFile(logFile, "rw"); + channel = outputFile.getChannel(); + + // Append to the end of the file + + channel.position(channel.size()); + + writeLine("").writeLine(""); + writeLine("LinkNodeExtensionPatch executing on " + new Date()); + } + + private HibernateHelper write(Object obj) throws IOException + { + channel.write(ByteBuffer.wrap(obj.toString().getBytes())); + return this; + } + private HibernateHelper writeLine(Object obj) throws IOException + { + write(obj); + write("\n"); + return this; + } + private void closeWriter() + { + try { channel.close(); } catch (Throwable e) {} + } + + public String fixNames() throws Exception + { + // Get the list of nodes to be updated + + @SuppressWarnings("unused") + List nodes = getInvalidNames(); + + int updated = 0; + for (NodeImpl node : nodes) + { + // Check that the node is a link node + + NodeRef nodeRef = node.getNodeRef(); + + if ( nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION) != null) + { + // Get the current file name + + String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + if (name != null && name.length() >= 4 && name.endsWith(".lnk")) + { + // Update the name string, replace '.lnk' with '.url' + + String updatedName = name.substring(0, name.length() - 4) + ".url"; + + int idx = 0; + boolean applied = false; + while (!applied) + { + try + { + nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, updatedName); + applied = true; + } + catch(DuplicateChildNodeNameException e) + { + idx++; + if (idx > 10) + { + writeLine(I18NUtil.getMessage(ERR_UNABLE_TO_FIX, name, updatedName)); + throw new PatchException(ERR_UNABLE_TO_FIX, logFile); + } + updatedName += "_" + idx; + } + } + writeLine(I18NUtil.getMessage(MSG_REWRITTEN, name ,updatedName)); + updated++; + getSession().flush(); + getSession().clear(); + } + } + } + + String msg = I18NUtil.getMessage(MSG_SUCCESS, updated, logFile); + return msg; + } + + @SuppressWarnings("unchecked") + private List getInvalidNames() + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session + .createQuery( + "select node from org.alfresco.repo.domain.hibernate.NodeImpl as node " + + "join node.properties prop where " + + " prop.stringValue like '%.lnk' "); + return query.list(); + } + }; + List results = (List) getHibernateTemplate().execute(callback); + return results; + } + + } +} diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index 950571e8c6..ebdc0a40c4 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -68,7 +68,8 @@ public class FileFolderServiceImpl implements FileFolderService "./*" + "[like(@cm:name, $cm:name, false)" + " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + - " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "'))]"; + " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + + " and (subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; /** Shallow search for all files and folders */ private static final String LUCENE_QUERY_SHALLOW_ALL = @@ -77,6 +78,7 @@ public class FileFolderServiceImpl implements FileFolderService "+(" + "TYPE:\"" + ContentModel.TYPE_CONTENT + "\" " + "TYPE:\"" + ContentModel.TYPE_FOLDER + "\" " + + "TYPE:\"" + ContentModel.TYPE_LINK + "\" " + ")"; /** Shallow search for all files and folders */ @@ -96,7 +98,8 @@ public class FileFolderServiceImpl implements FileFolderService ".//*" + "[like(@cm:name, $cm:name, false)" + " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + - " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "'))]"; + " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + + " and (subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; /** empty parameters */ private static final QueryParameterDefinition[] PARAMS_ANY_NAME = new QueryParameterDefinition[1]; @@ -246,7 +249,8 @@ public class FileFolderServiceImpl implements FileFolderService } return true; } - else if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_CONTENT)) + else if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_CONTENT) || + dictionaryService.isSubClass(typeQName, ContentModel.TYPE_LINK)) { // it is a regular file return false; diff --git a/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java index ca7f70f575..6abf23bb4a 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileInfoImpl.java @@ -35,7 +35,9 @@ import org.alfresco.service.namespace.QName; public class FileInfoImpl implements FileInfo { private NodeRef nodeRef; + private NodeRef linkNodeRef; private boolean isFolder; + private boolean isLink; private Map properties; /** @@ -46,6 +48,14 @@ public class FileInfoImpl implements FileInfo this.nodeRef = nodeRef; this.isFolder = isFolder; this.properties = properties; + + // Check if this is a link node + + if ( properties.containsKey( ContentModel.PROP_LINK_DESTINATION)) + { + isLink = true; + linkNodeRef = (NodeRef) properties.get( ContentModel.PROP_LINK_DESTINATION); + } } @Override @@ -55,8 +65,15 @@ public class FileInfoImpl implements FileInfo sb.append("FileInfo") .append("[name=").append(getName()) .append(", isFolder=").append(isFolder) - .append(", nodeRef=").append(nodeRef) - .append("]"); + .append(", nodeRef=").append(nodeRef); + + if ( isLink()) + { + sb.append(", linkref="); + sb.append(linkNodeRef); + } + + sb.append("]"); return sb.toString(); } @@ -70,6 +87,16 @@ public class FileInfoImpl implements FileInfo return isFolder; } + public boolean isLink() + { + return isLink; + } + + public NodeRef getLinkNodeRef() + { + return linkNodeRef; + } + public String getName() { return (String) properties.get(ContentModel.PROP_NAME); diff --git a/source/java/org/alfresco/service/cmr/model/FileInfo.java b/source/java/org/alfresco/service/cmr/model/FileInfo.java index f903b82596..7517dddcf7 100644 --- a/source/java/org/alfresco/service/cmr/model/FileInfo.java +++ b/source/java/org/alfresco/service/cmr/model/FileInfo.java @@ -43,6 +43,16 @@ public interface FileInfo */ public boolean isFolder(); + /** + * @return true if this instance represents a link to a node + */ + public boolean isLink(); + + /** + * @return Return the reference to the node that this node is linked to + */ + public NodeRef getLinkNodeRef(); + /** * @return Returns the name of the file or folder within the parent folder */