diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml index 0cfee86d48..06bb7fc42c 100644 --- a/config/alfresco/authority-services-context.xml +++ b/config/alfresco/authority-services-context.xml @@ -7,12 +7,12 @@ - + + - - - + + @@ -22,6 +22,12 @@ + + + + + + @@ -42,4 +48,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/desktop/Alfresco.exe b/config/alfresco/desktop/Alfresco.exe new file mode 100644 index 0000000000..1afb081373 Binary files /dev/null and b/config/alfresco/desktop/Alfresco.exe differ diff --git a/config/alfresco/extension/chaining-authentication-context.xml.sample b/config/alfresco/extension/chaining-authentication-context.xml.sample new file mode 100644 index 0000000000..f73a4a09b1 --- /dev/null +++ b/config/alfresco/extension/chaining-authentication-context.xml.sample @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${user.name.caseSensitive} + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + COMPANY.COM + + + Alfresco + + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/jaas-authentication-context.xml.sample b/config/alfresco/extension/jaas-authentication-context.xml.sample new file mode 100644 index 0000000000..75dfe6b25d --- /dev/null +++ b/config/alfresco/extension/jaas-authentication-context.xml.sample @@ -0,0 +1,38 @@ + + + + + + + + + + + DEFAULT.REALM + + + Alfresco + + + + + + + org.alfresco.repo.security.authentication.MutableAuthenticationDao + + + + + + + + + + + ${server.transaction.mode.default} + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/ldap-authentication-context.xml.sample b/config/alfresco/extension/ldap-authentication-context.xml.sample new file mode 100644 index 0000000000..f50725c1a9 --- /dev/null +++ b/config/alfresco/extension/ldap-authentication-context.xml.sample @@ -0,0 +1,442 @@ + + + + + + + + + + org.alfresco.repo.security.authentication.MutableAuthenticationDao + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + + + + + + + + %s + + + + + + + + + + + com.sun.jndi.ldap.LdapCtxFactory + + + + + + + ldap://openldap.domain.com:389 + + + + + + + + DIGEST-MD5 + + + + + + reader + + + + + secret + + + + + + + + + + + + + + + + (objectclass=inetOrgPerson) + + + + + dc=alfresco,dc=org + + + + + uid + + + + + + + + + + + + + + + + /app:company_home + + + + + + + + uid + + + + + givenName + + + + + sn + + + + + mail + + + + + o + + + + + + + + + + + (objectclass=groupOfNames) + + + + + dc=alfresco,dc=org + + + + + uid + + + + + cn + + + + + groupOfNames + + + + + inetOrgPerson + + + + + + + + + + + member + + + + + + + + + + + + + + + + + org.alfresco.repo.importer.ImporterJob + + + + + + + + + + + + + 30000 + + + + 3600000 + + + + + + + + + org.alfresco.repo.importer.ImporterJob + + + + + + + + + + + + + 30000 + + + + 3600000 + + + + + + + + + + + + + + + + + + + + + + + ${spaces.store} + + + + + /${system.system_container.childname}/${system.people_container.childname} + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${alfresco_user_store.store} + + + + + /${alfresco_user_store.system_container.childname}/${alfresco_user_store.authorities_container.childname} + + + + + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/extension/ntlm-authentication-context.xml.sample b/config/alfresco/extension/ntlm-authentication-context.xml.sample new file mode 100644 index 0000000000..25967eb1fa --- /dev/null +++ b/config/alfresco/extension/ntlm-authentication-context.xml.sample @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + false + + + + \ No newline at end of file diff --git a/config/alfresco/extension/replicating-content-services-context.xml.sample b/config/alfresco/extension/replicating-content-services-context.xml.sample new file mode 100644 index 0000000000..94ed1270cb --- /dev/null +++ b/config/alfresco/extension/replicating-content-services-context.xml.sample @@ -0,0 +1,71 @@ + + + + + + + + + s:/backups/alfresco + + + + + + + fileContentStore + + + + backupContentStore + + + + true + + + + 60 + + + + + + + + + + + + + + + + + + false + + + + false + + + + + + + + + + + + diff --git a/config/alfresco/index-recovery-context.xml b/config/alfresco/index-recovery-context.xml index 4d30ed0933..bad12938ae 100644 --- a/config/alfresco/index-recovery-context.xml +++ b/config/alfresco/index-recovery-context.xml @@ -2,23 +2,49 @@ - - - + + + + + + + + + + + workspace://SpacesStore + workspace://lightWeightVersionStore + user://alfrescoUserStore - + + + + + false + + + false + + + 1000 + + + NORMAL + + + \ No newline at end of file diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index d237e78d46..783db30183 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -7,7 +7,7 @@ version.major=1 version.minor=3 version.revision=0 -version.label= +version.label=dev # Edition label diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.aps b/source/cpp/CAlfrescoApp/CAlfrescoApp.aps new file mode 100644 index 0000000000..8e5c213a85 Binary files /dev/null and b/source/cpp/CAlfrescoApp/CAlfrescoApp.aps differ diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp b/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp new file mode 100644 index 0000000000..f39d06e0e4 --- /dev/null +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ + +#include "stdafx.h" +#include "CAlfrescoApp.h" +#include "CAlfrescoAppDlg.h" +#include "FileStatusDialog.h" + +#include + +#include "util\String.h" +#include "util\DataBuffer.h" +#include "util\FileName.h" + +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +using namespace std; +using namespace Alfresco; + +// CCAlfrescoAppApp + +BEGIN_MESSAGE_MAP(CCAlfrescoAppApp, CWinApp) + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + + +// CCAlfrescoAppApp construction + +CCAlfrescoAppApp::CCAlfrescoAppApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + + +// The one and only CCAlfrescoAppApp object + +CCAlfrescoAppApp theApp; + + +// CCAlfrescoAppApp initialization + +BOOL CCAlfrescoAppApp::InitInstance() +{ + // InitCommonControls() is required on Windows XP if an application + // manifest specifies use of ComCtl32.dll version 6 or later to enable + // visual styles. Otherwise, any window creation will fail. + + InitCommonControls(); + CWinApp::InitInstance(); + AfxEnableControlContainer(); + + // Get the application path + + String appPath = __wargv[0]; + + int pos = appPath.lastIndexOf(PathSeperator); + + if ( pos < 0) { + AfxMessageBox( L"Invalid application path", MB_OK | MB_ICONSTOP); + return 1; + } + + // Get the path to the folder containing the application + + String folderPath = appPath.substring(0, pos); + + // Create the Alfresco interface + + AlfrescoInterface alfresco(folderPath); + if ( alfresco.isAlfrescoFolder()) { + + try { + + // If there are no file paths on the command line then display a status page for the files + // in the Alfresco folder + + if ( __argc == 1) { + + // Display status for the files in the Alfresco folder + + doFolderStatus( alfresco); + } + else { + + // Build a list of the file names + + StringList fileList; + + for ( int i = 1; i < __argc; i++) + fileList.addString( String(__wargv[i])); + + // Process the file list and check in or out each file + + doCheckInOut( alfresco, fileList); + } + } + catch (Exception ex) { + CString msg; + msg.FormatMessage( L"Exception occurred\n\n%1", ex.getMessage().data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + } + } + else { + AfxMessageBox( L"Not a valid Alfresco CIFS folder", MB_OK | MB_ICONSTOP); + return 1; + } + + // Run the main dialog +/** + CCAlfrescoAppDlg dlg; + m_pMainWnd = &dlg; + INT_PTR nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } +**/ + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} + +/** + * Display file status of the files in the target Alfresco folder + * + * @param AlfrescoInterface& alfresco + * @param const wchar_t* fileSpec + * @return bool + */ +bool CCAlfrescoAppApp::doFolderStatus( AlfrescoInterface& alfresco, const wchar_t* fileSpec) { + + // Get the base UNC path + + String uncPath = alfresco.getUNCPath(); + uncPath.append(PathSeperator); + + // Search the Alfresco folder + + WIN32_FIND_DATA findData; + String searchPath = uncPath; + searchPath.append( fileSpec); + + bool sts = false; + HANDLE fHandle = FindFirstFile( searchPath, &findData); + AlfrescoFileInfoList fileList; + + if ( fHandle != INVALID_HANDLE_VALUE) { + + // Loop until all files have been returned + + PTR_AlfrescoFileInfo pFileInfo; + sts = true; + + while ( fHandle != INVALID_HANDLE_VALUE) { + + // Get the file name, ignore the '.' and '..' files + + String fName = findData.cFileName; + + if ( fName.equals(L".") || fName.equals(L"..")) { + + // Get the next file/folder name in the search + + if ( FindNextFile( fHandle, &findData) == 0) + fHandle = INVALID_HANDLE_VALUE; + continue; + } + + // Get the file information for the current file folder + + pFileInfo = alfresco.getFileInformation( findData.cFileName); + + if ( pFileInfo.get() != NULL) { + + // Add the file to the list + + fileList.addInfo( pFileInfo); + } + + // Get the next file/folder name in the search + + if ( FindNextFile( fHandle, &findData) == 0) + fHandle = INVALID_HANDLE_VALUE; + } + } + + // Display the file status dialog if there are files to display + + if ( fileList.size() > 0) { + + // Display the file status dialog + + CFileStatusDialog dlg( fileList); + dlg.DoModal(); + } + else { + CString msg; + msg.FormatMessage( L"No files found in %1", uncPath.data()); + AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + } + + // Return status + + return sts; +} + +/** + * Process the list of files and check in or out each file + * + * @param alfresco AlfrescoInterface& + * @param files StringList& + */ +bool CCAlfrescoAppApp::doCheckInOut( AlfrescoInterface& alfresco, StringList& files) { + + // Process the list of files and either check in the file if it is a working copy or check out + // the file + + for ( unsigned int i = 0; i < files.numberOfStrings(); i++) { + + // Get the current file name + + String curFile = files.getStringAt( i); + + // Check if the path is on an Alfresco mapped drive + + if ( alfresco.isMappedDrive() && curFile.startsWithIgnoreCase( alfresco.getDrivePath())) { + + // Convert the path to a UNC path + + String uncPath = alfresco.getRootPath(); + uncPath.append( curFile.substring(2)); + + curFile = uncPath; + } + + // Check that the path is to a file + + bool copyFile = false; + + DWORD attr = GetFileAttributes( curFile); + if ( attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + + // Get the file name from the path + + StringList nameParts = FileName::splitPath( curFile); + String curName = nameParts.getStringAt( 1); + + // Get the Alfresco file status information + + PTR_AlfrescoFileInfo pFileInfo = alfresco.getFileInformation( curName); + + // If the path is to a file that is not on the Alfresco share the file will need to be copied, + // after checking the status of a matching file in the Alfresco folder + + if ( curFile.length() >= 3 && curFile.substring(1,3).equals( L":\\")) { + + // Check if there is an existing file with the same name + + if ( pFileInfo.get() != NULL) { + + // Check if the file is a working copy + + if ( pFileInfo->isWorkingCopy()) { + + // Local file matches a working copy file in the Alfresco folder + + CString msg; + msg.FormatMessage( L"Found matching working copy for local file %1", curName.data()); + AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + } + else if ( pFileInfo->getLockType() != LockNone) { + + // File is locked, may be the original document + + CString msg; + msg.FormatMessage( L"Destination file %1 is locked", curName.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + return false; + } + else { + + // Indicate that we have copied a new file to the Alfresco share, do not check in/out + + copyFile = true; + } + } + else { + + // Indicate that we have copied a new file to the Alfresco share, do not check in/out + + copyFile = true; + } + + // Build the from/to paths, must be double null terminated + + wchar_t fromPath[MAX_PATH + 1]; + wchar_t toPath[MAX_PATH + 1]; + + memset( fromPath, 0, sizeof( fromPath)); + memset( toPath, 0, sizeof( toPath)); + + wcscpy( fromPath, curFile.data()); + wcscpy( toPath, alfresco.getUNCPath()); + + // Copy the local file to the Alfresco folder + + SHFILEOPSTRUCT fileOpStruct; + memset( &fileOpStruct, 0, sizeof(SHFILEOPSTRUCT)); + + fileOpStruct.hwnd = HWND_DESKTOP; + fileOpStruct.wFunc = FO_COPY; + fileOpStruct.pFrom = fromPath; + fileOpStruct.pTo = toPath; + fileOpStruct.fFlags= 0; + fileOpStruct.fAnyOperationsAborted =false; + + // Copy the file to the Alfresco folder + + if ( SHFileOperation( &fileOpStruct) != 0) { + + // File copy failed + + CString msg; + msg.FormatMessage( L"Failed to copy file %1", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + return false; + } + else if ( fileOpStruct.fAnyOperationsAborted) { + + // User aborted the file copy + + CString msg; + msg.FormatMessage( L"Copy aborted for %1", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + return false; + } + + // Get the file information for the copied file + + pFileInfo = alfresco.getFileInformation( curName); + } + + // Check in or check out the file + + if ( pFileInfo.get() != NULL) { + + // Check if the file should be checked in/out + + if ( copyFile == false) { + + // Check if the file is a working copy, if so then check it in + + if ( pFileInfo->isWorkingCopy()) { + + // Check in the file + + doCheckIn( alfresco, pFileInfo); + } + else if ( pFileInfo->getLockType() == LockNone) { + + // Check out the file + + doCheckOut( alfresco, pFileInfo); + } + else { + + // File is locked, may already be checked out + + CString msg; + msg.FormatMessage( L"File %1 is locked", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + } + } + else { + + // No existing file to link the copied file to + + CString msg; + msg.FormatMessage( L"Copied file %1 to Alfresco folder", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + } + + } + else { + CString msg; + msg.FormatMessage( L"Failed to get file status for %1", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + } + } + else { + + // Check the error status + + CString msg; + + if ( attr != INVALID_FILE_ATTRIBUTES) + msg.FormatMessage( L"Path %1 is a folder, ignored", curFile.data()); + else + msg.FormatMessage( L"File %1 does not exist", curFile.data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + } + } + + // Return status + + return true; +} + +/** + * Check in the specified file + * + * @param alfresco AlfrescoInterface& + * @param pFileInfo PTR_AlfrescoFileInfo& + * @return bool + */ +bool CCAlfrescoAppApp::doCheckIn( AlfrescoInterface& alfresco, PTR_AlfrescoFileInfo& pFileInfo) { + + bool checkedIn = false; + + try { + + // Check in the specified file + + alfresco.checkIn( pFileInfo->getName()); + + CString msg; + msg.FormatMessage( L"Checked in file %1", pFileInfo->getName().data()); + AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + + // Indicate that the check in was successful + + checkedIn = true; + } + catch (Exception ex) { + CString msg; + msg.FormatMessage( L"Error checking in file %1\n\n%2", pFileInfo->getName().data(), ex.getMessage().data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + } + + // Return the check in status + + return checkedIn; +} + +/** + * Check out the specified file + * + * @param alfresco AlfrescoInterface& + * @param pFileInfo PTR_AlfrescoFileInfo& + * @return bool + */ +bool CCAlfrescoAppApp::doCheckOut( AlfrescoInterface& alfresco, PTR_AlfrescoFileInfo& pFileInfo) { + + bool checkedOut = false; + + try { + + // Check out the specified file + + String workingCopy; + alfresco.checkOut( pFileInfo->getName(), workingCopy); + + CString msg; + msg.FormatMessage( L"Checked out file %1 to %2", pFileInfo->getName().data(), workingCopy.data()); + AfxMessageBox( msg, MB_OK | MB_ICONINFORMATION); + + // Indicate that the check out was successful + + checkedOut = true; + } + catch (Exception ex) { + CString msg; + msg.FormatMessage( L"Error checking out file %1\n\n%2", pFileInfo->getName().data(), ex.getMessage().data()); + AfxMessageBox( msg, MB_OK | MB_ICONSTOP); + } + + // Return the check out status + + return checkedOut; +} diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.h b/source/cpp/CAlfrescoApp/CAlfrescoApp.h new file mode 100644 index 0000000000..c618a04b84 --- /dev/null +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.h @@ -0,0 +1,65 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#pragma once + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +// Includes + +#include "alfresco\Alfresco.hpp" + +using namespace Alfresco; + +// CCAlfrescoAppApp: +// See CAlfrescoApp.cpp for the implementation of this class +// + +class CCAlfrescoAppApp : public CWinApp +{ +public: + CCAlfrescoAppApp(); + +// Overrides + public: + virtual BOOL InitInstance(); + +// Implementation + + DECLARE_MESSAGE_MAP() + +private: + // Main Alfresco interface functions + + bool doFolderStatus( AlfrescoInterface& alfresco, const wchar_t* fileSpec = L"*.*"); + bool doCheckInOut( AlfrescoInterface& alfresco, StringList& files); + bool doCheckIn( AlfrescoInterface& alfresco, PTR_AlfrescoFileInfo& fileInfo); + bool doCheckOut( AlfrescoInterface& alfresco, PTR_AlfrescoFileInfo& fileInfo); +}; + +extern CCAlfrescoAppApp theApp; \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.htm b/source/cpp/CAlfrescoApp/CAlfrescoApp.htm new file mode 100644 index 0000000000..237a093a41 --- /dev/null +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.htm @@ -0,0 +1,19 @@ + + + + + + + + + + +
+
+ +
+TODO: Place controls here. +
+ + + \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.ncb b/source/cpp/CAlfrescoApp/CAlfrescoApp.ncb new file mode 100644 index 0000000000..39edd3a847 Binary files /dev/null and b/source/cpp/CAlfrescoApp/CAlfrescoApp.ncb differ diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.rc b/source/cpp/CAlfrescoApp/CAlfrescoApp.rc new file mode 100644 index 0000000000..b7dde5c0e6 --- /dev/null +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.rc @@ -0,0 +1,256 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOGEX 0, 0, 235, 55 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | + WS_SYSMENU +CAPTION "About CAlfrescoApp" +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + ICON 128,IDC_STATIC,11,17,20,20 + LTEXT "CAlfrescoApp Version 1.0",IDC_STATIC,40,10,119,8, + SS_NOPREFIX + LTEXT "Copyright (C) 2005",IDC_STATIC,40,25,119,8 + DEFPUSHBUTTON "OK",IDOK,178,7,50,16,WS_GROUP +END + +IDD_CALFRESCOAPP_DIALOG DIALOGEX 0, 0, 469, 156 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | + WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Alfresco Check In/Out" +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + PUSHBUTTON "OK",IDOK,209,130,50,13 + LTEXT "Checked in 99 files",IDC_MSGTEXT,25,22,418,8 + LISTBOX IDC_FILELIST,23,38,424,83,LBS_SORT | + LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP +END + + +///////////////////////////////////////////////////////////////////////////// +// +// HTML +// + +IDR_HTML_CALFRESCOAPP_DIALOG HTML "CAlfrescoApp.htm" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "Alfresco" + VALUE "FileDescription", "Alfresco Check In/Out" + VALUE "FileVersion", "1.0.0.1" + VALUE "InternalName", "CAlfrescoApp.exe" + VALUE "LegalCopyright", "(c) Alfresco. All rights reserved." + VALUE "OriginalFilename", "CAlfrescoApp.exe" + VALUE "ProductName", "Alfresco" + VALUE "ProductVersion", "1.0.0.1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 228 + TOPMARGIN, 7 + BOTTOMMARGIN, 48 + END + + IDD_CALFRESCOAPP_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 462 + TOPMARGIN, 7 + BOTTOMMARGIN, 149 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_ABOUTBOX "&About CAlfrescoApp..." +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (U.K.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#include ""res\\CAlfrescoApp.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "alfresco.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_FILESTATUS DIALOGEX 0, 0, 448, 332 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | + WS_SYSMENU +CAPTION "Alfresco File Status" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,391,311,50,14 + CONTROL "",IDC_FILELIST,"SysListView32",LVS_REPORT | + LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,434,299 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_FILESTATUS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 441 + TOPMARGIN, 7 + BOTTOMMARGIN, 325 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.K.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 +#pragma code_page(1252) +#include "res\CAlfrescoApp.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.sln b/source/cpp/CAlfrescoApp/CAlfrescoApp.sln new file mode 100644 index 0000000000..655f0d3159 --- /dev/null +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CAlfrescoApp", "CAlfrescoApp.vcproj", "{055DCC85-2D1A-4594-B2BE-ED292D2BF26D}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Debug.ActiveCfg = Debug|Win32 + {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Debug.Build.0 = Debug|Win32 + {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Release.ActiveCfg = Release|Win32 + {055DCC85-2D1A-4594-B2BE-ED292D2BF26D}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.suo b/source/cpp/CAlfrescoApp/CAlfrescoApp.suo new file mode 100644 index 0000000000..75201f26f0 Binary files /dev/null and b/source/cpp/CAlfrescoApp/CAlfrescoApp.suo differ diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj b/source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj new file mode 100644 index 0000000000..371ce42ead --- /dev/null +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/cpp/CAlfrescoApp/CAlfrescoAppDlg.cpp b/source/cpp/CAlfrescoApp/CAlfrescoAppDlg.cpp new file mode 100644 index 0000000000..8843b0bfa4 --- /dev/null +++ b/source/cpp/CAlfrescoApp/CAlfrescoAppDlg.cpp @@ -0,0 +1,164 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "stdafx.h" +#include "CAlfrescoApp.h" +#include "CAlfrescoAppDlg.h" +#include ".\calfrescoappdlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + + +// CAboutDlg dialog used for App About + +class CAboutDlg : public CDialog +{ +public: + CAboutDlg(); + +// Dialog Data + enum { IDD = IDD_ABOUTBOX }; + + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + +// Implementation +protected: + DECLARE_MESSAGE_MAP() +}; + +CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) +{ +} + +void CAboutDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); +} + +BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) +END_MESSAGE_MAP() + + +CCAlfrescoAppDlg::CCAlfrescoAppDlg(CWnd* pParent /*=NULL*/) + : CDialog( CCAlfrescoAppDlg::IDD, pParent) +{ + m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON1); +} + +void CCAlfrescoAppDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); +} + +BEGIN_MESSAGE_MAP(CCAlfrescoAppDlg, CDialog) + ON_WM_SYSCOMMAND() + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +// CCAlfrescoAppDlg message handlers + +BOOL CCAlfrescoAppDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Add "About..." menu item to system menu. + + // IDM_ABOUTBOX must be in the system command range. + ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); + ASSERT(IDM_ABOUTBOX < 0xF000); + + CMenu* pSysMenu = GetSystemMenu(FALSE); + if (pSysMenu != NULL) + { + CString strAboutMenu; + strAboutMenu.LoadString(IDS_ABOUTBOX); + if (!strAboutMenu.IsEmpty()) + { + pSysMenu->AppendMenu(MF_SEPARATOR); + pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); + } + } + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // TODO: Add extra initialization here + + return TRUE; // return TRUE unless you set the focus to a control +} + +void CCAlfrescoAppDlg::OnSysCommand(UINT nID, LPARAM lParam) +{ + if ((nID & 0xFFF0) == IDM_ABOUTBOX) + { + CAboutDlg dlgAbout; + dlgAbout.DoModal(); + } + else + { + CDialog::OnSysCommand(nID, lParam); + } +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CCAlfrescoAppDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +// The system calls this function to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CCAlfrescoAppDlg::OnQueryDragIcon() +{ + return static_cast(m_hIcon); +} diff --git a/source/cpp/CAlfrescoApp/CAlfrescoAppDlg.h b/source/cpp/CAlfrescoApp/CAlfrescoAppDlg.h new file mode 100644 index 0000000000..9fb2082e16 --- /dev/null +++ b/source/cpp/CAlfrescoApp/CAlfrescoAppDlg.h @@ -0,0 +1,51 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#pragma once + +// CCAlfrescoAppDlg dialog +class CCAlfrescoAppDlg : public CDialog +{ +// Construction +public: + CCAlfrescoAppDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + enum { IDD = IDD_CALFRESCOAPP_DIALOG }; + + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + +// Implementation +protected: + HICON m_hIcon; + + // Generated message map functions + virtual BOOL OnInitDialog(); + afx_msg void OnSysCommand(UINT nID, LPARAM lParam); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + DECLARE_MESSAGE_MAP() +public: +}; diff --git a/source/cpp/CAlfrescoApp/FileStatusDialog.cpp b/source/cpp/CAlfrescoApp/FileStatusDialog.cpp new file mode 100644 index 0000000000..152884e714 --- /dev/null +++ b/source/cpp/CAlfrescoApp/FileStatusDialog.cpp @@ -0,0 +1,118 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "stdafx.h" +#include "CAlfrescoApp.h" +#include "FileStatusDialog.h" + +#include "util\Long.h" + +// CFileStatusDialog dialog + +IMPLEMENT_DYNAMIC(CFileStatusDialog, CDialog) +CFileStatusDialog::CFileStatusDialog(AlfrescoFileInfoList& fileList, CWnd* pParent /*=NULL*/) + : CDialog(CFileStatusDialog::IDD, pParent), + m_fileList( fileList) +{ +} + +CFileStatusDialog::~CFileStatusDialog() +{ +} + +void CFileStatusDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_FILELIST, m_listCtrl); +} + + +BEGIN_MESSAGE_MAP(CFileStatusDialog, CDialog) +END_MESSAGE_MAP() + +/** + * Initialize the dialog + */ +BOOL CFileStatusDialog::OnInitDialog() { + + // Call the base class + + CDialog::OnInitDialog(); + + // Add headers to the list control + + m_listCtrl.InsertColumn( 0, L"Name", LVCFMT_LEFT, 200); + m_listCtrl.InsertColumn( 1, L"Mime-type", LVCFMT_LEFT, 140); + m_listCtrl.InsertColumn( 2, L"Size", LVCFMT_RIGHT, 80); + m_listCtrl.InsertColumn( 3, L"Status", LVCFMT_LEFT, 100); + m_listCtrl.InsertColumn( 4, L"Owner", LVCFMT_LEFT, 100); + + // Add the list view data + + for ( unsigned int i = 0; i < m_fileList.size(); i++) { + + // Get the current file information + + const AlfrescoFileInfo* pInfo = m_fileList.getInfoAt( i); + + // Add the item to the list view + + if ( pInfo != NULL) { + + // Insert a new item in the view + + int nIndex = m_listCtrl.InsertItem( 0, pInfo->getName()); + + if ( pInfo->isType() == TypeFile) { + + // Display the mime-type and content length + + m_listCtrl.SetItemText( nIndex, 1, pInfo->getContentType()); + m_listCtrl.SetItemText( nIndex, 2, Long::toString( pInfo->getContentLength())); + + String status; + String owner; + + if ( pInfo->isWorkingCopy()) { + status = L"Work"; + } + else if ( pInfo->getLockType() != LockNone) { + status = L"Locked"; + owner = pInfo->getLockOwner(); + } + + m_listCtrl.SetItemText( nIndex, 3, status); + m_listCtrl.SetItemText( nIndex, 4, owner); + } + } + } + + // Clear the file info list + + m_fileList.clear(); + + return FALSE; +} + +// CFileStatusDialog message handlers diff --git a/source/cpp/CAlfrescoApp/FileStatusDialog.h b/source/cpp/CAlfrescoApp/FileStatusDialog.h new file mode 100644 index 0000000000..6ac3a53ac3 --- /dev/null +++ b/source/cpp/CAlfrescoApp/FileStatusDialog.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#pragma once +#include "afxcmn.h" + +#include "alfresco\Alfresco.hpp" + +// CFileStatusDialog dialog + +class CFileStatusDialog : public CDialog +{ + DECLARE_DYNAMIC(CFileStatusDialog) + +public: + CFileStatusDialog( AlfrescoFileInfoList& fileList, CWnd* pParent = NULL); // standard constructor + virtual ~CFileStatusDialog(); + +// Dialog Data + enum { IDD = IDD_FILESTATUS }; + + // Initialize the dialog + + BOOL OnInitDialog(); + +protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + + DECLARE_MESSAGE_MAP() + CListCtrl m_listCtrl; + +protected: + // File information list + + AlfrescoFileInfoList& m_fileList; +}; diff --git a/source/cpp/CAlfrescoApp/FileStatusView.cpp b/source/cpp/CAlfrescoApp/FileStatusView.cpp new file mode 100644 index 0000000000..2b552204ce --- /dev/null +++ b/source/cpp/CAlfrescoApp/FileStatusView.cpp @@ -0,0 +1,40 @@ +// FileStatusView.cpp : implementation file +// + +#include "stdafx.h" +#include "CAlfrescoApp.h" +#include "FileStatusView.h" + + +// CFileStatusView + +IMPLEMENT_DYNCREATE(CFileStatusView, CListView) + +CFileStatusView::CFileStatusView() +{ +} + +CFileStatusView::~CFileStatusView() +{ +} + +BEGIN_MESSAGE_MAP(CFileStatusView, CListView) +END_MESSAGE_MAP() + + +// CFileStatusView diagnostics + +#ifdef _DEBUG +void CFileStatusView::AssertValid() const +{ + CListView::AssertValid(); +} + +void CFileStatusView::Dump(CDumpContext& dc) const +{ + CListView::Dump(dc); +} +#endif //_DEBUG + + +// CFileStatusView message handlers diff --git a/source/cpp/CAlfrescoApp/FileStatusView.h b/source/cpp/CAlfrescoApp/FileStatusView.h new file mode 100644 index 0000000000..066f367e55 --- /dev/null +++ b/source/cpp/CAlfrescoApp/FileStatusView.h @@ -0,0 +1,24 @@ +#pragma once + + +// CFileStatusView view + +class CFileStatusView : public CListView +{ + DECLARE_DYNCREATE(CFileStatusView) + +protected: + CFileStatusView(); // protected constructor used by dynamic creation + virtual ~CFileStatusView(); + +public: +#ifdef _DEBUG + virtual void AssertValid() const; + virtual void Dump(CDumpContext& dc) const; +#endif + +protected: + DECLARE_MESSAGE_MAP() +}; + + diff --git a/source/cpp/CAlfrescoApp/ReadMe.txt b/source/cpp/CAlfrescoApp/ReadMe.txt new file mode 100644 index 0000000000..247ba918eb --- /dev/null +++ b/source/cpp/CAlfrescoApp/ReadMe.txt @@ -0,0 +1,90 @@ +================================================================================ + MICROSOFT FOUNDATION CLASS LIBRARY : CAlfrescoApp Project Overview +=============================================================================== + +The application wizard has created this CAlfrescoApp application for +you. This application not only demonstrates the basics of using the Microsoft +Foundation Classes but is also a starting point for writing your application. + +This file contains a summary of what you will find in each of the files that +make up your CAlfrescoApp application. + +CAlfrescoApp.vcproj + This is the main project file for VC++ projects generated using an application wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + application wizard. + +CAlfrescoApp.h + This is the main header file for the application. It includes other + project specific headers (including Resource.h) and declares the + CCAlfrescoAppApp application class. + +CAlfrescoApp.cpp + This is the main application source file that contains the application + class CCAlfrescoAppApp. + +CAlfrescoApp.rc + This is a listing of all of the Microsoft Windows resources that the + program uses. It includes the icons, bitmaps, and cursors that are stored + in the RES subdirectory. This file can be directly edited in Microsoft + Visual C++. Your project resources are in 1033. + +res\CAlfrescoApp.ico + This is an icon file, which is used as the application's icon. This + icon is included by the main resource file CAlfrescoApp.rc. + +res\CAlfrescoApp.rc2 + This file contains resources that are not edited by Microsoft + Visual C++. You should place all resources not editable by + the resource editor in this file. + +///////////////////////////////////////////////////////////////////////////// + +The application wizard creates one dialog class: +CAlfrescoAppDlg.h, CAlfrescoAppDlg.cpp - the dialog + These files contain your CCAlfrescoAppDlg class. This class defines + the behavior of your application's main dialog. The dialog's template is + in CAlfrescoApp.rc, which can be edited in Microsoft Visual C++. +///////////////////////////////////////////////////////////////////////////// + +Other Features: + +ActiveX Controls + The application includes support to use ActiveX controls. +///////////////////////////////////////////////////////////////////////////// + +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named CAlfrescoApp.pch and a precompiled types file named StdAfx.obj. + +Resource.h + This is the standard header file, which defines new resource IDs. + Microsoft Visual C++ reads and updates this file. + +CAlfrescoApp.manifest + Application manifest files are used by Windows XP to describe an applications + dependency on specific versions of Side-by-Side assemblies. The loader uses this + information to load the appropriate assembly from the assembly cache or private + from the application. The Application manifest maybe included for redistribution + as an external .manifest file that is installed in the same folder as the application + executable or it may be included in the executable in the form of a resource. +///////////////////////////////////////////////////////////////////////////// + +Other notes: + +The application wizard uses "TODO:" to indicate parts of the source code you +should add to or customize. + +If your application uses MFC in a shared DLL, and your application is in a +language other than the operating system's current language, you will need +to copy the corresponding localized resources MFC70XXX.DLL from the Microsoft +Visual C++ CD-ROM under the Win\System directory to your computer's system or +system32 directory, and rename it to be MFCLOC.DLL. ("XXX" stands for the +language abbreviation. For example, MFC70DEU.DLL contains resources +translated to German.) If you don't do this, some of the UI elements of +your application will remain in the language of the operating system. + +///////////////////////////////////////////////////////////////////////////// diff --git a/source/cpp/CAlfrescoApp/alfresco.ico b/source/cpp/CAlfrescoApp/alfresco.ico new file mode 100644 index 0000000000..9307688a6c Binary files /dev/null and b/source/cpp/CAlfrescoApp/alfresco.ico differ diff --git a/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp b/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp new file mode 100644 index 0000000000..1004731cda --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp @@ -0,0 +1,302 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _Alfresco_H +#define _Alfresco_H + +// Includes + +#include +#include + +#include +#include +#include "util\Exception.h" +#include "util\String.h" +#include "util\DataBuffer.h" + +// Classes defined in this header file + +namespace Alfresco { + class AlfrescoInterface; + class AlfrescoFileInfo; + class AlfrescoFileInfoList; + typedef std::auto_ptr PTR_AlfrescoFileInfo; +} + +// Constants + +namespace Alfresco { + + // Alfresco I/O control codes + + #define FSCTL_ALFRESCO_PROBE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) + #define FSCTL_ALFRESCO_FILESTS CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) + #define FSCTL_ALFRESCO_CHECKOUT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x802, METHOD_BUFFERED, FILE_WRITE_DATA) + #define FSCTL_ALFRESCO_CHECKIN CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0x803, METHOD_BUFFERED, FILE_WRITE_DATA) + + // Request signature bytes + + #define IOSignature "ALFRESCO" + #define IOSignatureLen 8 + + // Path prefixes/components + + #define UNCPathPrefix L"\\\\" + #define PathSeperator L"\\" + + // I/O control status codes + + #define StsSuccess 0 + + #define StsError 1 + #define StsFileNotFound 2 + #define StsAccessDenied 3 + #define StsBadParameter 4 + #define StsNotWorkingCopy 5 + + // Boolean field values + + #define True 1 + #define False 0 + + // File status field values + // + // Node type + + #define TypeFile 0 + #define TypeFolder 1 + + // Lock status + + #define LockNone 0 + #define LockRead 1 + #define LockWrite 2 +} + +// Define Alfresco interface exceptions + +DEFINE_EXCEPTION(Alfresco, BadInterfaceException); + +/** +* Alfresco API Class +* +* Provides the interface to an Alfresco CIFS server to perform Alfresco specific functions +* not available via the normal file I/O functions. +*/ +class Alfresco::AlfrescoInterface { +public: + // Class constructors + + AlfrescoInterface(String& path); + + // Class destructor + + ~AlfrescoInterface(); + + // Return the UNC path and root path + + inline const String& getUNCPath( void) const { return m_uncPath; } + inline const String& getRootPath( void) const { return m_rootPath; } + + // Check if the application is running from a mapped drive, return the drive path + + inline bool isMappedDrive( void) const { return m_mappedDrive.length() > 0 ? true : false; } + inline const String& getDrivePath( void) const { return m_mappedDrive; } + + // Check if the path is on an Alfresco CIFS server + + bool isAlfrescoFolder( void); + + // Return the Alfresco file information for a file/folder within the current folder + + PTR_AlfrescoFileInfo getFileInformation(const wchar_t* fileName); + + // Check in a working copy file + + void checkIn( const wchar_t* fileName, bool keepCheckedOut = false); + + // Check out a file + + void checkOut( const wchar_t* fileName, String& workingCopy); + +private: + // Send an I/O control request, receive and validate the response + + void sendIOControl( const unsigned int ctlCode, DataBuffer& reqbuf, DataBuffer& respbuf); + +private: + // Hide the copy constructor + + AlfrescoInterface(const AlfrescoInterface& alfresco) {}; + +private: + // Instance variables + // + // UNC path and root path + + String m_uncPath; + String m_rootPath; + + // Local path letter if running from a mapped drive + + String m_mappedDrive; + + // Handle to folder + + HANDLE m_handle; + +}; + +/** + * Alfresco File Information Class + * + * Contains Alfresco specific file information for a file/folder on an Alfresco CIFS server. + */ +class Alfresco::AlfrescoFileInfo { +public: + // Class constructor + + AlfrescoFileInfo( const wchar_t* fName); + + // Return the file/folder name + + inline const String& getName( void) const { return m_name; } + + // Determine if the file is a file or folder + + inline unsigned int isType( void) const { return m_type; } + + // Return the working copy status, owner, copied from + + inline bool isWorkingCopy( void) const { return m_workingCopy; } + inline const String& getCopyOwner( void) const { return m_workOwner; } + inline const String& getCopiedFrom( void) const { return m_copiedFrom; } + + // Return the lock status + + inline unsigned int getLockType( void) const { return m_lockType; } + inline const String& getLockOwner( void) const { return m_lockOwner; } + + // Return the content details + + inline bool hasContent( void) const { return m_hasContent; } + inline LONG64 getContentLength( void) const { return m_contentLen; } + inline const String& getContentType( void) const { return m_contentMimeType; } + + // Set the file/folder type + + inline void setType( unsigned int typ) { m_type = typ; } + + // Set the working copy owner and copied from + + void setWorkingCopy( const wchar_t* owner, const wchar_t* copiedFrom); + + // Set the lock type and owner + + void setLockType( unsigned int typ, const wchar_t* owner = L""); + + // Set the content length and type + + void setContent( LONG64 siz, const wchar_t* mimeType); + + // Operators + + bool operator==( const AlfrescoFileInfo& finfo); + bool operator<( const AlfrescoFileInfo& finfo); + +private: + // Hide the copy constructor + + AlfrescoFileInfo(const AlfrescoFileInfo& aInfo) {}; + +private: + // Instance variables + // + // File/folder name + + String m_name; + unsigned int m_type; + + // Working copy flag, owner and copied from + + bool m_workingCopy; + String m_workOwner; + String m_copiedFrom; + + // Lock type and owner + + unsigned int m_lockType; + String m_lockOwner; + + // Content mime-type and length + + bool m_hasContent; + LONG64 m_contentLen; + String m_contentMimeType; +}; + +/** + * Alfresco File Info List Class + */ +class Alfresco::AlfrescoFileInfoList { +public: + // Class constructor + + AlfrescoFileInfoList( void) {}; + + // Add a file information object to the list + + inline void addInfo( AlfrescoFileInfo* pInfo) { m_list.push_back( pInfo); } + inline void addInfo( PTR_AlfrescoFileInfo pInfo) { if ( pInfo.get() != NULL) m_list.push_back( pInfo.release()); } + + // Return the number of objects in the list + + inline size_t size( void) const { return m_list.size(); } + + // Return the specified file information + + inline const AlfrescoFileInfo* getInfoAt( unsigned int idx) const { return m_list[idx]; } + + // Assignment operator + + inline AlfrescoFileInfo*& operator[] ( const unsigned int idx) { return m_list[idx]; } + + // Remove all objects from the list + + inline void clear( void) { for ( unsigned int i = 0; i < m_list.size(); delete m_list[i++]); m_list.clear(); } + + // Return the vector + + std::vector getList( void) { return m_list; } + +private: + // Instance variables + // + // List of file information objects + + std::vector m_list; +}; + +#endif diff --git a/source/cpp/CAlfrescoApp/includes/util/ByteArray.h b/source/cpp/CAlfrescoApp/includes/util/ByteArray.h new file mode 100644 index 0000000000..e8269843da --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/ByteArray.h @@ -0,0 +1,109 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _ByteArray_H +#define _ByteArray_H + +// Includes + +#include +#include + +#include "util\Types.h" + +// Classes defined in this header file + +namespace Alfresco { + class ByteArray; + + typedef std::auto_ptr PTR_ByteArray; +} + +/** + * Byte Array Class + * + * Provides a byte array object similar to Javas byte[]. + */ +class Alfresco::ByteArray { +public: + // Constructors + + ByteArray( BUFLEN len = 0, bool clearMem = false); + ByteArray( CBUFPTR data, BUFLEN len); + ByteArray( const char* data, BUFLEN len); + + // Copy constructor + + ByteArray( const ByteArray& byts); + + // Class destructor + + ~ByteArray(); + + // Return the data/length + + inline CBUFPTR getData( void) const { return m_data; } + inline BUFPTR getData( void) { return m_data; } + inline BUFLEN getLength( void) const { return m_length; } + + // Set the array length + + void setLength( BUFLEN len, bool clearMem = false); + + // Set a byte + + void setByte( unsigned int idx, unsigned char val); + + // Subscript operator + + unsigned char& operator[](const unsigned int idx); + + // Assignment operator + + ByteArray& operator=( const ByteArray& byts); + ByteArray& operator=( std::string& byts); + + // Equality operator + + bool operator== ( const ByteArray& byts); + + // Return the start address of the byte array + + operator const unsigned char* ( void) { return m_data; } + +protected: + // Set the byte array and length + + void setData( CBUFPTR data, BUFLEN len); + +private: + // Instance variables + // + // Byte data and length + + BUFPTR m_data; + BUFLEN m_length; +}; + +#endif diff --git a/source/cpp/CAlfrescoApp/includes/util/DataBuffer.h b/source/cpp/CAlfrescoApp/includes/util/DataBuffer.h new file mode 100644 index 0000000000..7bc250264b --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/DataBuffer.h @@ -0,0 +1,157 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _DataBuffer_H +#define _DataBuffer_H + +// Includes + +#include "util\DataPacker.h" + +// Classes defined in this header file + +namespace Alfresco { + class DataBuffer; + typedef std::auto_ptr PTR_DataBuffer; +} + +// Constants + +namespace Alfresco { + + // Default data buffer size + + #define DataBufferDefaultSize 256 +} + +/** + * Data Buffer Class + * + * Dynamic buffer for getting/setting data blocks. + */ +class Alfresco::DataBuffer { +public: + // Class constructors + + DataBuffer( unsigned int siz = DataBufferDefaultSize); + DataBuffer( BUFPTR buf, BUFPOS off, BUFLEN len); + + // Class destructor + + ~DataBuffer(); + + // Getter methods + + inline BUFPTR getBuffer( void) { return m_buf; } + + BUFLEN getLength( void) const; + unsigned int getLengthInWords( void) const; + BUFLEN getAvailableLength( void) const; + inline BUFLEN getBufferLength(void) const { return m_buflen; } + + inline unsigned int getDisplacement( void) const { return m_pos - m_offset; } + inline BUFPOS getOffset( void) { return m_offset; } + inline BUFPOS getPosition( void) { return m_pos; } + + // Get data items from the buffer + + unsigned char getByte( void); + unsigned int getShort( void); + unsigned int getInt( void); + LONG64 getLong( void); + String getString( bool uni = true); + String getString( unsigned int maxLen, bool uni = true); + + unsigned int getShortAt( unsigned int idx); + unsigned int getIntAt( unsigned int idx); + LONG64 getLongAt( unsigned int idx); + + // Put data items into the buffer + + void putByte( unsigned char byt); + void putShort( unsigned int sval); + void putInt( unsigned int ival); + void putLong( LONG64 lval); + + void putShortAt( unsigned int idx, unsigned int sval); + void putIntAt( unsigned int idx, unsigned int ival); + void putLongAt( unsigned int idx, LONG64 lval); + + void putString( const String& str, bool uni = true, bool nulTerm = true); + void putFixedString( const String& str, unsigned int len); + BUFPOS putStringAt( const String& str, BUFPOS pos, bool uni = true, bool nulTerm = true); + BUFPOS putFixedStringAt( const String& str, unsigned int len, BUFPOS pos); + + void putStringPointer( unsigned int off); + void putZeros( unsigned int cnt); + + // Align the buffer position + + void wordAlign( void); + void longwordAlign( void); + + // Append a raw data block to the buffer + + void appendData( BUFPTR buf, BUFPOS off, BUFLEN len); + + // Copy the data to the user buffer and update the read position + + unsigned int copyData( BUFPTR buf, BUFPOS pos, unsigned int cnt); + + // Skip data items in the buffer + + void skipBytes( unsigned int len); + + // Setter methods + + inline void setPosition( BUFPOS pos) { m_pos = pos; } + void setEndOfBuffer( void); + void setLength( BUFLEN len); + +private: + // Extend the buffer + + void extendBuffer( BUFLEN ext); + void extendBuffer( void); + +protected: + // Instance variables + // + // Data buffer + + BUFPTR m_buf; + unsigned int m_buflen; + + // Flag to indicate if the buffer is owned by this object + + bool m_owner; + + // Buffer positions/offsets + + BUFPOS m_pos; + BUFPOS m_endpos; + BUFPOS m_offset; +}; + +#endif diff --git a/source/cpp/CAlfrescoApp/includes/util/DataPacker.h b/source/cpp/CAlfrescoApp/includes/util/DataPacker.h new file mode 100644 index 0000000000..d9eb561a73 --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/DataPacker.h @@ -0,0 +1,93 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _DataPacker_H +#define _DataPacker_H + +// Includes + +#include "util\String.h" +#include "util\Types.h" +#include "util\JavaTypes.h" + +// Classes defined in this header file + +namespace Alfresco { + class DataPacker; +} + +/** + * DataPacker Class + * + * The DataPacker class provides methods for packing and unpacking of various data types from a buffer. + */ +class Alfresco::DataPacker { +private: + // Hide constructors + + DataPacker( void) {}; + DataPacker(const DataPacker& dp) {}; + +public: + // Unpack data types from a buffer + + static int getShort(CBUFPTR buf, BUFPOS pos); + static int getInt(CBUFPTR buf, BUFPOS pos); + static LONG64 getLong(CBUFPTR buf, BUFPOS pos); + + static int getIntelShort(CBUFPTR buf, BUFPOS pos); + static int getIntelInt(CBUFPTR buf, BUFPOS pos); + static LONG64 getIntelLong(CBUFPTR buf, BUFPOS pos); + + static String getString(CBUFPTR buf, BUFPOS pos, const unsigned int maxLen, const bool isUni = false); + static String getUnicodeString(CBUFPTR buf, BUFPOS pos, const unsigned int maxLen); + + // Pack data types into a buffer + + static void putShort(const int val, BUFPTR buf, BUFPOS pos); + static void putInt(const int val, BUFPTR buf, BUFPOS pos); + static void putLong(const LONG64 val, BUFPTR buf, BUFPOS pos); + + static void putIntelShort(const int val, BUFPTR buf, BUFPOS pos); + static void putIntelInt(const int val, BUFPTR buf, BUFPOS pos); + static void putIntelLong(const LONG64 val, BUFPTR buf, BUFPOS pos); + + static unsigned int putString(const String& str, BUFPTR buf, BUFPOS pos, bool nullTerm = true, bool isUni = false); + static unsigned int putString(const char* str, BUFLEN len, BUFPTR buf, BUFPOS pos, bool nullTerm = true); + static unsigned int putString(const wchar_t* str, BUFLEN len, BUFPTR buf, BUFPOS pos, bool nullTerm = true); + + static void putZeros(BUFPTR buf, BUFPOS pos, const unsigned int count); + + // Calculate buffer positions + + static unsigned int getStringLength(const String& str, const bool isUni = false, const bool nulTerm = false); + static unsigned int getBufferPosition(BUFPOS pos, const String& str, const bool isUni = false, const bool nulTerm = false); + + // Align a buffer offset + + static inline BUFPOS longwordAlign( BUFPOS pos) { return ( pos + 3) & 0xFFFFFFFC; } + static inline BUFPOS wordAlign( BUFPOS pos) { return ( pos + 1) & 0xFFFFFFFE; } +}; + +#endif diff --git a/source/cpp/CAlfrescoApp/includes/util/Exception.h b/source/cpp/CAlfrescoApp/includes/util/Exception.h new file mode 100644 index 0000000000..a22de7059e --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/Exception.h @@ -0,0 +1,130 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _JavaException_H +#define _JavaException_H + +// Includes + +#include "util\String.h" + +// Classes defined in this header file + +namespace Alfresco { + class Exception; + class IOException; +} + +// Macro to check for null and throw a null pointer exception + +#define NULL_POINTER_CHECK(p,m) if(p==NULL) throw NullPointerException(m) + +/** + * Java-like Exception Class + * + * Used as a base class for all Java-like exception classes. + */ +class Alfresco::Exception { +public: + // Constructors + + Exception( const wchar_t* msg = NULL, const wchar_t* msg2 = NULL, const wchar_t* msg3 = NULL, const wchar_t* msg4 = NULL, const wchar_t* msg5 = NULL); + Exception( const char* moduleName, unsigned int lineNum, const wchar_t* msg = NULL, const wchar_t* msg2 = NULL, const wchar_t* msg3 = NULL, const wchar_t* msg4 = NULL, const wchar_t* msg5 = NULL); + + // Copy constructor + + Exception( const Exception& ex); + + // Class destructor + + ~Exception(); + + // Return the exception message + + inline const String& getMessage( void) const { return m_msg; } + + // Return the exception as a string + + inline const String& toString( void) const { return m_msg; } + +private: + // Instance variables + // + // Exception message + + String m_msg; +}; + +// Macros to declare an exception class + +#define DEFINE_EXCEPTION(ns,ex) namespace ns { class ex : public Exception { \ +public: \ + ex( const char* modName, unsigned int lineNum, const wchar_t* msg = NULL, const wchar_t* msg2 = NULL, const wchar_t* msg3 = NULL, const wchar_t* msg4 = NULL, const wchar_t* msg5 = NULL); \ + ex( const wchar_t* msg = NULL, const wchar_t* msg2 = NULL, const wchar_t* msg3 = NULL, const wchar_t* msg4 = NULL, const wchar_t* msg5 = NULL); }; } + +#define DEFINE_IOEXCEPTION(ns,ex) namespace ns { class ex : public IOException { \ +public: \ + ex( const char* modName, unsigned int lineNum, const wchar_t* msg = NULL, const wchar_t* msg2 = NULL, const wchar_t* msg3 = NULL, const wchar_t* msg4 = NULL, const wchar_t* msg5 = NULL); \ + ex( const wchar_t* msg = NULL, const wchar_t* msg2 = NULL, const wchar_t* msg3 = NULL, const wchar_t* msg4 = NULL, const wchar_t* msg5 = NULL); }; } + +// Macros to define new exception class code, should be used in a module not a header + +#define EXCEPTION_CLASS(ns,ex) \ + ex :: ex( const char* modName, unsigned int lineNum, const wchar_t* msg, const wchar_t* msg2, const wchar_t* msg3, const wchar_t* msg4, const wchar_t* msg5) : \ + Exception(modName,lineNum,msg,msg2,msg3,msg4,msg5) {} \ + ex :: ex( const wchar_t* msg, const wchar_t* msg2, const wchar_t* msg3, const wchar_t* msg4, const wchar_t* msg5) : \ + Exception(msg,msg2,msg3,msg4,msg5) {} + +// Define the IOException class + +DEFINE_EXCEPTION(Alfresco,IOException); + +// Define the macro create new IOException based exceptions + +#define IOEXCEPTION_CLASS(ns,ex) \ + ex :: ex( const char* modName, unsigned int lineNum, const wchar_t* msg, const wchar_t* msg2, const wchar_t* msg3, const wchar_t* msg4, const wchar_t* msg5) : \ + IOException(modName,lineNum,msg,msg2,msg3,msg4,msg5) {} \ + ex :: ex( const wchar_t* msg, const wchar_t* msg2, const wchar_t* msg3, const wchar_t* msg4, const wchar_t* msg5) : \ + IOException(msg,msg2,msg3,msg4,msg5) {} + +// Define standard exceptions + +DEFINE_EXCEPTION(Alfresco,NullPointerException); +DEFINE_EXCEPTION(Alfresco,ArrayIndexOutOfBoundsException); +DEFINE_EXCEPTION(Alfresco,NumberFormatException); + +DEFINE_IOEXCEPTION(Alfresco, AccessDeniedException); +DEFINE_IOEXCEPTION(Alfresco, DirectoryNotEmptyException); +DEFINE_IOEXCEPTION(Alfresco, DiskFullException); +DEFINE_IOEXCEPTION(Alfresco, FileExistsException); +DEFINE_IOEXCEPTION(Alfresco, FileOfflineException); +DEFINE_IOEXCEPTION(Alfresco, FileSharingException); +DEFINE_IOEXCEPTION(Alfresco, FileNotFoundException); +DEFINE_IOEXCEPTION(Alfresco, PathNotFoundException); +DEFINE_IOEXCEPTION(Alfresco, FileLockException); +DEFINE_IOEXCEPTION(Alfresco, FileUnlockException); +DEFINE_IOEXCEPTION(Alfresco, LockConflictException); +DEFINE_IOEXCEPTION(Alfresco, NotLockedException); + +#endif \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/includes/util/FileName.h b/source/cpp/CAlfrescoApp/includes/util/FileName.h new file mode 100644 index 0000000000..3cb3442177 --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/FileName.h @@ -0,0 +1,100 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _FileName_H +#define _FileName_H + +// Includes + +#include "util\String.h" + +// Classes defined in this header file + +namespace Alfresco { + class FileName; +} + +/** + * File Naming Utility Class + * + * Contains various utility methods for building and splitting file paths. + */ +class Alfresco::FileName { +public: + // Build a path using the specified components + + static const String buildPath( const String& dev, const String& path, const String& fileName, wchar_t sep = L'\\'); + + // Check if a file name contains a stream name + + static bool containsStreamName( const String& fileName); + + // Convert path separator characters + + static const String convertSeperators( const String& path, wchar_t sep); + + // Make a relative path + + static const String makeRelativePath( const String& basePath, const String& fullPath); + + // Map an input path to a real path + + static const String mapPath(const String& base, const String& path); + + // Normalize a path converting all directories to uppercase and keeping the file name as is + + static const String normalizePath(const String& path); + + // Remove the file name from the path + + static const String removeFileName(const String& path); + + // Split the path into all the component directories and filename + + static StringList splitAllPaths(const String& path); + + // Split the path into separate directory path and file name strings + + static StringList splitPath( const String& path, wchar_t sep = L'\\'); + + // Split a path string into directory path, file name and stream name components + + static StringList splitPathStream( const String& path); + +public: + // Constant values + + static String& DosSeperator; + static String& NTFSStreamSeperator; + + static wchar_t DOS_SEPERATOR; + +private: + // Hide constructors, static only class + + FileName( void) {}; + FileName( const FileName& fname) {}; +}; + +#endif diff --git a/source/cpp/CAlfrescoApp/includes/util/Integer.h b/source/cpp/CAlfrescoApp/includes/util/Integer.h new file mode 100644 index 0000000000..e08d7ebd2b --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/Integer.h @@ -0,0 +1,67 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _JavaInteger_H +#define _JavaInteger_H + +// Includes + +#include "util\String.h" +#include "util\Exception.h" +#include "util\Types.h" + +// Classes defined in this header file + +namespace Alfresco { + class Integer; +} + +/** + * Java-like Integer Class + * + * Provides static methods to convert integer values to strings. + */ +class Alfresco::Integer { +public: + // Convert an integer to a hexadecimal string + + static String toHexString( const unsigned int ival); + static String toHexString( BUFPTR ptr); + + // Convert an integer value to a string + + static String toString( unsigned int ival, unsigned int radix = 10); + + // Parse a string to generate an integer value + + static unsigned int parseInt( const String& str, unsigned int radix = 10); + +private: + // Hide constructors, static only class + + Integer( void); + Integer(Integer& ival); +}; + +#endif \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/includes/util/JavaTypes.h b/source/cpp/CAlfrescoApp/includes/util/JavaTypes.h new file mode 100644 index 0000000000..7a537405a7 --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/JavaTypes.h @@ -0,0 +1,32 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _JavaTypes_H +#define _JavaTypes_H + +// Typedefs for Java primitive types + +typedef __int64 LONG64; + +#endif diff --git a/source/cpp/CAlfrescoApp/includes/util/Long.h b/source/cpp/CAlfrescoApp/includes/util/Long.h new file mode 100644 index 0000000000..d51f5db42b --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/Long.h @@ -0,0 +1,84 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _JavaLong_H +#define _JavaLong_H + +// Includes + +#include + +#include "util\String.h" +#include "util\Exception.h" +#include "util\JavaTypes.h" + +// Classes defined in this header file + +namespace Alfresco { + class Long; +} + +/** + * Java-like Long Class + * + * Provides static methods to convert long/64 bit values to strings. + */ +class Alfresco::Long { +public: + // Convert a long/64 bit integer to a hexadecimal string + + static String toHexString( const LONG64 lval); + + // Convert a long/64 bit integer to a decimal string + + static String toString( const LONG64 lval); + + // Make a long/64bit value from the low/high 32bit values + + static LONG64 makeLong( unsigned int lowPart, unsigned int highPart); + static LONG64 makeLong( FILETIME fTime); + + // Get the low/high 32bit values from a 64bit value + + static bool hasHighPart( LONG64 lval) { return ( lval > 0xFFFFFFFF) ? true : false; } + + static unsigned int getLowPart( LONG64 lval) { return (unsigned int) lval & 0xFFFFFFFF; } + static unsigned int getHighPart( LONG64 lval) { return (unsigned int) ((lval >> 32) & 0xFFFFFFFF); } + + // Parse a string to generate a long/64 bit integer value + + static LONG64 parseLong( const String& str, unsigned int radix = 10); + + // Copy a long/64bit value to a FILETIME structure + + static void copyTo( LONG64 lval, FILETIME& ftime); + +private: + // Hide constructors, static only class + + Long( void); + Long(Long& ival); +}; + +#endif \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/includes/util/String.h b/source/cpp/CAlfrescoApp/includes/util/String.h new file mode 100644 index 0000000000..52902445b4 --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/String.h @@ -0,0 +1,268 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _JavaString_H +#define _JavaString_H + +// Includes + +#include +#include +#include + +#include "util\ByteArray.h" +#include "util\Types.h" + +// Classes defined in this header file + +namespace Alfresco { + class String; + class StringList; +} + +/** + * Java-like String Class + */ +class Alfresco::String { +public: + // Constructors + + String(); + String(const unsigned int alloc); + String(const char* str); + String(const unsigned char* str); + String(const char* buf, const unsigned int offset, const unsigned int len); + String(const wchar_t* str); + String(const wchar_t* buf, const unsigned int offset, const unsigned int len); + String(const String& str); + String(const std::wstring& str); + String(ByteArray& byts); + + // Return the string length + + inline unsigned int length( void) const { return ( unsigned int) m_string.length(); } + + // Check if a string is empty + + inline bool isNull( void) const { return m_string.length() > 0 ? false : true; } + inline bool isNotEmpty( void) const { return m_string.length() > 0 ? true : false; } + + // Compare strings for equality + + bool equals(const wchar_t* str) const; + bool equals(const String& str) const; + + bool equalsIgnoreCase(const wchar_t* str) const; + bool equalsIgnoreCase(const String& str) const; + + // Compare strings + + int compareTo( const String& str) const; + int compareTo( const wchar_t* pStr) const; + + // Convert to lowercase/uppercase returning the new string + + String toLowerCase() const; + String toUpperCase() const; + + // Search for the occurrence of a character or string + + int indexOf(const wchar_t ch, int startIndex = 0) const; + int indexOf(const wchar_t* str, int startIndex = 0) const; + int indexOf(const String& str, int startIndex = 0) const; + + // Search for the occurrence of a character or string + + int lastIndexOf(const wchar_t ch, int startIndex = -1) const; + int lastIndexOf(const wchar_t* str, int startIndex = -1) const; + int lastIndexOf(const String& str, int startIndex = -1) const; + + // Check if the string starts with the specified string + + bool startsWith(const wchar_t* str) const; + bool startsWith(const String& str) const; + + bool startsWithIgnoreCase(const wchar_t* str) const; + bool startsWithIgnoreCase(const String& str) const; + + // Check if the string ends with the specified string + + bool endsWith(const wchar_t* str) const; + bool endsWith(const String& str) const; + + // Replace all occurrences of the specified character in the string + + void replace( wchar_t oldCh, wchar_t newCh); + + // Append character, string, integer values to the string + + void append( wchar_t ch); + void append( const char* str); + void append( const wchar_t* str); + void append( const String& str); + void append( const unsigned int ival); + void append( const unsigned long lval); + void append( const LONG64 l64val); + + // Get the character at the specified position in the string + + inline wchar_t charAt(const unsigned int idx) const { return m_string[idx]; } + + // Set the string length + + inline void setLength( unsigned int len) { m_string.resize( len, 0); } + + // Trim leading and trailing whitespace from the string + + String trim( void) const; + + // Return the substring of this string + + String substring( unsigned int beginIndex) const; + String substring( unsigned int beginIndex, unsigned int endIndex) const; + + // Set the allocated capacity for the string by allocating or shrinking the current string buffer + + inline void reserve( const unsigned int capacity = 0) { m_string.reserve( capacity); } + + // Assignment operator + + String& operator=(const wchar_t* str); + String& operator=(const String& str); + + // Append operator + + inline String& operator+=(wchar_t ch) { append( ch); return *this; } + inline String& operator+=(const char* str) { append( str); return *this; } + inline String& operator+=(const wchar_t* str) { append( str); return *this; } + inline String& operator+=(const String& str) { append( str); return *this; } + inline String& operator+=(const unsigned int ival) { append( ival); return *this; } + inline String& operator+=(const unsigned long lval) { append( lval); return *this; } + inline String& operator+=(const LONG64 l64val) { append( l64val); return *this; } + + // Equality operator + + bool operator== ( const String& str) const; + bool operator== ( const wchar_t* str) const; + bool operator== ( const char* str) const; + + // Less than operator + + bool operator< ( const String& str) const; + + // Return the string data + + inline const wchar_t* data() const { return m_string.data(); } + + // Conversion operator + + inline operator const wchar_t* ( void) const { return m_string.data(); } + + // Return the string as an array of bytes + + ByteArray getBytes( ByteArray& byts) const; + ByteArray getBytes( void) const; + + // Split the string into tokens using the specified delimiters + + StringList tokenize( const String& delims) const; + + // Streaming operators + + friend std::wostream& operator<<(std::wostream& out, const String& str); + friend std::ostream& operator<<(std::ostream& out, const String& str); + + // Access the internal string object + + inline std::wstring getString( void) { return m_string; } + inline const std::wstring getString( void) const { return m_string; } + +private: + // String data + + std::wstring m_string; +}; + +/** + * String List Class + */ +class Alfresco::StringList { +public: + // Class constructor + + StringList( void); + StringList( unsigned int reserve); + StringList( const StringList& strList); + + // Add a string to the list + + inline void addString( const String& str) { m_list.push_back( str); } + + // Check if the list contains the specified string + + bool containsString( const String& str); + bool containsStringCaseless ( const String& str); + + // Return the index of the specified string, or -1 if not found + + int indexOf( const String& str) const; + + // Remove a string from the list + + void removeString( const String& str); + void removeStringCaseless( const String& str); + + // Return the number of strings in the list + + inline size_t numberOfStrings( void) const { return m_list.size(); } + + // Return the specified string + + inline const String& getStringAt( unsigned int idx) const { return m_list[idx]; } + + // Assignment operator + + inline String& operator[] ( const unsigned int idx) { return m_list[idx]; } + + // Remove all strings from the list + + inline void removeAllStrings( void) { m_list.clear(); } + + // Copy the string list + + void copyFrom( const StringList& strList); + + // Return the string list as a comma separated list + + String toString( void) const; + +private: + // Instance variables + // + // List of strings + + std::vector m_list; +}; + +#endif diff --git a/source/cpp/CAlfrescoApp/includes/util/System.h b/source/cpp/CAlfrescoApp/includes/util/System.h new file mode 100644 index 0000000000..6fa1d686ed --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/System.h @@ -0,0 +1,55 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _JavaSystem_H +#define _JavaSystem_H + +// Includes + +#include "util\Types.h" + +// Classes defined in this header file + +namespace Alfresco { + class System; +} + +/** + * Java-like System Class + */ +class Alfresco::System { +public: + + // Get the current system time in milliseconds + + static DATETIME currentTimeMillis( void); + +private: + // Hide constructors, static only class + + System( void) {}; + System ( const System& sys) {}; +}; + +#endif \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/includes/util/Types.h b/source/cpp/CAlfrescoApp/includes/util/Types.h new file mode 100644 index 0000000000..67f431a88c --- /dev/null +++ b/source/cpp/CAlfrescoApp/includes/util/Types.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#ifndef _AlfrescoTypes_H +#define _AlfrescoTypes_H + +// Includes + +#include "util\JavaTypes.h" + +namespace Alfresco { + + // Type definitions + // + // Data buffer pointer, position and length + + typedef unsigned char* BUFPTR; + typedef unsigned int BUFPOS; + typedef unsigned int BUFLEN; + + typedef const unsigned char* CBUFPTR; + typedef const unsigned int CBUFPOS; + typedef const unsigned int CBUFLEN; + + // File position and length + + typedef LONG64 FILEPOS; + typedef LONG64 FILELEN; + + // Date/time + + typedef LONG64 DATETIME; + #define NULL_DATETIME ((DATETIME) 0) +} + +#endif diff --git a/source/cpp/CAlfrescoApp/res/CAlfrescoApp.ico b/source/cpp/CAlfrescoApp/res/CAlfrescoApp.ico new file mode 100644 index 0000000000..b9e83ab000 Binary files /dev/null and b/source/cpp/CAlfrescoApp/res/CAlfrescoApp.ico differ diff --git a/source/cpp/CAlfrescoApp/res/CAlfrescoApp.manifest b/source/cpp/CAlfrescoApp/res/CAlfrescoApp.manifest new file mode 100644 index 0000000000..024ac68133 --- /dev/null +++ b/source/cpp/CAlfrescoApp/res/CAlfrescoApp.manifest @@ -0,0 +1,22 @@ + + + +Your app description here + + + + + + diff --git a/source/cpp/CAlfrescoApp/res/CAlfrescoApp.rc2 b/source/cpp/CAlfrescoApp/res/CAlfrescoApp.rc2 new file mode 100644 index 0000000000..87038a0bac --- /dev/null +++ b/source/cpp/CAlfrescoApp/res/CAlfrescoApp.rc2 @@ -0,0 +1,13 @@ +// +// CAlfrescoApp.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/source/cpp/CAlfrescoApp/resource.h b/source/cpp/CAlfrescoApp/resource.h new file mode 100644 index 0000000000..d88a6cfbd7 --- /dev/null +++ b/source/cpp/CAlfrescoApp/resource.h @@ -0,0 +1,25 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by CAlfrescoApp.rc +// +#define IDM_ABOUTBOX 0x0010 +#define IDD_ABOUTBOX 100 +#define IDS_ABOUTBOX 101 +#define IDD_CALFRESCOAPP_DIALOG 102 +#define IDR_HTML_CALFRESCOAPP_DIALOG 104 +#define IDI_ICON1 133 +#define IDD_DIALOG1 134 +#define IDD_FILESTATUS 134 +#define IDC_MSGTEXT 1001 +#define IDC_FILELIST 1002 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 135 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/source/cpp/CAlfrescoApp/source/AlfrescoApp.cpp b/source/cpp/CAlfrescoApp/source/AlfrescoApp.cpp new file mode 100644 index 0000000000..1cb9ec954a --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/AlfrescoApp.cpp @@ -0,0 +1,420 @@ +#include + +#include "alfresco\Alfresco.hpp" + +#include "util\String.h" +#include "util\DataBuffer.h" +#include "util\FileName.h" + +#include + +using namespace std; +using namespace JLAN; + +// Function prototypes + +bool doFolderStatus( Alfresco& alfresco, const wchar_t* fileSpec = L"*.*"); +bool doCheckInOut( Alfresco& alfresco, StringList& files); +bool doCheckIn( Alfresco& alfresco, PTR_AlfrescoFileInfo& fileInfo); +bool doCheckOut( Alfresco& alfresco, PTR_AlfrescoFileInfo& fileInfo); + +/** + * Alfresco Windows Drag And Drop Application + * + * @author GKSpencer + */ +int wmain( int argc, wchar_t* argv[], wchar_t* envp[]) { + + // Output a startup banner + + wcout << L"Alfresco Drag And Drop Application" << endl; + wcout << L"----------------------------------" << endl; + + // Check if the app is running from a network drive + + String appPath(argv[0]); +// String appPath("\\\\StarlaA\\Alfresco\\Garys Space\\AlfrescoApp.exe"); +// String appPath("Z:\\Garys Space\\AlfrescoApp.exe"); +// argc = 2; + + // Looks like a UNC path, trim off the application name + + int pos = appPath.lastIndexOf(PathSeperator); + if ( pos < 0) { + wcout << L"%% Invalid application path, " << appPath << endl; + return 1; + } + + // Get the path to the folder containing the application + + String folderPath = appPath.substring(0, pos); + + // Create the Alfresco interface + + Alfresco alfresco(folderPath); + if ( alfresco.isAlfrescoFolder()) { + + // If there are no file paths on the command line then display a status page for the files + // in the Alfresco folder + + if ( argc == 1) { + + // Display status for the files in the Alfresco folder + + doFolderStatus( alfresco); + } + else { + + // Build a list of the file names + + StringList fileList; + + for ( int i = 1; i < argc; i++) + fileList.addString( String(argv[i])); +// fileList.addString(L"N:\\testArea\\msword\\CIFSLOG.doc"); +// fileList.addString(L"\\\\StarlaA\\Alfresco\\Garys Space\\CIFSLOG.doc"); + + // Process the file list and check in or out each file + + doCheckInOut( alfresco, fileList); + } + } + else { + wcout << L"%% Not a valid Alfresco CIFS folder, " << folderPath << endl; + return 1; + } + + // Wait for user input + + wcout << L"Press to continue ..." << flush; + getchar(); +} + +/** + * Display file status of the files in the target Alfresco folder + * + * @param Alfresco& alfresco + * @param const wchar_t* fileSpec + * @return bool + */ +bool doFolderStatus( Alfresco& alfresco, const wchar_t* fileSpec) { + + // Get the base UNC path + + String uncPath = alfresco.getUNCPath(); + uncPath.append(PathSeperator); + + // Search the Alfresco folder + + WIN32_FIND_DATA findData; + String searchPath = uncPath; + searchPath.append( fileSpec); + + bool sts = false; + HANDLE fHandle = FindFirstFile( searchPath, &findData); + + if ( fHandle != INVALID_HANDLE_VALUE) { + + // Loop until all files have been returned + + PTR_AlfrescoFileInfo pFileInfo; + sts = true; + + while ( fHandle != INVALID_HANDLE_VALUE) { + + // Get the file name, ignore the '.' and '..' files + + String fName = findData.cFileName; + + if ( fName.equals(L".") || fName.equals(L"..")) { + + // Get the next file/folder name in the search + + if ( FindNextFile( fHandle, &findData) == 0) + fHandle = INVALID_HANDLE_VALUE; + continue; + } + + // Get the file information for the current file folder + + pFileInfo = alfresco.getFileInformation( findData.cFileName); + + if ( pFileInfo.get() != NULL) { + + // Output the file details + + wcout << pFileInfo->getName() << endl; + + if ( pFileInfo->isType() == TypeFolder) + wcout << L" [Folder]" << endl; + + if ( pFileInfo->isWorkingCopy()) + wcout << L" [Work: " << pFileInfo->getCopyOwner() << L", " << pFileInfo->getCopiedFrom() << L"]" << endl; + + if ( pFileInfo->getLockType() != LockNone) + wcout << L" [Lock: " << (pFileInfo->getLockType() == LockRead ? L"READ" : L"WRITE") << L", " << + pFileInfo->getLockOwner() << L"]" << endl; + + if ( pFileInfo->hasContent()) + wcout << L" [Content: " << pFileInfo->getContentLength() << L", " << pFileInfo->getContentType() << L"]" << endl;; + + // Get the next file/folder name in the search + + if ( FindNextFile( fHandle, &findData) == 0) + fHandle = INVALID_HANDLE_VALUE; + } + else { + sts = false; + fHandle = INVALID_HANDLE_VALUE; + } + } + } + + // Return status + + return sts; +} + +/** + * Process the list of files and check in or out each file + * + * @param alfresco Alfresco& + * @param files StringList& + */ +bool doCheckInOut( Alfresco& alfresco, StringList& files) { + + // Process the list of files and either check in the file if it is a working copy or check out + // the file + + for ( unsigned int i = 0; i < files.numberOfStrings(); i++) { + + // Get the current file name + + String curFile = files.getStringAt( i); + + // Check if the path is on an Alfresco mapped drive + + if ( alfresco.isMappedDrive() && curFile.startsWithIgnoreCase( alfresco.getDrivePath())) { + + // Convert the path to a UNC path + + String uncPath = alfresco.getRootPath(); + uncPath.append( curFile.substring(2)); + + curFile = uncPath; + } + + // Check that the path is to a file + + bool copyFile = false; + + DWORD attr = GetFileAttributes( curFile); + if ( attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + + // Get the file name from the path + + StringList nameParts = FileName::splitPath( curFile); + String curName = nameParts.getStringAt( 1); + + // Get the Alfresco file status information + + PTR_AlfrescoFileInfo pFileInfo = alfresco.getFileInformation( curName); + + // If the path is to a file that is not on the Alfresco share the file will need to be copied, + // after checking the status of a matching file in the Alfresco folder + + if ( curFile.length() >= 3 && curFile.substring(1,3).equals( L":\\")) { + + // Check if there is an existing file with the same name + + if ( pFileInfo.get() != NULL) { + + // Check if the file is a working copy + + if ( pFileInfo->isWorkingCopy()) { + + // Local file matches a working copy file in the Alfresco folder + + wcout << L"Found matching working copy for local file " << curName << endl; + } + else if ( pFileInfo->getLockType() != LockNone) { + + // File is locked, may be the original document + + wcout << L"%% Destination file " << curName << L" is locked" << endl; + return false; + } + else { + + // Indicate that we have copied a new file to the Alfresco share, do not check in/out + + copyFile = true; + } + } + else { + + // Indicate that we have copied a new file to the Alfresco share, do not check in/out + + copyFile = true; + } + + // Build the from/to paths, must be double null terminated + + wchar_t fromPath[MAX_PATH + 1]; + wchar_t toPath[MAX_PATH + 1]; + + memset( fromPath, 0, sizeof( fromPath)); + memset( toPath, 0, sizeof( toPath)); + + wcscpy( fromPath, curFile.data()); + wcscpy( toPath, alfresco.getUNCPath()); + + // Copy the local file to the Alfresco folder + + SHFILEOPSTRUCT fileOpStruct; + memset( &fileOpStruct, 0, sizeof(SHFILEOPSTRUCT)); + + fileOpStruct.hwnd = HWND_DESKTOP; + fileOpStruct.wFunc = FO_COPY; + fileOpStruct.pFrom = fromPath; + fileOpStruct.pTo = toPath; + fileOpStruct.fFlags= 0; + fileOpStruct.fAnyOperationsAborted =false; + + // Copy the file to the Alfresco folder + + if ( SHFileOperation( &fileOpStruct) != 0) { + + // File copy failed + + wcout << L"%% Failed to copy file " << curFile << endl; + return false; + } + else if ( fileOpStruct.fAnyOperationsAborted) { + + // User aborted the file copy + + wcout << L"%% Copy aborted for " << curFile << endl; + return false; + } + + // Get the file information for the copied file + + pFileInfo = alfresco.getFileInformation( curName); + } + + // Check in or check out the file + + if ( pFileInfo.get() != NULL) { + + // Check if the file should be checked in/out + + if ( copyFile == false) { + + // Check if the file is a working copy, if so then check it in + + if ( pFileInfo->isWorkingCopy()) { + + // Check in the file + + doCheckIn( alfresco, pFileInfo); + } + else if ( pFileInfo->getLockType() == LockNone) { + + // Check out the file + + doCheckOut( alfresco, pFileInfo); + } + else { + + // File is locked, may already be checked out + + wcout << L"%% File " << curFile << L" is locked" << endl; + } + } + else { + + // No existing file to link the copied file to + + wcout << L"Copied file " << curFile << L" to Alfresco share" << endl; + } + + } + else + wcout << L"%% Failed to get file status for " << curFile << endl; + } + else + wcout << L"%% Path " << curFile << L" is a folder, ignored" << endl; + } + + // Return status + + return true; +} + +/** + * Check in the specified file + * + * @param alfresco Alfresco& + * @param pFileInfo PTR_AlfrescoFileInfo& + * @return bool + */ +bool doCheckIn( Alfresco& alfresco, PTR_AlfrescoFileInfo& pFileInfo) { + + bool checkedIn = false; + + try { + + // Check in the specified file + + alfresco.checkIn( pFileInfo->getName()); + + wcout << L"Checked in file " << pFileInfo->getName() << endl; + + // Indicate that the check in was successful + + checkedIn = true; + } + catch (Exception ex) { + wcerr << L"%% Error checking in file " << pFileInfo->getName() << endl; + wcerr << L" " << ex.getMessage() << endl; + } + + // Return the check in status + + return checkedIn; +} + +/** + * Check out the specified file + * + * @param alfresco Alfresco& + * @param pFileInfo PTR_AlfrescoFileInfo& + * @return bool + */ +bool doCheckOut( Alfresco& alfresco, PTR_AlfrescoFileInfo& pFileInfo) { + + bool checkedOut = false; + + try { + + // Check out the specified file + + String workingCopy; + alfresco.checkOut( pFileInfo->getName(), workingCopy); + + wcout << L"Checked out file " << pFileInfo->getName() << " to " << workingCopy << endl; + + // Indicate that the check out was successful + + checkedOut = true; + } + catch (Exception ex) { + wcerr << L"%% Error checking out file " << pFileInfo->getName() << endl; + wcerr << L" " << ex.getMessage() << endl; + } + + // Return the check out status + + return checkedOut; +} diff --git a/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp b/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp new file mode 100644 index 0000000000..a657619c7a --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ + +#include "alfresco\Alfresco.hpp" +#include "util\DataBuffer.h" +#include "util\Exception.h" +#include "util\Integer.h" + +#include + +using namespace Alfresco; +using namespace std; + +// Define exceptions + +EXCEPTION_CLASS(Alfresco, BadInterfaceException); + +/** + * Class constructor + * + * @param path UNC or mapped drive path to an Alfresco folder on CIFS server + */ +AlfrescoInterface::AlfrescoInterface(String& path) { + + // Clear the file handle + + m_handle = INVALID_HANDLE_VALUE; + + // Check if the path is to a mapped drive + + String alfPath = path; + + if ( alfPath.length() >= 3 && alfPath.substring(1,3).equals( L":\\")) { + + // Try and convert the local path to a UNC path + + m_mappedDrive = alfPath.substring(0, 2); + wchar_t remPath[MAX_PATH]; + DWORD remPathLen = MAX_PATH; + + DWORD sts = WNetGetConnection(( LPWSTR) m_mappedDrive.data(), remPath, &remPathLen); + if ( sts != NO_ERROR) + return; + + // Build the UNC path to the folder + + alfPath = remPath; + if ( alfPath.endsWith( PathSeperator) == false) + alfPath.append( PathSeperator); + + if ( path.length() > 3) + alfPath.append( path.substring( 3)); + } + + // Save the UNC path + + m_uncPath = alfPath; + + // Check if the UNC path is valid + + if ( m_uncPath.startsWith(UNCPathPrefix)) { + + // Strip any trailing separator from the path + + if ( m_uncPath.endsWith(PathSeperator)) + m_uncPath = m_uncPath.substring(0, m_uncPath.length() - 1); + + // Make sure the path is to a folder + + DWORD attr = GetFileAttributes(m_uncPath); + + if ( attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) { + + // Open the path + + m_handle = CreateFile(m_uncPath, FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + } + + // Set the root path + + int pos = m_uncPath.indexOf( PathSeperator, 2); + if ( pos != -1) { + pos = m_uncPath.indexOf( PathSeperator, pos + 1); + if ( pos == -1) + m_rootPath = m_uncPath; + else + m_rootPath = m_uncPath.substring(0, pos); + } + } +} + +/** + * Class destructor + */ +AlfrescoInterface::~AlfrescoInterface() { + + // Close the folder + + if ( m_handle != INVALID_HANDLE_VALUE) + CloseHandle(m_handle); +} + +/** + * Check if the path is a folder on an Alfresco CIFS server + * + * @return bool + */ +bool AlfrescoInterface::isAlfrescoFolder( void) { + + // Check if the handle is valid, if not then the path is not valid + + if ( m_handle == INVALID_HANDLE_VALUE) + return false; + + // Send a special I/O control to the Alfresco share to check that it is an Alfresco CIFS server + + DataBuffer reqbuf(16); + DataBuffer respbuf(256); + + reqbuf.putFixedString(IOSignature, IOSignatureLen); + + bool alfFolder = false; + + try { + sendIOControl( FSCTL_ALFRESCO_PROBE, reqbuf, respbuf); + alfFolder = true; + } + catch ( Exception ex) { + } + + // If the folder is not an Alfresco CIFS folder then close the folder + + if ( alfFolder == false) { + CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; + } + + // Return the folder status + + return alfFolder; +} + +/** + * Return Alfresco file information for the specified file/folder + * + * @param fileName const wchar_t* + * @return PTR_AlfrescoFileInfo + */ +PTR_AlfrescoFileInfo AlfrescoInterface::getFileInformation( const wchar_t* fileName) { + + // Check if the folder handle is valid + + if ( m_handle == INVALID_HANDLE_VALUE) + throw BadInterfaceException(); + + // Build the file information I/O control request + + DataBuffer reqbuf( 256); + DataBuffer respbuf( 512); + + reqbuf.putFixedString( IOSignature, IOSignatureLen); + reqbuf.putString( fileName); + + sendIOControl( FSCTL_ALFRESCO_FILESTS, reqbuf, respbuf); + + // Unpack the request status + + PTR_AlfrescoFileInfo pFileInfo; + + unsigned int reqSts = respbuf.getInt(); + if ( reqSts == StsSuccess) { + + // Create the file information + + pFileInfo.reset( new AlfrescoFileInfo( fileName)); + + // Unpack the file details + + pFileInfo->setType( respbuf.getInt()); + if ( pFileInfo->isType() == TypeFile) { + + // Unpack the working copy details + + if ( respbuf.getInt() == True) { + String workOwner = respbuf.getString(); + String workFrom = respbuf.getString(); + + pFileInfo->setWorkingCopy( workOwner, workFrom); + } + + // Unpack the lock details + + unsigned int lockType = respbuf.getInt(); + String lockOwner; + + if ( lockType != LockNone) + lockOwner = respbuf.getString(); + + pFileInfo->setLockType( lockType, lockOwner); + + // Unpack the content details + + if ( respbuf.getInt() == True) { + LONG64 siz = respbuf.getLong(); + String mimeType = respbuf.getString(); + + pFileInfo->setContent( siz, mimeType); + } + } + } + + // Return the file information + + return pFileInfo; +} + +/** + * Check in a working copy file + * + * @param fileName const wchar_t* + * @param keepCheckedOut bool + */ +void AlfrescoInterface::checkIn( const wchar_t* fileName, bool keepCheckedOut) { + + // Check if the folder handle is valid + + if ( m_handle == INVALID_HANDLE_VALUE) + throw BadInterfaceException(); + + // Build the file information I/O control request + + DataBuffer reqbuf( 256); + DataBuffer respbuf( 128); + + reqbuf.putFixedString( IOSignature, IOSignatureLen); + reqbuf.putString( fileName); + reqbuf.putInt( keepCheckedOut ? True : False); + + sendIOControl( FSCTL_ALFRESCO_CHECKIN, reqbuf, respbuf); + + // Get the status code + + unsigned int stsCode = respbuf.getInt(); + if ( stsCode == StsSuccess) + return; + else { + + // Get the error message, if available + + String errMsg; + + if ( respbuf.getAvailableLength() > 0) + errMsg = respbuf.getString(); + else { + errMsg = "Error code "; + errMsg.append( Integer::toString( stsCode)); + } + + // Throw an exception + + throw Exception( errMsg); + } +} + +/** + * Check out a file and return the working copy file name + * + * @param fileName const wchar_t* + * @param workingCopy String& + */ +void AlfrescoInterface::checkOut( const wchar_t* fileName, String& workingCopy) { + + // Check if the folder handle is valid + + if ( m_handle == INVALID_HANDLE_VALUE) + throw BadInterfaceException(); + + // Build the file information I/O control request + + DataBuffer reqbuf( 256); + DataBuffer respbuf( 256); + + reqbuf.putFixedString( IOSignature, IOSignatureLen); + reqbuf.putString( fileName); + + sendIOControl( FSCTL_ALFRESCO_CHECKOUT, reqbuf, respbuf); + + // Get the status code + + unsigned int stsCode = respbuf.getInt(); + if ( stsCode == StsSuccess) { + + // Get the working copy file name + + workingCopy = respbuf.getString(); + } + else { + + // Get the error message, if available + + String errMsg; + + if ( respbuf.getAvailableLength() > 0) + errMsg = respbuf.getString(); + else { + errMsg = "Error code "; + errMsg.append( Integer::toString( stsCode)); + } + + // Throw an exception + + throw Exception( errMsg); + } +} + +/** + * Send an I/O control request to the Alfresco CIFS server, receive and validate the response + * + * @param ctlCode const unsigned int + * @param reqbuf DataBuffer& + * @param respbuf DataBuffer& + */ +void AlfrescoInterface::sendIOControl( const unsigned int ctlCode, DataBuffer& reqbuf, DataBuffer& respbuf) { + + // Send the I/O control request, receive the response + + DWORD retLen = 0; + + BOOL res = DeviceIoControl( m_handle, ctlCode, reqbuf.getBuffer(), reqbuf.getLength(), + respbuf.getBuffer(), respbuf.getBufferLength(), &retLen, (LPOVERLAPPED) NULL); + + if ( res) { + + // Validate the reply signature + + if ( retLen >= IOSignatureLen) { + respbuf.setLength(retLen); + respbuf.setEndOfBuffer(); + + String sig = respbuf.getString(IOSignatureLen, false); + + if ( sig.equals(IOSignature) == false) + throw Exception( L"Invalid I/O control signature received"); + } + } + else + throw Exception( L"Send I/O control error", Integer::toString( GetLastError())); +} + +/** + * Class constructor + * + * @param fileName const wchar_t* + */ +AlfrescoFileInfo::AlfrescoFileInfo(const wchar_t* fileName) { + m_name = fileName; + + m_workingCopy = false; + m_lockType = LockNone; + + m_hasContent = false; + m_contentLen = 0L; +} + +/** + * Set the working copy owner and copied from document path + * + * @param owner const wchar_t* + * @param copiedFrom const wchar_t* + */ +void AlfrescoFileInfo::setWorkingCopy( const wchar_t* owner, const wchar_t* copiedFrom) { + m_workingCopy = false; + m_workOwner = L""; + m_copiedFrom = L""; + + if ( owner != NULL) { + m_workingCopy = true; + m_workOwner = owner; + if ( copiedFrom != NULL) + m_copiedFrom = copiedFrom; + } +} + +/** + * Set the lock type and owner + * + * @param typ unsigned int + * @param owner const wchar_t* + */ +void AlfrescoFileInfo::setLockType( unsigned int typ, const wchar_t* owner) { + m_lockType = typ; + m_lockOwner = owner; +} + +/** +* Set the lock type and owner +* +* @param siz LONG64 +* @param mimeType const wchar_t* +*/ +void AlfrescoFileInfo::setContent( LONG64 siz, const wchar_t* mimeType) { + m_hasContent = true; + m_contentLen = siz; + m_contentMimeType = mimeType; +} + +/** + * Equality operator + * + * @return bool + */ +bool AlfrescoFileInfo::operator==( const AlfrescoFileInfo& finfo) { + if ( getName().equals( finfo.getName())) + return true; + return false; +} + +/** + * Less than operator + * + * @return bool + */ +bool AlfrescoFileInfo::operator<( const AlfrescoFileInfo& finfo) { + if ( finfo.getName().compareTo( getName()) < 0) + return true; + return false; +} diff --git a/source/cpp/CAlfrescoApp/source/util/ByteArray.cpp b/source/cpp/CAlfrescoApp/source/util/ByteArray.cpp new file mode 100644 index 0000000000..032c6314cf --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/ByteArray.cpp @@ -0,0 +1,232 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "util\ByteArray.h" +#include + +using namespace Alfresco; + +/** + * Class constructor + * + * @param len BUFLEN + * @param clearMem bool + */ +ByteArray::ByteArray( BUFLEN len, bool clearMem) { + + // Allocate a byte array of the specified size + + if ( len > 0) { + m_data = new unsigned char[ len]; + + if ( clearMem) + memset( m_data, 0, len); + } + else + m_data = NULL; + m_length = len; +} + +/** + * Class constructor + * + * @param data CBUFPTR + * @param len BUFLEN + */ +ByteArray::ByteArray( CBUFPTR data, BUFLEN len) { + m_data = NULL; + m_length = 0; + + setData( data, len); +} + +/** +* Class constructor +* +* @param data const char* +* @param len BUFLEN +*/ +ByteArray::ByteArray( const char* data, BUFLEN len) { + m_data = NULL; + m_length = 0; + + setData(( CBUFPTR) data, len); +} + +/** + * Copy constructor + * + * @param byts const ByteArray& + */ +ByteArray::ByteArray( const ByteArray& byts) { + m_data = NULL; + m_length = 0; + + setData( byts.getData(), byts.getLength()); +} + +/** + * Class destructor + * + * @return + */ +ByteArray::~ByteArray() { + if ( m_data != NULL) + delete[] m_data; +} + +/** + * Subscript operator + * + * @param idx const unsigned int + * @return unsigned char& + */ +unsigned char& ByteArray::operator [](const unsigned int idx) { + return m_data[ idx]; +} + +/** + * Assignment operator + * + * @param byts const ByteArray& + * @return ByteArray& + */ +ByteArray& ByteArray::operator = (const ByteArray& byts) { + if ( byts.getLength() > 0) + setData( byts.getData(), byts.getLength()); + else + m_length = 0; + + return *this; +} + +/** + * Assignment operator + * + * @param byts std::string& + * @return ByteArray& + */ +ByteArray& ByteArray::operator = ( std::string& byts) { + if ( byts.length() > 0) + setData(( CBUFPTR) byts.data(), ( BUFLEN) byts.length()); + else + m_length = 0; + + return *this; +} + +/** + * Equality operator + * + * @param byts const ByteArray& + * @return bool + */ +bool ByteArray::operator== ( const ByteArray& byts) { + + // Check if the arrays are the same length + + if ( getLength() != byts.getLength()) + return false; + + // Check if the array is empty + + if (getLength() == 0) + return true; + + // Check if the array bytes are equal + + if ( memcmp( getData(), byts.getData(), getLength()) == 0) + return true; + return false; +} + +/** + * Set the array length, and optionally clear the memory + * + * @param len BUFLEN + * @param clearMem bool + */ +void ByteArray::setLength( BUFLEN len, bool clearMem) { + + // Check if the current block is the correct length + + if ( m_length != len) { + + // Delete the current array + + if ( m_data != NULL) + delete[] m_data; + + // Allocate the new array + + if ( len > 0) + m_data = new unsigned char[len]; + else + m_data = NULL; + m_length = len; + } + + // Check if the memory should be cleared + + if ( clearMem && m_data != NULL) + memset( m_data, 0, m_length); +} + +/** + * Set the data and length + * + * @param data CBUFPTR + * @param len BUFLEN + */ +void ByteArray::setData( CBUFPTR data, BUFLEN len) { + + // Delete the existing data + + if ( m_data != NULL) + delete[] m_data; + + // Allocate a byte array of the specified size + + if ( data != NULL && len > 0) { + + // Allocate a buffer and copy the data + + m_data = new unsigned char[ len]; + memcpy( m_data, data, len); + } + else + m_data = NULL; + m_length = len; +} + +/** + * Set a byte value + * + * @param idx unsigned int + * @param val unsigned char + */ +void ByteArray::setByte( unsigned int idx, unsigned char val) { + if ( idx < getLength()) + m_data[idx] = val; +} diff --git a/source/cpp/CAlfrescoApp/source/util/DataBuffer.cpp b/source/cpp/CAlfrescoApp/source/util/DataBuffer.cpp new file mode 100644 index 0000000000..5134d12442 --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/DataBuffer.cpp @@ -0,0 +1,732 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "util\DataBuffer.h" +#include "util\Exception.h" + +using namespace Alfresco; +using namespace std; + +// Use a macro for buffer overflow checks + +#define CHECK_BUFFER(sz) {if ((( m_buflen + m_offset) - m_pos) < sz) throw ArrayIndexOutOfBoundsException(__FILE__, __LINE__, L"DataBuffer overflow"); } +#define CHECK_BUFFER_POS(pos,sz) {if ((( m_buflen + m_offset) - pos) < sz) throw ArrayIndexOutOfBoundsException(__FILE__, __LINE__, L"DataBuffer overflow"); } + +#define EXTEND_CHECK(sz) {if ((( m_buflen + m_offset) - m_pos) < sz) extendBuffer(); } +#define EXTEND_CHECK_POS(pos,sz) {if ((( m_buflen + m_offset) - pos) < sz) extendBuffer(); } + +/** + * Class constructor + * + * @param siz unsigned int + */ +DataBuffer::DataBuffer( unsigned int siz) { + m_buf = new unsigned char[siz]; + m_buflen = siz; + + m_owner = true; + + m_pos = 0; + m_endpos = 0; + m_offset = 0; +} + +/** + * Class constructor + * + * @param buf BUFPTR + * @param off BUFPOS + * @param len BUFLEN + */ +DataBuffer::DataBuffer( BUFPTR buf, BUFPOS off, BUFLEN len) { + m_buf = buf; + m_buflen = len; + + m_owner = false; + + m_pos = off; + m_offset = off; + m_endpos = off + len; +} + +/** + * Class destructor + */ +DataBuffer::~DataBuffer() { + + // Delete the buffer, if owned by this object + + if ( m_owner == true && m_buf != NULL) + delete[] m_buf; +} + +/** + * Return the buffer length + * + * @return BUFLEN + */ +BUFLEN DataBuffer::getLength( void) const { + if ( m_endpos != 0) + return m_endpos - m_offset; + return m_pos - m_offset; +} + +/** + * Return the length in words + * + * @return unsigned int + */ +unsigned int DataBuffer::getLengthInWords( void) const { + return getLength() / 2; +} + +/** + * Return the available buffer length + * + * @return BUFLEN + */ +BUFLEN DataBuffer::getAvailableLength( void) const { + if ( m_endpos == 0) + return 0; + return m_endpos - m_pos; +} + +/** + * Get a byte from the buffer and advance the buffer pointer + * + * @return unsigned char + */ +unsigned char DataBuffer::getByte( void) { + + // Check if there is enough space in the buffer for the data + + CHECK_BUFFER(1); + + // Return the data + + return (unsigned int) m_buf[m_pos++]; +} + +/** + * Get a short/16bit value from the buffer and advance the buffer pointer + * + * @return unsigned int + */ +unsigned int DataBuffer::getShort( void) { + + // Check if there is enough space in the buffer for the data + + CHECK_BUFFER(2); + + // Get a short value from the buffer + + unsigned int sval = DataPacker::getIntelShort( m_buf, m_pos); + m_pos += 2; + return sval; +} + +/** + * Get an integer from the buffer and advance the buffer pointer + * + * @return unsigned int + */ +unsigned int DataBuffer::getInt( void) { + + // Check if there is enough space in the buffer for the data + + CHECK_BUFFER(4); + + // Get a short value from the buffer + + unsigned int ival = DataPacker::getIntelInt( m_buf, m_pos); + m_pos += 4; + return ival; +} + +/** + * Get a long from the buffer and advance the buffer pointer + * + * @return LONG64 + */ +LONG64 DataBuffer::getLong( void) { + + // Check if there is enough space in the buffer for the data + + CHECK_BUFFER(8); + + // Get a long value from the buffer + + LONG64 lval = DataPacker::getIntelLong( m_buf, m_pos); + m_pos += 8; + return lval; +} + +/** + * Get a string from the buffer and advance the buffer pointer + * + * @param uni bool + * @return String + */ +String DataBuffer::getString( bool uni) { + return getString( 255, uni); +} + +/** + * Get a string from the buffer and advance the buffer pointer + * + * @param maxlen unsigned int + * @param uni bool + * @return String + */ +String DataBuffer::getString( unsigned int maxlen, bool uni) { + + // Check for Unicode or ASCII + + String ret; + unsigned int availLen = 0; + + if ( uni) { + + // Word align the current buffer position, calculate the available length + + m_pos = DataPacker::wordAlign(m_pos); + availLen = (m_endpos - m_pos) / 2; + if ( availLen < maxlen) + maxlen = availLen; + + ret = DataPacker::getUnicodeString(m_buf, m_pos, maxlen); + if ( ret.length() < maxlen) + m_pos += (ret.length() * 2) + 2; + else + m_pos += maxlen * 2; + } + else { + + // Calculate the available length + + availLen = m_endpos - m_pos; + if ( availLen < maxlen) + maxlen = availLen; + + // Unpack the ASCII string + + ret = DataPacker::getString(m_buf, m_pos, maxlen); + if ( ret.length() < maxlen) + m_pos += ret.length() + 1; + else + m_pos += maxlen; + } + + // Return the string + + return ret; +} + +/** + * Get a short value at the specified buffer position + * + * @param idx unsigned int + * @return unsigned int + */ +unsigned int DataBuffer::getShortAt( unsigned int idx) { + + // Check if there is enough data in the buffer + + BUFPOS pos = m_offset + (idx * 2); + CHECK_BUFFER_POS(pos, 2); + + // Unpack the short value + + return DataPacker::getIntelShort(m_buf, pos); +} + +/** + * Get an integer value at the specified buffer position + * + * @param idx unsigned int + * @return unsigned int + */ +unsigned int DataBuffer::getIntAt( unsigned int idx) { + + // Check if there is enough data in the buffer + + BUFPOS pos = m_offset + (idx * 4); + CHECK_BUFFER_POS(pos, 4); + + // Unpack the integer value + + return DataPacker::getIntelInt(m_buf, pos); +} + +/** + * Get a long value at the specified buffer position + * + * @param idx unsigned int + * @return LONG64 + */ +LONG64 DataBuffer::getLongAt( unsigned int idx) { + + // Check if there is enough data in the buffer + + BUFPOS pos = m_offset + (idx * 8); + CHECK_BUFFER_POS(pos, 8); + + // Unpack the long value + + return DataPacker::getIntelLong(m_buf, pos); +} + +/** + * Append a byte to the buffer and advance the buffer pointer + * + * @param byt unsigned char + */ +void DataBuffer::putByte( unsigned char byt) { + + // Check if the buffer needs extending + + EXTEND_CHECK(1); + + // Pack the data, update the buffer pointer + + m_buf[m_pos++] = byt; +} + +/** + * Append a short to the buffer and advance the buffer pointer + * + * @param sval unsigned int + */ +void DataBuffer::putShort( unsigned int sval) { + + // Check if the buffer needs extending + + EXTEND_CHECK(2); + + // Pack the data, update the buffer pointer + + DataPacker::putIntelShort( sval, m_buf, m_pos); + m_pos += 2; +} + +/** + * Append an integer to the buffer and advance the buffer pointer + * + * @param ival unsigned int + */ +void DataBuffer::putInt( unsigned int ival) { + + // Check if the buffer needs extending + + EXTEND_CHECK(4); + + // Pack the data, update the buffer pointer + + DataPacker::putIntelInt( ival, m_buf, m_pos); + m_pos += 4; +} + +/** + * Append a long to the buffer and advance the buffer pointer + * + * @param lval LONG64 + */ +void DataBuffer::putLong( LONG64 lval) { + + // Check if the buffer needs extending + + EXTEND_CHECK(8); + + // Pack the data, update the buffer pointer + + DataPacker::putIntelLong( lval, m_buf, m_pos); + m_pos += 8; +} + +/** + * Put a short value into the buffer at the specified position + * + * @param idx unsigned int + * @param sval unsigned int + */ +void DataBuffer::putShortAt( unsigned int idx, unsigned int sval) { + + // Check if there is enough space in the buffer + + BUFPOS pos = m_offset + (idx * 2); + EXTEND_CHECK_POS(pos,2); + + // Pack the short value + + DataPacker::putIntelShort(sval, m_buf, pos); +} + +/** + * Put an integer value into the buffer at the specified position + * + * @param idx unsigned int + * @param ival unsigned int + */ +void DataBuffer::putIntAt( unsigned int idx, unsigned int ival) { + + // Check if there is enough space in the buffer + + BUFPOS pos = m_offset + (idx * 4); + EXTEND_CHECK_POS(pos,4); + + // Pack the integer value + + DataPacker::putIntelInt(ival, m_buf, pos); +} + +/** + * Put a long value into the buffer at the specified position + * + * @param idx unsigned int + * @param lval LONG64 + */ +void DataBuffer::putLongAt( unsigned int idx, LONG64 lval) { + + // Check if there is enough space in the buffer + + BUFPOS pos = m_offset + (idx * 8); + EXTEND_CHECK_POS(pos,8); + + // Pack the long value + + DataPacker::putIntelLong(lval, m_buf, pos); +} + +/** + * Append a string to the buffer and advance the buffer pointer + * + * @param str const String& + * @param uni bool + * @param nulTerm bool + */ +void DataBuffer::putString( const String& str, bool uni, bool nulTerm) { + + // Check for Unicode or ASCII + + if ( uni) { + + // Check if there is enough space in the buffer + + unsigned int bytLen = str.length() * 2; + if ( m_buflen - m_pos < bytLen) + extendBuffer(bytLen + 4); + + // Word align the buffer position, pack the Unicode string + + m_pos = DataPacker::wordAlign(m_pos); + DataPacker::putString(str, m_buf, m_pos, nulTerm, true); + m_pos += (str.length() * 2); + if ( nulTerm) + m_pos += 2; + } + else { + + // Check if there is enough space in the buffer + + if ( m_buflen - m_pos < str.length()) + extendBuffer(str.length() + 2); + + // Pack the ASCII string + + DataPacker::putString(str, m_buf, m_pos, nulTerm); + m_pos += str.length(); + if ( nulTerm) + m_pos++; + } +} + +/** + * Append a fixed length string to the buffer and advance the buffer pointer + * + * @param str const String& + * @param len unsigned int + */ +void DataBuffer::putFixedString( const String& str, unsigned int len) { + + // Check if there is enough space in the buffer + + if ( m_buflen - m_pos < str.length()) + extendBuffer(str.length() + 2); + + // Pack the ASCII string + + DataPacker::putString(str, m_buf, m_pos); + m_pos += len; + + // Pad the string to the required length + + while ( len > str.length()) { + m_buf[m_pos++] = 0; + len--; + } +} + +/** + * Put a string into the buffer at the specified position + * + * @param str const String& + * @param pos BUFPOS + * @param uni bool + * @param nulTerm bool + * @return BUFPOS + */ +BUFPOS DataBuffer::putStringAt( const String& str, BUFPOS pos, bool uni, bool nulTerm) { + + // Check for Unicode or ASCII + + BUFPOS retPos = 0; + + if ( uni) { + + // Check if there is enough space in the buffer + + unsigned int bytLen = str.length() * 2; + if ( m_buflen - pos < bytLen) + extendBuffer(bytLen + 4); + + // Word align the buffer position, pack the Unicode string + + pos = DataPacker::wordAlign(pos); + retPos = DataPacker::putString(str, m_buf, pos, nulTerm); + } + else { + + // Check if there is enough space in the buffer + + if ( m_buflen - pos < str.length()) + extendBuffer(str.length() + 2); + + // Pack the ASCII string + + retPos = DataPacker::putString(str, m_buf, pos, nulTerm); + } + + // Return the end of string buffer position + + return retPos; +} + +/** + * Put a fixed length string into the buffer at the specified position + * + * @param str const String& + * @param len unsigned int + * @param pos BUFPOS + * @return BUFPOS + */ +BUFPOS DataBuffer::putFixedStringAt( const String& str, unsigned int len, BUFPOS pos) { + + // Check if there is enough space in the buffer + + if ( m_buflen - pos < str.length()) + extendBuffer(str.length() + 2); + + // Pack the ASCII string + + pos = DataPacker::putString(str, m_buf, pos); + + // Pad the string + + while ( len > str.length()) { + m_buf[pos++] = 0; + len--; + } + + // Return the end of string buffer position + + return pos; +} + +/** + * Put a string pointer into the buffer + * + * @param off unsigned int + */ +void DataBuffer::putStringPointer( unsigned int off) { + + // Calculate the offset from the start of the data buffer to the string position + + DataPacker::putIntelInt(off - m_offset, m_buf, m_pos); + m_pos += 4; +} + +/** + * Append a block of nulls to the buffer and advance the buffer pointer + * + * @param cnt unsigned int + */ +void DataBuffer::putZeros( unsigned int cnt) { + + // Check if there is enough space in the buffer + + if ( m_buflen - m_pos < cnt) + extendBuffer(cnt); + + // Pack the zero bytes + + for ( unsigned int i = 0; i < cnt; i++) + m_buf[m_pos++] = 0; +} + +/** + * Word align the buffer pointer + * + */ +void DataBuffer::wordAlign( void) { + m_pos = DataPacker::wordAlign(m_pos); +} + +/** + * Longword align the buffer pointer + * + */ +void DataBuffer::longwordAlign( void) { + m_pos = DataPacker::longwordAlign(m_pos); +} + +/** + * Append a block of byte data to the buffer and advance the buffer pointer + * + * @param buf BUFPTR + * @param off BUFPOS + * @param len BUFLEN + */ +void DataBuffer::appendData( BUFPTR buf, BUFPOS off, BUFLEN len) { + + // Check if there is enough space in the buffer + + if ( m_buflen - m_pos < len) + extendBuffer(len); + + // Copy the data to the buffer and update the current write position + + memcpy( m_buf + m_pos, buf + off, len); + m_pos += len; +} + +/** + * Copy data to the user buffer and advance the buffer pointer + * + * @param buf BUFPTR + * @param pos BUFPOS + * @param cnt unsigned int + */ +unsigned int DataBuffer::copyData( BUFPTR buf, BUFPOS pos, unsigned int cnt) { + + // Check if there is any more data to copy + + if ( m_pos == m_endpos) + return 0; + + // Calculate the amount of data to copy + + unsigned int siz = m_endpos - m_pos; + if ( siz > cnt) + siz = cnt; + + // Copy the data to the user buffer and update the current read position + + memcpy( buf + pos, m_buf + m_pos, siz); + m_pos += siz; + + // Return the amount of data copied + + return siz; +} + +/** + * Advance the buffer pointer by the specified amount + * + * @param len unsigned int + */ +void DataBuffer::skipBytes( unsigned int len) { + + // Check if there is enough data in the buffer + + CHECK_BUFFER(len); + + // Skip bytes + + m_pos += len; +} + +/** + * Set the end of buffer position + */ +void DataBuffer::setEndOfBuffer( void) { + m_endpos = m_pos; + m_pos = m_offset; +} + +/** + * Set the buffer length + * + * @param len BUFLEN + */ +void DataBuffer::setLength( BUFLEN len) { + m_pos = m_offset + len; +} + +/** + * Extend the buffer by the specified amount by reallocating the buffer and copying the existing + * data to the new buffer + * + * @param ext BUFLEN + */ +void DataBuffer::extendBuffer( BUFLEN ext) { + + // Create a new buffer of the required size + + BUFLEN newlen = m_buflen + ext; + BUFPTR newBuf = new unsigned char[newlen]; + + // Copy the data from the current buffer to the new buffer + + memcpy( newBuf, m_buf, m_buflen); + + // Check if the previous buffer was owned by this object + + if ( m_owner) + delete[] m_buf; + + // Set the new buffer to be the main buffer + + m_buf = newBuf; + m_buflen = newlen; + m_owner = true; +} + +/** + * Extend the buffer doubling the current size by reallocating the buffer and copying the existing + * data to the new buffer + * + */ +void DataBuffer::extendBuffer( void) { + extendBuffer( m_buflen * 2); +} diff --git a/source/cpp/CAlfrescoApp/source/util/DataPacker.cpp b/source/cpp/CAlfrescoApp/source/util/DataPacker.cpp new file mode 100644 index 0000000000..89eaf1f9dc --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/DataPacker.cpp @@ -0,0 +1,436 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include +#include "util\DataPacker.h" +#include "util\ByteArray.h" + +using namespace Alfresco; + +/** + * Unpack a short/16 bit value from the buffer. + * + * @param buf CBUFPTR + * @param pos BUFPOS + * @return int + */ +int DataPacker::getShort(CBUFPTR buf, BUFPOS pos) { + int sval = ( buf[pos] << 8) + buf[pos+1]; + return sval; +} + +/** + * Unpack an int/32 bit value from the buffer. + * + * @param buf CBUFPTR + * @param pos BUFPOS + * @return int + */ +int DataPacker::getInt(CBUFPTR buf, BUFPOS pos) { + int ival = (buf[pos] << 24) + (buf[pos+1] << 16) + (buf[pos+2] << 8) + buf[pos+3]; + return ival; +} + +/** + * Unpack a long/64 bit value from the buffer. + * + * @param buf CBUFPTR + * @param pos BUFPOS + * @return LONG64 + */ +LONG64 DataPacker::getLong(CBUFPTR buf, BUFPOS pos) { + LONG64 lval = 0; + BUFPTR pLval = (BUFPTR) &lval; + + for ( unsigned int i = 0; i < 8; i++) { + pLval[7 - i] = buf[pos + i]; + } + return lval; +} + +/** + * Unpack a short/16 bit value in Intel format from the buffer. + * + * @param buf CBUFPTR + * @param pos BUFPOS + * @return int + */ +int DataPacker::getIntelShort(CBUFPTR buf, BUFPOS pos) { + int sval = ( buf[pos+1] << 8) + buf[pos]; + return sval; +} + +/** + * Unpack an int/32 bit value in Intel format from the buffer. + * + * @param buf CBUFPTR + * @param pos BUFPOS + * @return int + */ +int DataPacker::getIntelInt(CBUFPTR buf, BUFPOS pos) { + int ival = (buf[pos+3] << 24) + (buf[pos+2] << 16) + (buf[pos+1] << 8) + buf[pos]; + return ival; +} + +/** + * Unpack a long/64 bit value in Intel format from the buffer. + * + * @param buf CBUFPTR + * @param pos BUFPOS + * @return LONG64 + */ +LONG64 DataPacker::getIntelLong(CBUFPTR buf, BUFPOS pos) { + LONG64 lval = 0; + BUFPTR pLval = (BUFPTR) &lval; + + for ( unsigned int i = 0; i < 8; i++) { + pLval[i] = buf[pos + i]; + } + return lval; +} + +/** + * Unpack a string from the buffer. + * + * @param buf CBUFPTR + * @param pos BUFPOS + * @param maxLen const unsigned int + * @param isUni const bool + * @return String + */ +String DataPacker::getString(CBUFPTR buf, BUFPOS pos, const unsigned int maxLen, const bool isUni) { + + // Check for a Unicode string + + if ( isUni) + return getUnicodeString( buf, pos, maxLen); + + // Search for the trailing null + + unsigned int maxpos = pos + maxLen; + unsigned int endpos = pos; + + while (buf[endpos] != '\0' && endpos < maxpos) + endpos++; + return String((const char*) buf, pos, endpos - pos); +} + +/** + * Unpack a Unicode string from the buffer. + * + * @param buf CBUFPTR + * @param pos BUFPOS + * @param maxLen const unsigned int + * @return String + */ +String DataPacker::getUnicodeString(CBUFPTR buf, BUFPOS pos, const unsigned int maxLen) { + + // Check for an empty string + + if ( maxLen == 0) + return String(); + + // Word align the position + + pos = wordAlign( pos); + + // Search for the trailing null + + int maxpos = pos + (maxLen * 2); + int endpos = pos; + + std::wstring str; + + int cpos = 0; + wchar_t curChar; + + do { + + // Get a Unicode character from the buffer + + curChar = (wchar_t) DataPacker::getIntelShort(buf, endpos); + + // Add the character to the string + + if ( curChar != 0) + str += curChar; + + // Update the buffer pointer + + endpos += 2; + + } while (curChar != 0 && endpos < maxpos); + + // Return the string + + return String(str); +} + +/** + * Pack a short/16 bit value into the buffer. + * + * @param val const int + * @param buf BUFPTR + * @param pos BUFPOS + */ +void DataPacker::putShort(const int val, BUFPTR buf, BUFPOS pos) { + buf[pos] = (unsigned char) (val >> 8) & 0xFF; + buf[pos+1] = (unsigned char) (val & 0xFF); +} + +/** + * Pack an int/32 bit value into the buffer. + * + * @param val const int + * @param buf BUFPTR + * @param pos BUFPOS + */ +void DataPacker::putInt(const int val, BUFPTR buf, BUFPOS pos) { + buf[pos] = (unsigned char) (val >> 24) & 0xFF; + buf[pos+1] = (unsigned char) (val >> 16) & 0xFF; + buf[pos+2] = (unsigned char) (val >> 8) & 0xFF; + buf[pos+3] = (unsigned char) (val & 0xFF); +} + +/** + * Pack a long/64 bit value into the buffer. + * + * @param val const LONG64 + * @param buf BUFPTR + * @param pos BUFPOS + */ +void DataPacker::putLong(const LONG64 val, BUFPTR buf, BUFPOS pos) { + BUFPTR pLval = (BUFPTR) &val; + + buf[pos] = pLval[7]; + buf[pos+1] = pLval[6]; + buf[pos+2] = pLval[5]; + buf[pos+3] = pLval[4]; + buf[pos+4] = pLval[3]; + buf[pos+5] = pLval[2]; + buf[pos+6] = pLval[1]; + buf[pos+7] = pLval[0]; +} + +/** + * Pack a short/16 bit value in Intel format into the buffer. + * + * @param val const int + * @param buf BUFPTR + * @param pos BUFPOS + */ +void DataPacker::putIntelShort(const int val, BUFPTR buf, BUFPOS pos) { + buf[pos+1] = (unsigned char) (val >> 8) & 0xFF; + buf[pos] = (unsigned char) (val & 0xFF); +} + +/** + * Pack an int/32 bit value in Intel format into the buffer. + * + * @param val const int + * @param buf BUFPTR + * @param pos BUFPOS + */ +void DataPacker::putIntelInt(const int val, BUFPTR buf, BUFPOS pos) { + buf[pos+3] = (unsigned char) (val >> 24) & 0xFF; + buf[pos+2] = (unsigned char) (val >> 16) & 0xFF; + buf[pos+1] = (unsigned char) (val >> 8) & 0xFF; + buf[pos] = (unsigned char) (val & 0xFF); +} + +/** + * Pack a long/64 bit value in Intel format into the buffer. + * + * @param val const LONG64 + * @param buf BUFPTR + * @param pos BUFPOS + */ +void DataPacker::putIntelLong(const LONG64 val, BUFPTR buf, BUFPOS pos) { + BUFPTR pLval = (BUFPTR) &val; + + buf[pos+7] = pLval[7]; + buf[pos+6] = pLval[6]; + buf[pos+5] = pLval[5]; + buf[pos+4] = pLval[4]; + buf[pos+3] = pLval[3]; + buf[pos+2] = pLval[2]; + buf[pos+1] = pLval[1]; + buf[pos] = pLval[0]; +} + +/** + * Pack a string into the buffer. + * + * @param str const String& + * @param buf BUFPTR + * @param pos BUFPOS + * @param nullTerm const bool + * @param isUni const bool + * @return int + */ +unsigned int DataPacker::putString(const String& str, BUFPTR buf, BUFPOS pos, const bool nullTerm, const bool isUni) { + + // Check if the string should be packed as Unicode or ASCII + + unsigned int newPos = pos; + + if ( isUni == true) { + + // Pack the characters + + for ( unsigned int i = 0; i < str.length(); i++) { + wchar_t ch = str.charAt(i); + buf[newPos++] = (unsigned char) (ch & 0xFF); + buf[newPos++] = (unsigned char) (ch >> 8) & 0xFF; + } + + // Add a null terminator, if required + + if ( nullTerm == true) { + buf[newPos++] = '\0'; + buf[newPos++] = '\0'; + } + } + else { + + // Get the string as ASCII characters + + ByteArray byts = str.getBytes(); + + // Pack the characters + + for ( unsigned int i = 0; i < str.length(); i++) + buf[newPos++] = byts[i]; + + // Add a null terminator, if required + + if ( nullTerm == true) + buf[newPos++] = '\0'; + } + + // Return the new buffer position + + return newPos; +} + +/** + * Pack an ASCII string into the buffer + * + * @param str const char* + * @param buf BUFPTR + * @param pos BUFPOS + * @param nullTerm bool + * @return unsigned int + */ +unsigned int DataPacker::putString(const char* str, BUFLEN len, BUFPTR buf, BUFPOS pos, bool nullTerm) { + + // Copy the ASCII string to the buffer + + memcpy(buf + pos, str, len); + + BUFPOS endPos = pos + len; + if ( nullTerm == true) + buf[endPos] = '\0'; + + // Return the new buffer position + + return endPos; +} + +/** + * Pack a Unicode string into the buffer + * + * @param str const wchar_t* + * @param buf BUFPTR + * @param pos BUFPOS + * @param nullTerm bool + * @return unsigned int + */ +unsigned int DataPacker::putString(const wchar_t* str, BUFLEN len, BUFPTR buf, BUFPOS pos, bool nullTerm) { + + // Copy the Unicode string to the buffer + + BUFLEN uniLen = len * 2; + BUFPOS endPos = pos + uniLen; + + memcpy(buf + pos, str, uniLen); + if ( nullTerm == true) { + buf[pos + uniLen + 1] = '\0'; + buf[pos + uniLen + 2] = '\0'; + endPos += 2; + } + + // Return the new buffer position + + return endPos; +} + +/** + * Pack a number of zero bytes into the buffer. + * + * @param buf BUFPTR + * @param pos BUFPOS + * @param count const unsigned int + */ +void DataPacker::putZeros(BUFPTR buf, BUFPOS pos, const unsigned int count) { + for (unsigned int i = 0; i < count; i++) + buf[pos + i] = (unsigned char) 0; +} + +/** + * Determine the amount of buffer space required to pack the string with the specified settings. + * + * @param str const String& + * @param isUni const bool + * @param nulTerm const bool + * @return unsigned int + */ +unsigned int DataPacker::getStringLength(const String& str, const bool isUni, const bool nulTerm) { + int len = str.length(); + if ( nulTerm == true) + len += 1; + if ( isUni == true) + len *= 2; + + return len; +} + +/** + * Calculate the buffer position after packing the string with the specified settings. + * + * @param pos BUFPOS + * @param str const String& + * @param isUni const bool + * @param nulTerm const bool + * @return unsigned int + */ +unsigned int DataPacker::getBufferPosition(BUFPOS pos, const String& str, const bool isUni, const bool nulTerm) { + unsigned int len = str.length(); + if ( nulTerm == true) + len += 1; + if ( isUni == true) + len *= 2; + + return pos + len; +} diff --git a/source/cpp/CAlfrescoApp/source/util/Exception.cpp b/source/cpp/CAlfrescoApp/source/util/Exception.cpp new file mode 100644 index 0000000000..74e76464c2 --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/Exception.cpp @@ -0,0 +1,137 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "util\Exception.h" + +using namespace Alfresco; +using namespace std; + +// Define standard Java-like exceptions + +EXCEPTION_CLASS(Alfresco, IOException); +EXCEPTION_CLASS(Alfresco, NullPointerException); +EXCEPTION_CLASS(Alfresco, ArrayIndexOutOfBoundsException); +EXCEPTION_CLASS(Alfresco, NumberFormatException); + +/** +* Class constructor +* +* @param moduleName const char* +* @param lineNum unsigned int +* @param msg const wchar_t* +* @param msg2 const wchar_t* +* @param msg3 const wchar_t* +* @param msg4 const wchar_t* +* @param msg5 const wchar_t* +*/ +Exception::Exception( const char* moduleName, unsigned int lineNum, const wchar_t* msg, const wchar_t* msg2, + const wchar_t* msg3, const wchar_t* msg4, const wchar_t* msg5) { + + // Prefix the message string with the module name and line number + + m_msg = moduleName; + if ( lineNum != 0) { + m_msg += " ("; + m_msg += lineNum; + m_msg += ")"; + } + m_msg += ": "; + + // Add the messages parts + + if ( msg) + m_msg += msg; + + if ( msg2) { + m_msg += " "; + m_msg += msg2; + } + + if ( msg3) { + m_msg += " "; + m_msg += msg3; + } + + if ( msg4) { + m_msg += " "; + m_msg += msg4; + } + + if ( msg5) { + m_msg += " "; + m_msg += msg5; + } +} + +/** + * Class constructor + * + * @param msg const wchar_t* + * @param msg2 const wchar_t* + * @param msg3 const wchar_t* + * @param msg4 const wchar_t* + * @param msg5 const wchar_t* + */ +Exception::Exception( const wchar_t* msg, const wchar_t* msg2, const wchar_t* msg3, const wchar_t* msg4, const wchar_t* msg5) { + if ( msg) + m_msg = msg; + + if ( msg2) { + m_msg += " "; + m_msg += msg2; + } + + if ( msg3) { + m_msg += " "; + m_msg += msg3; + } + + if ( msg4) { + m_msg += " "; + m_msg += msg4; + } + + if ( msg5) { + m_msg += " "; + m_msg += msg5; + } +} + +/** + * Copy constructor + * + * @param ex const Exception& + */ +Exception::Exception( const Exception& ex) { + m_msg = ex.getMessage(); +} + +/** + * Class destructor + * + */ +Exception::~Exception() { +} + + diff --git a/source/cpp/CAlfrescoApp/source/util/FileName.cpp b/source/cpp/CAlfrescoApp/source/util/FileName.cpp new file mode 100644 index 0000000000..9906e0abd4 --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/FileName.cpp @@ -0,0 +1,390 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "util\FileName.h" + +using namespace Alfresco; +using namespace std; + +// Declare the Dos separator and NTFS stream separator strings + +String& Alfresco::FileName::DosSeperator = String("\\"); +String& Alfresco::FileName::NTFSStreamSeperator = String(":"); + +wchar_t Alfresco::FileName::DOS_SEPERATOR = L'\\'; + +/** + * Build a path using the specified components + * + * @param dev const String& + * @param path const String& + * @param fileName const String& + * @param sep wchar_t + * @return const String + */ +const String FileName::buildPath( const String& dev, const String& path, const String& fileName, wchar_t sep) { + + // Build the path string + + String fullPath; + + // Check for a device name + + if ( dev.isNotEmpty()) { + + // Add the device name + + fullPath.append( dev); + + // Check if the device name has a file separator + + if ( dev.length() > 0 && dev.charAt( dev.length() - 1) != sep) + fullPath.append( sep); + } + + // Check for a path + + if ( path.isNotEmpty()) { + + // Add the path + + if (fullPath.length() > 0 + && (path.charAt(0) == sep || path.charAt(0) == DOS_SEPERATOR)) + fullPath.append( path.substring(1)); + else + fullPath.append( path); + + // Add a trailing separator, if required + + if (path.length() > 0 + && path.charAt(path.length() - 1) != sep + && fileName.isNotEmpty()) + fullPath.append(sep); + } + + // Check for a file name + + if (fileName.isNotEmpty()) { + + // Add the file name + + if ( fullPath.length() > 0 && ( fileName.charAt(0) == sep || fileName.charAt(0) == DOS_SEPERATOR)) + fullPath.append( fileName.substring(1)); + else + fullPath.append( fileName); + } + + // Debug + + // Debug.println ( "BuildPath: " + fullPath.toString ()); + + // Convert the file separator characters in the path if we are not using the normal + // DOS file separator character. + + if (sep != DOS_SEPERATOR) + return convertSeperators( fullPath, sep); + return fullPath; +} + +/** + * Check if a file name contains a stream name + * + * @param fileName const String& + * @return bool + */ +bool FileName::containsStreamName( const String& fileName) { + + // Check if the path contains the stream name separator character + + if ( fileName.indexOf( NTFSStreamSeperator) != -1) + return true; + return false; +} + +/** + * Convert path separator characters + * + * @param path const String& + * @param sep wchar_t + * @return const String + */ +const String FileName::convertSeperators( const String& path, wchar_t sep) { + + // Check if the path contains any DOS separators + + if ( path.indexOf( DOS_SEPERATOR) == -1) + return path; + + // Convert DOS path separators to the specified separator + + String newPath; + unsigned int idx = 0; + + while ( idx < path.length()) { + + // Get the current character from the path and check if it is a DOS path + // separator character. + + wchar_t ch = path.charAt(idx++); + if (ch == DOS_SEPERATOR) + newPath.append(sep); + else + newPath.append(ch); + } + + // Return the new path string + + return newPath; +} + +/** + * Make a relative path + * + * @param basePath const String& + * @param fullPath const String& + * @return const String + */ +const String FileName::makeRelativePath( const String& basePath, const String& fullPath) { + + // Check if the base path is the root path + + if ( basePath.length() == 0 || basePath.equals( DosSeperator)) { + + // Return the full path, strip any leading separator + + if ( fullPath.length() > 0 && fullPath.charAt(0) == DOS_SEPERATOR) + return fullPath.substring(1); + return fullPath; + } + + // Split the base and full paths into separate components + + StringList baseNames = splitAllPaths(basePath); + StringList fullNames = splitAllPaths(fullPath); + + // Check that the full path is actually within the base path tree + + if ( baseNames.numberOfStrings() > 0 && fullNames.numberOfStrings() > 0 && + baseNames.getStringAt(0).equalsIgnoreCase(fullNames.getStringAt(0)) == false) + return String(); + + // Match the path names + + unsigned int idx = 0; + + while ( idx < baseNames.numberOfStrings() && idx < fullNames.numberOfStrings() && + baseNames.getStringAt(idx).equalsIgnoreCase(fullNames.getStringAt(idx))) + idx++; + + // Build the relative path + + String relPath(128); + + while ( idx < fullNames.numberOfStrings()) { + relPath.append(fullNames.getStringAt(idx++)); + if ( idx < fullNames.numberOfStrings()) + relPath.append(DOS_SEPERATOR); + } + + // Return the relative path + + return relPath; +} + +/** + * Map an input path to a real path + * + * @param base const String& + * @param path const String& + * @return const String + */ +const String FileName::mapPath(const String& base, const String& path) { + return String(); +} + +/** + * Normalize a path converting all directories to uppercase and keeping the file name as is + * + * @param path const String& + * @return const String + */ +const String FileName::normalizePath(const String& path) { + + // Split the path into directories and file name, only uppercase the directories to normalize + // the path. + + String normPath = path; + + if ( path.length() > 3) { + + // Split the path to separate the folders/file name + + int pos = path.lastIndexOf( DOS_SEPERATOR); + if ( pos != -1) { + + // Get the path and file name parts, normalize the path + + String pathPart = path.substring(0, pos).toUpperCase(); + String namePart = path.substring(pos); + + // Rebuild the path string + + normPath = pathPart; + normPath += namePart; + } + } + + // Return the normalized path + + return normPath; +} + +/** + * Remove the file name from the path + * + * @param path const String& + * @return const String + */ +const String FileName::removeFileName(const String& path) { + + // Find the last path separator + + int pos = path.lastIndexOf(DOS_SEPERATOR); + if (pos != -1) + return path.substring(0, pos); + + // Return an empty string, no path separators + + return ""; +} + +/** + * Split the path into all the component directories and filename + * + * @param path const String& + * @return StringList + */ +StringList FileName::splitAllPaths(const String& path) { + + // Check if the path is valid + + StringList paths; + + if ( path.length() == 0) { + paths.addString( path); + return paths; + } + + // Split the path + + return path.tokenize( DosSeperator); +} + +/** + * Split the path into separate directory path and file name strings + * + * @param path const String& + * @param sep wchar_t + * @return StringList + */ +StringList FileName::splitPath( const String& path, wchar_t sep) { + + // Create an array of strings to hold the path and file name strings + + StringList pathList; + String path0, path1; + + // Check if the path is valid + + if ( path.length() > 0) { + + // Check if the path has a trailing separator, if so then there is no + // file name. + + int pos = path.lastIndexOf(sep); + + if (pos == -1 || pos == (path.length() - 1)) { + + // Set the path string in the returned string array + + path0 = path; + } + else { + + // Split the path into directory list and file name strings + + path1 = path.substring(pos + 1); + + if (pos == 0) + path0 = path.substring(0, pos + 1); + else + path0 = path.substring(0, pos); + } + } + + // Set the path strings + + pathList.addString( path0); + pathList.addString( path1); + + // Return the path strings + + return pathList; +} + +/** + * Split a path string into directory path, file name and stream name components + * + * @param path const String& + * @return StringList + */ +StringList FileName::splitPathStream( const String& path) { + + // Allocate the return list + + StringList pathList; + + // Split the path into directory path and file/stream name + + pathList = FileName::splitPath(path, DOS_SEPERATOR); + + if ( pathList[1].length() == 0) + return pathList; + + // Split the file name into file and stream names + + int pos = pathList[1].indexOf( NTFSStreamSeperator); + + if ( pos != -1) { + + // Split the file/stream name + + pathList[2] = pathList[1].substring(pos); + pathList[1] = pathList[1].substring(0,pos); + } + + // Return the path components list + + return pathList; +} diff --git a/source/cpp/CAlfrescoApp/source/util/Integer.cpp b/source/cpp/CAlfrescoApp/source/util/Integer.cpp new file mode 100644 index 0000000000..7fbea17220 --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/Integer.cpp @@ -0,0 +1,76 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "util\Integer.h" + +using namespace Alfresco; + +/** + * Convert an integer value to a hexadecimal string + * + * @param ival const unsigned int + * @return String + */ +String Integer::toHexString( const unsigned int ival) { + char buf[32]; + itoa(ival, buf, 16); + return String(buf); +} + +/** +* Convert an buffer pointer to a hexadecimal string +* +* @param ptr BUFPTR +* @return String +*/ +String Integer::toHexString( BUFPTR ptr) { + char buf[32]; + sprintf( buf, "%p", ptr); + return String(buf); +} + +/** + * Convert an integer to a string + * + * @param ival unsigned int + * @param radix unsigned int + * @return String + */ +String Integer::toString( unsigned int ival, unsigned int radix) { + char buf[32]; + itoa(ival, buf, radix); + return String(buf); +} + +/** + * Parse a string to generate an integer value + * + * @param str const String& + * @param radix unsigned int + * @return unsigned int + */ +unsigned int Integer::parseInt( const String& str, unsigned int radix) { + wchar_t* pEndPtr = NULL; + return (unsigned int) wcstoul( str.data(), &pEndPtr, radix); +} diff --git a/source/cpp/CAlfrescoApp/source/util/Long.cpp b/source/cpp/CAlfrescoApp/source/util/Long.cpp new file mode 100644 index 0000000000..623f6631b3 --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/Long.cpp @@ -0,0 +1,96 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "util\Long.h" + +using namespace Alfresco; + +/** + * Convert a long/64 bit integer value to a hexadecimal string + * + * @param lval const LONG64 + * @return String + */ +String Long::toHexString( const LONG64 lval) { + char buf[32]; + sprintf( buf, "%I64x", lval); + return String(buf); +} + +/** +* Convert a long/64 bit integer value to a decimal string +* +* @param lval const LONG64 +* @return String +*/ +String Long::toString( const LONG64 lval) { + char buf[32]; + sprintf( buf, "%I64d", lval); + return String(buf); +} + +/** + * Make a long/64bit value from the low/high 32bit values + * + * @param lowPart unsigned int + * @param highPart unsigned int + * @return LONG64 + */ +LONG64 Long::makeLong( unsigned int lowPart, unsigned int highPart) { + LONG64 lVal = (LONG64) lowPart + (((LONG64) highPart) << 32); + return lVal; +} + +/** +* Make a long/64bit value from the low/high 32bit values of the FILETIME structure +* +* @param fTime FILETIME +* @return LONG64 +*/ +LONG64 Long::makeLong( FILETIME fTime) { + LONG64 lVal = (LONG64) fTime.dwLowDateTime + (((LONG64) fTime.dwHighDateTime) << 32); + return lVal; +} + +/** + * Parse a string to generate a long/64 bit integer value + * + * @param str const String& + * @param radix unsigned int + * @return LONG64 + */ +LONG64 Long::parseLong( const String& str, unsigned int radix) { + wchar_t* pEndPtr = NULL; + return _wcstoui64( str.data(), &pEndPtr, radix); +} + +/** + * Copy a long/64bit value to a FILETIME structure + * + * @param lval LONG64 + * @param ftime FILETIME& + */ +void Long::copyTo( LONG64 lval, FILETIME& ftime) { + memcpy( &ftime, &lval, sizeof( LONG64)); +} diff --git a/source/cpp/CAlfrescoApp/source/util/String.cpp b/source/cpp/CAlfrescoApp/source/util/String.cpp new file mode 100644 index 0000000000..986e4a556e --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/String.cpp @@ -0,0 +1,889 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "util\String.h" + +using namespace Alfresco; +using namespace std; + +/** + * Default constructor + */ +String::String() { + m_string = std::wstring(); +} + +/** + * Class constructor + * + * @param alloc const unsigned int + */ +String::String(const unsigned int alloc) { + m_string = std::wstring(); + m_string.reserve( alloc); +} + +/** + * Class constructor + * + * @param str const char* + */ +String::String(const char* str) { + + // Expand the characters to wide characters and append to the string + + wchar_t wch; + + while ( *str != '\0') { + wch = (wchar_t) *str++; + m_string += wch; + } +} + +/** +* Class constructor +* +* @param str const unsigned char* +*/ +String::String(const unsigned char* str) { + + // Expand the characters to wide characters and append to the string + + wchar_t wch; + + while ( *str != '\0') { + wch = (wchar_t) *str++; + m_string += wch; + } +} + +/** + * Class constructor + * + * @param buf const char* + * @param offset const unsigned int + * @param len const unsigned int + */ +String::String(const char* buf, const unsigned int offset, const unsigned int len) { + + // Expand the characters to wide characters and append to the string + + wchar_t wch; + + const char* str = buf + offset; + unsigned int sLen = len; + + while ( sLen--) { + wch = (wchar_t) *str++; + m_string += wch; + } +} + +/** + * Class constructor + * + * @param str const wchar_t* + */ +String::String(const wchar_t* str) { + m_string = std::wstring( str); +} + +/** + * Class constructor + * + * @param buf const wchar_t* + * @param offset const unsigned int + * @param len const unsigned int + */ +String::String(const wchar_t* buf, const unsigned int offset, const unsigned int len) { + m_string = std::wstring(buf + offset, len); +} + +/** + * Class constructor + * + * @param str const std::wstring& + */ +String::String(const std::wstring& str) { + m_string = str; +} + +/** + * Copy constructor + * + * @param str const String& + */ +String::String(const String& str) { + m_string = std::wstring(str.data()); +} + +/** +* Class constructor +* +* @param byts ByteArray& +*/ +String::String( ByteArray& byts) { + + // Expand the characters to wide characters and append to the string + + wchar_t wch; + + for ( unsigned int idx = 0; idx < byts.getLength(); idx++) { + wch = (wchar_t) byts[idx]; + m_string += wch; + } +} + +/** + * Compare strings for equality + * + * @param str const wchar_t* + * @return bool + */ +bool String::equals(const wchar_t* str) const { + + // Check that the string is valid + + if ( str == NULL) + return false; + + // Compare the strings + + if ( m_string.compare(str) == 0) + return true; + return false; +} + +/** + * Compare strings for equality + * + * @param str const String& + * @return bool + */ +bool String::equals(const String& str) const { + + // Compare the strings + + if ( m_string.compare(str.data()) == 0) + return true; + return false; +} + +/** + * Compare strings for equality ignoring case + * + * @param str const wchar_t* + * @return bool + */ +bool String::equalsIgnoreCase(const wchar_t* str) const { + return _wcsicmp( str, data()) == 0 ? true : false; +} + +/** + * Compare strings for equality ignoring case + * + * @param str const String& + * @return bool + */ +bool String::equalsIgnoreCase(const String& str) const { + return _wcsicmp( str.data(), data()) == 0 ? true : false; +} + +/** + * Compare strings + * + * @param str const String& + * @return int + */ +int String::compareTo( const String& str) const { + return m_string.compare( str.getString()); +} + +/** + * Compare strings + * + * @param pStr const wchar_t* + * @return int + */ +int String::compareTo( const wchar_t* pStr) const { + return m_string.compare( pStr); +} + +/** + * Convert the string to lower case returning the resulting String + * + * @return String + */ +String String::toLowerCase() const { + + // Create a copy of the string then convert to lowercase + + std::wstring lstr(m_string); + + for ( unsigned int i = 0; i < lstr.length(); i++) + lstr[i] = tolower(lstr[i]); + + return String(lstr); +} + +/** + * Convert the string to upper case returning the resulting String + * + * @return String + */ +String String::toUpperCase() const { + + // Create a copy of the string then convert to uppercase + + std::wstring ustr(m_string); + + for ( unsigned int i = 0; i < ustr.length(); i++) + ustr[i] = toupper(ustr[i]); + + return String(ustr); +} + +/** + * Return the index of the specified character, or -1 if not found + * + * @param ch const wchar_t + * @param startIndex int + * @return int + */ +int String::indexOf(const wchar_t ch, int startIndex) const { + return (int) m_string.find_first_of( ch, startIndex); +} + +/** + * Return the index of the specified string, or -1 if not found + * + * @param str const wchar_t* + * @param startIndex int + * @return int + */ +int String::indexOf(const wchar_t* str, int startIndex) const { + return (int) m_string.find_first_of( str, startIndex); +} + +/** + * Return the index of the specified string, or -1 if not found + * + * @param str const String& + * @param startIndex int + * @return int + */ +int String::indexOf(const String& str, int startIndex) const { + return (int) m_string.find_first_of( str, startIndex); +} + +/** + * Return the last index of the specified character, or -1 if not found + * + * @param ch const wchar_t + * @param startIndex int + * @return int + */ +int String::lastIndexOf(const wchar_t ch, int startIndex) const { + return (int) m_string.find_last_of( ch, startIndex); +} + +/** + * Return the last index of the specified string, or -1 if not found + * + * @param str const wchar_t* + * @param startIndex int + * @return int + */ +int String::lastIndexOf(const wchar_t* str, int startIndex) const { + return (int) m_string.find_last_of( str, startIndex); +} + +/** + * Return the last index of the specified string, or -1 if not found + * + * @param str const String& + * @param startIndex int + * @return int + */ +int String::lastIndexOf(const String& str, int startIndex) const { + return (int) m_string.find_last_of( str, startIndex); +} + +/** + * Check if this string starts with the specified string. + * + * @param str const wchar_t* + * @return bool + */ +bool String::startsWith(const wchar_t* str) const { + + // Check if the string to check is valid + + if ( str == NULL) + return false; + + // Get the string length, if the comparison string is longer than this string + // then there is no match. + + size_t len = wcslen(str); + if ( str == NULL || wcslen(str) > m_string.length()) + return false; + + // Check if this string starts with the specified string + + if ( m_string.compare(0, len, str) == 0) + return true; + return false; +} + +/** + * Check if this string starts with the specified string. + * + * @param str const String& + * @return bool + */ +bool String::startsWith(const String& str) const { + + // Get the string length, if the comparison string is longer than this string + // then there is no match. + + if ( str.length() > m_string.length()) + return false; + + // Check if this string starts with the specified string + + if ( m_string.compare(0, str.length(), str.data()) == 0) + return true; + return false; +} + +/** + * Check if this string starts with the specified string, ignoring case. + * + * @param str const wchar_t* + * @return bool + */ +bool String::startsWithIgnoreCase(const wchar_t* str) const { + + // Check if the string to check is valid + + if ( str == NULL) + return false; + + // Get the string length, if the comparison string is longer than this string + // then there is no match. + + size_t len = wcslen(str); + if ( str == NULL || wcslen(str) > m_string.length()) + return false; + + // Check if this string starts with the specified string + + if ( _wcsnicmp(str, data(), len) == 0) + return true; + return false; +} + +/** + * Check if this string starts with the specified string, ignoring case. + * + * @param str const String& + * @return bool + */ +bool String::startsWithIgnoreCase(const String& str) const { + + // Get the string length, if the comparison string is longer than this string + // then there is no match. + + if ( str.length() > m_string.length()) + return false; + + // Check if this string starts with the specified string + + if ( _wcsnicmp( str.data(), data(), str.length()) == 0) + return true; + return false; +} + +/** + * Check if this string ends with the specified string. + * + * @param str const wchar_t* + * @return bool + */ +bool String::endsWith(const wchar_t* str) const { + + // Check if the string to check is valid + + if ( str == NULL) + return false; + + // Get the string length, if the comparison string is longer than this string + // then there is no match. + + size_t len = wcslen(str); + if ( str == NULL || wcslen(str) > m_string.length()) + return false; + + // Check if this string ends with the specified string + + if ( m_string.compare(m_string.length() - len, len, str) == 0) + return true; + return false; +} + +/** + * Check if this string ends with the specified string. + * + * @param str const String& + * @return bool + */ +bool String::endsWith(const String& str) const { + + // Get the string length, if the comparison string is longer than this string + // then there is no match. + + if ( str.length() > m_string.length()) + return false; + + // Check if this string ends with the specified string + + if ( m_string.compare(m_string.length() - str.length(), str.length(), str.data()) == 0) + return true; + return false; +} + +/** + * Trim leading and trailing whitespace from the string returning the resulting String + * + * @return String + */ +String String::trim( void) const { + std::wstring str = m_string; + str.erase( str.find_last_not_of( L" ") + 1); + + return String( str); +} + +/** + * Return a substring of this string + * + * @param beginIndex unsigned int + * @return String + */ +String String::substring( unsigned int beginIndex) const { + std::wstring str = m_string.substr( beginIndex); + return String(str); +} + +/** + * Return a substring of this string + * + * @param beginIndex unsigned int + * @param endIndex unsigned int + * @return String + */ +String String::substring( unsigned int beginIndex, unsigned int endIndex) const { + std::wstring str = m_string.substr( beginIndex, (endIndex - beginIndex)); + return String(str); +} + +/** + * Assignment operator + * + * @param str const wchar_t* + * @return String& + */ +String& String::operator=(const wchar_t* str) { + m_string = str; + return *this; +} + +/** + * Assignment operator + * + * @param str const String& + * @return String& + */ +String& String::operator=(const String& str) { + m_string = str.data(); + return *this; +} + +/** + * Return the string as an array of 8 bit bytes. + * + * @param byts ByteArray& + * @return ByteArray + */ +ByteArray String::getBytes( ByteArray& byts) const { + + // Create a byte array to hold the byte data + + byts.setLength( length()); + + // Convert the wide characters to ASCII characters + + for ( unsigned int i = 0; i < length(); i++) + byts[ i] = (char) (charAt(i) & 0xFF); + return byts; +} + +/** +* Return the string as an array of 8 bit bytes. +* +* @return ByteArray +*/ +ByteArray String::getBytes( void) const { + + // Create a byte array to hold the byte data + + ByteArray byts; + byts.setLength( length()); + + // Convert the wide characters to ASCII characters + + for ( unsigned int i = 0; i < length(); i++) + byts[ i] = (char) (charAt(i) & 0xFF); + return byts; +} + +/** + * Equality operator + * + * @param str const String& + * @return bool + */ +bool String::operator== ( const String& str) const { + return equals( str); +} + +/** + * Equality operator + * + * @param str const wchar_t* + * @return bool + */ +bool String::operator== ( const wchar_t* str) const { + return equals( str); +} + +/** + * Equality operator + * + * @param str const char* + * @return bool + */ +bool String::operator== ( const char* str) const { + return equals( String( str)); +} + +/** + * Wide character output stream operator + * + * @param out wostream& + * @param str const String& + * @return wostream& + */ +std::wostream& Alfresco::operator<< ( std::wostream& out, const Alfresco::String& str) { + return out << str.data(); +} + +/** + * Less than operator + * + * @param str const String& + * @return bool + */ +bool String::operator<( const String& str) const { + return getString().compare( str.getString()) < 0 ? true : false; +} + +/** + * ASCII character output stream operator + * + * @param out ostream& + * @param str const String& + * @return ostream& + */ +std::ostream& Alfresco::operator<< ( std::ostream& out, const Alfresco::String& str) { + std::string ascStr; + ascStr.reserve( str.length()); + + for ( unsigned int i = 0; i < str.length(); i++) + ascStr += (char) ( str.charAt( i) & 0xFF); + return out << ascStr.c_str(); +} + +/** +* Replace occurrences of the character oldCh with newCh +* +* @param oldCh wchar_t +* @param newCh wchar_t +*/ +void String::replace( wchar_t oldCh, wchar_t newCh) { + if ( m_string.size() == 0) + return; + for ( unsigned int i = 0; i < m_string.size(); i++) { + if ( m_string.at( i) == oldCh) + m_string[i] = newCh; + } +} + +/** + * Append a character to this string + * + * @param ch wchar_t + */ +void String::append( wchar_t ch) { + m_string += ch; +} + +/** + * Append a string to this string + * + * @param str const char* + */ +void String::append ( const char* str) { + + // Expand the characters to wide characters and append to the string + + wchar_t wch; + + while ( *str != '\0') { + wch = (wchar_t) *str++; + m_string += wch; + } +} + +/** + * Append a string to this string + * + * @param str const wchar_t* + */ +void String::append (const wchar_t* str) { + m_string += str; +} + +/** + * Append a string to this string + * + * @param str const String& + */ +void String::append (const String& str) { + m_string += str.getString(); +} + +/** + * Append an integer value to this string + * + * @param ival const unsigned int + */ +void String::append (const unsigned int ival) { + wchar_t buf[32]; + swprintf( buf, L"%u", ival); + + m_string += buf; +} + +/** +* Append a long value to this string +* +* @param lval const unsigned long +*/ +void String::append (const unsigned long lval) { + wchar_t buf[32]; + swprintf( buf, L"%lu", lval); + + m_string += buf; +} + +/** +* Append a long/64 bit value to this string +* +* @param l64val const unsigned long +*/ +void String::append (const LONG64 l64val) { + wchar_t buf[32]; + swprintf( buf, L"%I64u", l64val); + + m_string += buf; +} + +/** + * Split a string into tokens + * + * @param delims const String& + * @return StringList + */ +StringList String::tokenize( const String& delims) const { + + // Skip leading delimiters + + StringList tokens; + string::size_type lastPos = m_string.find_first_not_of( delims, 0); + + // Find a non-delimiter character + + string::size_type pos = m_string.find_first_of( delims, lastPos); + + while ( pos != string::npos || lastPos != string::npos) { + + // Add the current token to the list + + tokens.addString( m_string.substr( lastPos, pos - lastPos)); + + // Skip delimiter(s) + + lastPos = m_string.find_first_not_of( delims, pos); + + // Find next token + + pos = m_string.find_first_of( delims, lastPos); + } + + // Return the token list + + return tokens; +} + +/** + * Default constructor + */ +StringList::StringList( void) { +} + +/** + * Class constructor + * + * @param reserve unsigned int + */ +StringList::StringList( unsigned int reserve) { + m_list.reserve( reserve); +} + +/** + * Copy constructor + * + * @param strList const StringList& + */ +StringList::StringList( const StringList& strList) { + copyFrom( strList); +} + +/** + * Copy strings from the specified list + * + * @param strList const StringList& + */ +void StringList::copyFrom( const StringList& strList) { + for ( unsigned int idx = 0; idx < strList.numberOfStrings(); idx++) + addString( strList.getStringAt( idx)); +} + +/** + * Check if the list contains the string + * + * @param str const String& + * @return bool + */ +bool StringList::containsString ( const String& str) { + for ( std::vector::iterator pos = m_list.begin(); pos < m_list.end(); pos++) { + if ( str.equals( *pos)) + return true; + } + return false; +} + +/** + * Check if the list contains the string, ignoring case + * + * @param str const String& + * @return bool + */ +bool StringList::containsStringCaseless ( const String& str) { + for ( std::vector::iterator pos = m_list.begin(); pos < m_list.end(); pos++) { + if ( str.equalsIgnoreCase( *pos)) + return true; + } + return false; +} + +/** + * Find the specified string and return the position within the list, or -1 if not found + * + * @param str const String& + * @return int + */ +int StringList::indexOf( const String& str) const { + for ( unsigned int i = 0; i < m_list.size(); i++) { + if ( m_list[i].equals( str)) + return (int) i; + } + return -1; +} + +/** + * Remove the specified string from the list + * + * @param str const String& + */ +void StringList::removeString ( const String& str) { + for ( std::vector::iterator pos = m_list.begin(); pos < m_list.end(); pos++) { + if ( str.equals( *pos)) { + m_list.erase( pos); + return; + } + } +} + +/** + * Remove the specified string from the list, ignoring case + * + * @param str const String& + */ +void StringList::removeStringCaseless ( const String& str) { + for ( std::vector::iterator pos = m_list.begin(); pos < m_list.end(); pos++) { + if ( str.equalsIgnoreCase( *pos)) { + m_list.erase( pos); + return; + } + } +} + +/** + * Return the string list as a comma separated string + * + * @return String + */ +String StringList::toString( void) const { + String ret; + + for ( unsigned int i = 0; i < numberOfStrings(); i++) { + ret += getStringAt( i); + ret += ","; + } + + return ret; +} diff --git a/source/cpp/CAlfrescoApp/source/util/System.cpp b/source/cpp/CAlfrescoApp/source/util/System.cpp new file mode 100644 index 0000000000..003e79f839 --- /dev/null +++ b/source/cpp/CAlfrescoApp/source/util/System.cpp @@ -0,0 +1,47 @@ +/* +* Copyright (C) 2005 Alfresco, Inc. +* +* Licensed under the Alfresco Network License. You may obtain a +* copy of the License at +* +* http://www.alfrescosoftware.com/legal/ +* +* Please view the license relevant to your network subscription. +* +* BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, +* READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), +* YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE +* ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO +* THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE +* AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE +* TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" +* BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY +* HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE +* SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE +* TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU +* CHECK THE "I UNDERSTAND AND ACCEPT" BOX. +*/ + +#include "util\System.h" + +#include + +using namespace Alfresco; + +/** + * Return the current system time in milliseconds since Jan 1 1970 + * + * @return DATETIME + */ +DATETIME System::currentTimeMillis( void) { + + // Get the current system time + + struct __timeb64 timeNow; + _ftime64( &timeNow); + + // Build the milliseconds time + + DATETIME timeNowMillis = ( timeNow.time * 1000L) + (DATETIME) timeNow.millitm; + return timeNowMillis; +} \ No newline at end of file diff --git a/source/cpp/CAlfrescoApp/stdafx.cpp b/source/cpp/CAlfrescoApp/stdafx.cpp new file mode 100644 index 0000000000..23a4420cb9 --- /dev/null +++ b/source/cpp/CAlfrescoApp/stdafx.cpp @@ -0,0 +1,7 @@ +// stdafx.cpp : source file that includes just the standard includes +// CAlfrescoApp.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + diff --git a/source/cpp/CAlfrescoApp/stdafx.h b/source/cpp/CAlfrescoApp/stdafx.h new file mode 100644 index 0000000000..1c0fed7e3d --- /dev/null +++ b/source/cpp/CAlfrescoApp/stdafx.h @@ -0,0 +1,44 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, +// but are changed infrequently + +#pragma once + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers +#endif + +// Modify the following defines if you have to target a platform prior to the ones specified below. +// Refer to MSDN for the latest info on corresponding values for different platforms. +#ifndef WINVER // Allow use of features specific to Windows 95 and Windows NT 4 or later. +#define WINVER 0x0400 // Change this to the appropriate value to target Windows 98 and Windows 2000 or later. +#endif + +#ifndef _WIN32_WINNT // Allow use of features specific to Windows NT 4 or later. +#define _WIN32_WINNT 0x0400 // Change this to the appropriate value to target Windows 98 and Windows 2000 or later. +#endif + +#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later. +#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later. +#endif + +#ifndef _WIN32_IE // Allow use of features specific to IE 4.0 or later. +#define _WIN32_IE 0x0400 // Change this to the appropriate value to target IE 5.0 or later. +#endif + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit + +// turns off MFC's hiding of some common and often safely ignored warning messages +#define _AFX_ALL_WARNINGS + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes + +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#include // HTML Dialogs +#include diff --git a/source/java/org/alfresco/repo/cache/TreeCacheAdapter.java b/source/java/org/alfresco/repo/cache/TreeCacheAdapter.java new file mode 100644 index 0000000000..bfc6dcab26 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/TreeCacheAdapter.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.cache; + +import java.io.Serializable; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.jboss.cache.Fqn; +import org.jboss.cache.TreeCache; + +/** + * A thin adapter for TreeCache support. + * + * @author Derek Hulley + */ +public class TreeCacheAdapter + implements SimpleCache +{ + private TreeCache cache; + private Fqn regionFqn; + + public TreeCacheAdapter() + { + } + + /** + * @param cache the backing Ehcache instance + */ + public void setCache(TreeCache cache) + { + this.cache = cache; + } + + /** + * Set the uniquely named region of the cache within which all object must be cached + * + * @param regionName the cache region + */ + public void setRegionName(String regionName) + { + this.regionFqn = new Fqn(regionName); + } + + public boolean contains(K key) + { + try + { + return cache.exists(regionFqn, key); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("contains failed", e); + } + } + + @SuppressWarnings("unchecked") + public V get(K key) + { + try + { + Object element = cache.get(regionFqn, key); + if (element != null) + { + return (V) element; + } + else + { + return null; + } + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Failed to get from TreeCache: \n" + + " key: " + key, + e); + } + } + + public void put(K key, V value) + { + try + { + cache.put(regionFqn, key, value); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Failed to put into TreeCache: \n" + + " key: " + key + "\n" + + " value: " + value, + e); + } + } + + public void remove(K key) + { + try + { + cache.remove(regionFqn, key); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Failed to remove from TreeCache: \n" + + " key: " + key, + e); + } + } + + public void clear() + { + try + { + cache.remove(regionFqn); + } + catch (Throwable e) + { + throw new AlfrescoRuntimeException("Failed to clear cache", e); + } + } +} diff --git a/source/java/org/alfresco/repo/cache/TreeCacheAdapterTest.java b/source/java/org/alfresco/repo/cache/TreeCacheAdapterTest.java new file mode 100644 index 0000000000..4c1218abc1 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/TreeCacheAdapterTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.cache; + +import java.io.Serializable; + +import org.jboss.cache.DummyTransactionManagerLookup; +import org.jboss.cache.Fqn; +import org.jboss.cache.TreeCache; + +import junit.framework.TestCase; + +/** + * @see org.alfresco.repo.cache.TreeCacheAdapter + * + * @author Derek Hulley + */ +public class TreeCacheAdapterTest extends TestCase +{ + private static final String KEY_A = "A"; + private static final String VALUE_A = "AAA"; + private static final String KEY_B = "B"; + private static final String VALUE_B = "BBB"; + + private TreeCache treeCache; + private TreeCacheAdapter cache; + + @Override + public void setUp() throws Exception + { + treeCache = new TreeCache(); + treeCache.setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName()); + treeCache.start(); + + cache = new TreeCacheAdapter(); + cache.setCache(treeCache); + cache.setRegionName(getName()); + } + + public void testSimplePutGet() throws Exception + { + cache.put(KEY_A, VALUE_A); + cache.put(KEY_B, VALUE_B); + + // check that this is present in the underlying cache + Serializable checkValueA = (Serializable) treeCache.get(new Fqn(getName()), KEY_A); + assertNotNull("Value A is not present in underlying cache", checkValueA); + assertEquals("Value A is incorrect in underlying cache", VALUE_A, checkValueA); + + Serializable checkValueB = cache.get(KEY_B); + assertNotNull("Value B is not present in cache", checkValueB); + assertEquals("Value B is incorrect in cache", VALUE_B, checkValueB); + } +} diff --git a/source/java/org/alfresco/repo/content/replication/ContentStoreReplicator.java b/source/java/org/alfresco/repo/content/replication/ContentStoreReplicator.java new file mode 100644 index 0000000000..11a4733d7a --- /dev/null +++ b/source/java/org/alfresco/repo/content/replication/ContentStoreReplicator.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.content.replication; + +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.node.index.IndexRecovery; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * This component performs one-way replication between to content stores. + *

+ * It ensure that the content from the first store is copied to the second + * store where required, therefore primarily acting as a backup or + * replication mechanism. + *

+ * Once started, this process runs continuously on a low-priority thread + * and cannot be restarted. + * + * @author Derek Hulley + */ +public class ContentStoreReplicator +{ + private static Log logger = LogFactory.getLog(ContentStoreReplicator.class); + + private ContentStore sourceStore; + private ContentStore targetStore; + + /** used to ensure that this instance gets started once only */ + private boolean started; + /** set this on to keep replicating and never stop. The default is true. */ + private boolean runContinuously; + /** the time to wait between passes */ + private long waitTime; + + public ContentStoreReplicator() + { + this.started = false; + this.runContinuously = true; + this.waitTime = 60000L; + } + + /** + * Set the source that content must be taken from + * + * @param sourceStore the content source + */ + public void setSourceStore(ContentStore sourceStore) + { + this.sourceStore = sourceStore; + } + + /** + * Set the target that content must be written to + * + * @param targetStore the content target + */ + public void setTargetStore(ContentStore targetStore) + { + this.targetStore = targetStore; + } + + /** + * Set whether the thread should run continuously or terminate after + * a first pass. + * + * @param runContinuously true to run continously (default) + */ + public void setRunContinuously(boolean runContinuously) + { + this.runContinuously = runContinuously; + } + + /** + * Set the time to wait between replication passes (in seconds) + * + * @param waitTime the time between passes (in seconds). Default is 60s. + */ + public void setWaitTime(long waitTime) + { + // convert to millis + this.waitTime = waitTime * 1000L; + } + + /** + * Kick off the replication thread. This method can be used once. + */ + public synchronized void start() + { + if (started) + { + throw new AlfrescoRuntimeException("This ContentStoreReplicator has already been started"); + } + // create a low-priority, daemon thread to do the work + Runnable runnable = new ReplicationRunner(); + Thread thread = new Thread(runnable); + thread.setPriority(Thread.MIN_PRIORITY); + thread.setDaemon(true); + // start it + thread.start(); + } + + /** + * Stateful thread runnable that performs the replication. + * + * @author Derek Hulley + */ + private class ReplicationRunner implements Runnable + { + public void run() + { + // keep this thread going permanently + while (true) + { + try + { + ContentStoreReplicator.this.replicate(); + // check if the process should terminate + if (!runContinuously) + { + // the thread has caught up with all the available work and should not + // run continuously + if (logger.isDebugEnabled()) + { + logger.debug("Thread quitting - first pass of replication complete:"); + } + break; + } + // pause the the required wait time + synchronized(ContentStoreReplicator.this) + { + ContentStoreReplicator.this.wait(waitTime); + } + } + catch (InterruptedException e) + { + // ignore + } + catch (Throwable e) + { + // report + logger.error("Replication failure", e); + } + } + } + } + + /** + * Perform a full replication of all source to target URLs. + */ + private void replicate() + { + // get all the URLs from the source + Set sourceUrls = sourceStore.getUrls(); + // get all the URLs from the target + Set targetUrls = targetStore.getUrls(); + // remove source URLs that are present in the target + sourceUrls.removeAll(targetUrls); + + // ensure that each remaining source URL is present in the target + for (String contentUrl : sourceUrls) + { + replicate(contentUrl); + } + } + + /** + * Checks if the target store has the URL, and if not, replicates the content. + *

+ * Any failures are reported and not thrown, but the target URL is removed for + * good measure. + * + * @param contentUrl the URL to replicate + */ + private void replicate(String contentUrl) + { + try + { + // check that the target doesn't have it + if (targetStore.exists(contentUrl)) + { + // ignore this as the target has it already + if (logger.isDebugEnabled()) + { + logger.debug("No replication required - URL exists in target store: \n" + + " source store: " + sourceStore + "\n" + + " target store: " + targetStore + "\n" + + " content URL: " + contentUrl); + } + return; + } + // get a writer to the target store - this can fail if the content is there now + ContentWriter writer = targetStore.getWriter(null, contentUrl); + // get the source reader + ContentReader reader = sourceStore.getReader(contentUrl); + if (!reader.exists()) + { + // the content may have disappeared from the source store + if (logger.isDebugEnabled()) + { + logger.debug("Source store no longer has URL - no replication possible: \n" + + " source store: " + sourceStore + "\n" + + " target store: " + targetStore + "\n" + + " content URL: " + contentUrl); + } + return; + } + // copy from the reader to the writer + writer.putContent(reader); + } + catch (Throwable e) + { + logger.error("Failed to replicate URL - removing target content: \n" + + " source store: " + sourceStore + "\n" + + " target store: " + targetStore + "\n" + + " content URL: " + contentUrl, + e); + targetStore.delete(contentUrl); + } + } + + /** + * Kicks off the {@link ContentStoreReplicator content store replicator}. + * + * @author Derek Hulley + */ + public class ContentStoreReplicatorJob implements Job + { + /** KEY_CONTENT_STORE_REPLICATOR = 'contentStoreReplicator' */ + public static final String KEY_CONTENT_STORE_REPLICATOR = "contentStoreReplicator"; + + /** + * Forces a full index recovery using the {@link IndexRecovery recovery component} passed + * in via the job detail. + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + ContentStoreReplicator contentStoreReplicator = (ContentStoreReplicator) context.getJobDetail() + .getJobDataMap().get(KEY_CONTENT_STORE_REPLICATOR); + if (contentStoreReplicator == null) + { + throw new JobExecutionException("Missing job data: " + KEY_CONTENT_STORE_REPLICATOR); + } + // reindex + contentStoreReplicator.start(); + } + } +} diff --git a/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java b/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java new file mode 100644 index 0000000000..97854f2065 --- /dev/null +++ b/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.content.replication; + +import java.io.File; +import java.util.Set; + +import junit.framework.TestCase; + +import org.alfresco.repo.content.AbstractContentStore; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.filestore.FileContentStore; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.util.GUID; +import org.alfresco.util.TempFileProvider; + +/** + * Tests the content store replicator. + * + * @see org.alfresco.repo.content.replication.ContentStoreReplicator + * + * @author Derek Hulley + */ +@SuppressWarnings("unused") +public class ContentStoreReplicatorTest extends TestCase +{ + private static final String SOME_CONTENT = "The No. 1 Ladies' Detective Agency"; + + private ContentStoreReplicator replicator; + private ContentStore sourceStore; + private ContentStore targetStore; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + File tempDir = TempFileProvider.getTempDir(); + // create the source file store + String storeDir = tempDir.getAbsolutePath() + File.separatorChar + getName() + File.separatorChar + GUID.generate(); + sourceStore = new FileContentStore(storeDir); + // create the target file store + storeDir = tempDir.getAbsolutePath() + File.separatorChar + getName() + File.separatorChar + GUID.generate(); + targetStore = new FileContentStore(storeDir); + + // create the replicator + replicator = new ContentStoreReplicator(); + replicator.setSourceStore(sourceStore); + replicator.setTargetStore(targetStore); + replicator.setRunContinuously(false); // replicate once + replicator.setWaitTime(0); + } + + /** + * Creates a source with some files and replicates in a single pass, checking the results. + */ + public void testSinglePassReplication() throws Exception + { + ContentWriter writer = sourceStore.getWriter(null, null); + writer.putContent("123"); + + // replicate + replicator.start(); + + // wait a second + synchronized(this) + { + this.wait(1000L); + } + + assertTrue("Target store doesn't have content added to source", + targetStore.exists(writer.getContentUrl())); + + // this was a single pass, so now more replication should be done + writer = sourceStore.getWriter(null, null); + writer.putContent("456"); + + // wait a second + synchronized(this) + { + this.wait(1000L); + } + + assertFalse("Replication should have been single-pass", + targetStore.exists(writer.getContentUrl())); + } + + /** + * Adds content to the source while the replicator is going as fast as possible. + * Just to make it more interesting, the content is sometimes put in the target + * store as well. + *

+ * Afterwards, some content is removed from the the target. + *

+ * Then, finally, a check is performed to ensure that the source and target are + * in synch. + */ + public void testContinuousReplication() throws Exception + { + replicator.setRunContinuously(true); + replicator.setWaitTime(0L); + replicator.start(); + + String duplicateUrl = AbstractContentStore.createNewUrl(); + // start the replicator - it won't wait between iterations + for (int i = 0; i < 10; i++) + { + // put some content into both the target and source + duplicateUrl = AbstractContentStore.createNewUrl(); + ContentWriter duplicateTargetWriter = targetStore.getWriter(null, duplicateUrl); + ContentWriter duplicateSourceWriter = sourceStore.getWriter(null, duplicateUrl); + duplicateTargetWriter.putContent("Duplicate Target Content: " + i); + duplicateSourceWriter.putContent(duplicateTargetWriter.getReader()); + + for (int j = 0; j < 100; j++) + { + // write content + ContentWriter writer = sourceStore.getWriter(null, null); + writer.putContent("Repeated put: " + j); + } + } + + // remove the last duplicated URL from the target + targetStore.delete(duplicateUrl); + + // allow time for the replicator to catch up + synchronized(this) + { + this.wait(1000L); + } + + // check that we have an exact match of URLs + Set sourceUrls = sourceStore.getUrls(); + Set targetUrls = targetStore.getUrls(); + + sourceUrls.containsAll(targetUrls); + targetUrls.contains(sourceUrls); + } +} diff --git a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java new file mode 100644 index 0000000000..e6bced21de --- /dev/null +++ b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.content.replication; + +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.content.AbstractContentStore; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentStreamListener; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Replicating Content Store + *

+ * A content store implementation that is able to replicate content between stores. + * Content is not persisted by this store, but rather it relies on any number of + * child {@link org.alfresco.repo.content.ContentStore stores} to provide access to + * content readers and writers. + *

+ * The order in which the stores appear in the list of stores participating is + * important. The first store in the list is known as the primary store. + * When the replicator goes to fetch content, the stores are searched + * from first to last. The stores should therefore be arranged in order of + * speed. + *

+ * It supports the notion of inbound and/or outbound replication, both of which can be + * operational at the same time. + * + * Outbound Replication + *

+ * When this is enabled, then the primary store is used for writes. When the + * content write completes (i.e. the write channel is closed) then the content + * is synchronously copied to all other stores. The write is therefore slowed + * down, but the content replication will occur in-transaction. + *

+ * The {@link #setOutboundThreadPoolExecutor(boolean) outboundThreadPoolExecutor } + * property to enable asynchronous replication.
+ * With asynchronous replication, there is always a risk that a failure + * occurs during the replication. Depending on the configuration of the server, + * further action may need to be taken to rectify the problem manually. + * + * Inbound Replication + *

+ * This can be used to lazily replicate content onto the primary store. When + * content can't be found in the primary store, the other stores are checked + * in order. If content is found, then it is copied into the local store + * before being returned. Subsequent accesses will use the primary store.
+ * This should be used where the secondary stores are much slower, such as in + * the case of a store against some kind of archival mechanism. + * + *

No Replication

+ *

+ * Content is not written to the primary store only. The other stores are + * only used to retrieve content and the primary store is not updated with + * the content. + * + * @author Derek Hulley + */ +public class ReplicatingContentStore extends AbstractContentStore +{ + /* + * The replication process uses thread synchronization as it can + * decide to write content to specific URLs during requests for + * a reader. + * While this won't help the underlying stores if there are + * multiple replications on top of them, it will prevent repeated + * work from multiple threads entering an instance of this component + * looking for the same content at the same time. + */ + + private static Log logger = LogFactory.getLog(ReplicatingContentStore.class); + + private TransactionService transactionService; + private ContentStore primaryStore; + private List secondaryStores; + private boolean inbound; + private boolean outbound; + private ThreadPoolExecutor outboundThreadPoolExecutor; + + private Lock readLock; + private Lock writeLock; + + /** + * Default constructor set inbound = false and outbound = true; + */ + public ReplicatingContentStore() + { + inbound = false; + outbound = true; + + ReadWriteLock storeLock = new ReentrantReadWriteLock(); + readLock = storeLock.readLock(); + writeLock = storeLock.writeLock(); + } + + /** + * Required to ensure that content listeners are executed in a transaction + * + * @param transactionService + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Set the primary store that content will be replicated to or from + * + * @param primaryStore the primary content store + */ + public void setPrimaryStore(ContentStore primaryStore) + { + this.primaryStore = primaryStore; + } + + /** + * Set the secondary stores that this component will replicate to or from + * + * @param stores a list of stores to replicate to or from + */ + public void setSecondaryStores(List secondaryStores) + { + this.secondaryStores = secondaryStores; + } + + /** + * Set whether or not this component should replicate content to the + * primary store if not found. + * + * @param inbound true to pull content onto the primary store when found + * on one of the other stores + */ + public void setInbound(boolean inbound) + { + this.inbound = inbound; + } + + /** + * Set whether or not this component should replicate content to all stores + * as it is written. + * + * @param outbound true to enable synchronous replication to all stores + */ + public void setOutbound(boolean outbound) + { + this.outbound = outbound; + } + + /** + * Set the thread pool executer + * + * @param outboundThreadPoolExecutor set this to have the synchronization occur in a separate + * thread + */ + public void setOutboundThreadPoolExecutor(ThreadPoolExecutor outboundThreadPoolExecutor) + { + this.outboundThreadPoolExecutor = outboundThreadPoolExecutor; + } + + /** + * Forwards the call directly to the first store in the list of stores. + */ + public ContentReader getReader(String contentUrl) throws ContentIOException + { + if (primaryStore == null) + { + throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised"); + } + + // get a read lock so that we are sure that no replication is underway + ContentReader existingContentReader = null; + readLock.lock(); + try + { + // get a reader from the primary store + ContentReader primaryReader = primaryStore.getReader(contentUrl); + + // give it straight back if the content is there + if (primaryReader.exists()) + { + return primaryReader; + } + + // the content is not in the primary reader so we have to go looking for it + ContentReader secondaryContentReader = null; + for (ContentStore store : secondaryStores) + { + ContentReader reader = store.getReader(contentUrl); + if (reader.exists()) + { + // found the content in a secondary store + secondaryContentReader = reader; + break; + } + } + // we already know that the primary has nothing + // drop out if no content was found + if (secondaryContentReader == null) + { + return primaryReader; + } + // secondary content was found + // return it if we are not doing inbound + if (!inbound) + { + return secondaryContentReader; + } + + // we have to replicate inbound + existingContentReader = secondaryContentReader; + } + finally + { + readLock.unlock(); + } + + // -- a small gap for concurrent threads to get through -- + + // do inbound replication + writeLock.lock(); + try + { + // double check the primary + ContentReader primaryContentReader = primaryStore.getReader(contentUrl); + if (primaryContentReader.exists()) + { + // we were beaten to it + return primaryContentReader; + } + // get a writer + ContentWriter primaryContentWriter = primaryStore.getWriter(existingContentReader, contentUrl); + // copy it over + primaryContentWriter.putContent(existingContentReader); + // get a writer to the new content + primaryContentReader = primaryContentWriter.getReader(); + // done + return primaryContentReader; + } + finally + { + writeLock.unlock(); + } + } + + /** + * + */ + public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl) throws ContentIOException + { + // get the writer + ContentWriter writer = primaryStore.getWriter(existingContentReader, newContentUrl); + + // attach a replicating listener if outbound replication is on + if (outbound) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Attaching " + (outboundThreadPoolExecutor == null ? "" : "a") + "synchronous " + + "replicating listener to local writer: \n" + + " primary store: " + primaryStore + "\n" + + " writer: " + writer); + } + // attach the listener + ContentStreamListener listener = new ReplicatingWriteListener(secondaryStores, writer, outboundThreadPoolExecutor); + writer.addListener(listener); + writer.setTransactionService(transactionService); // mandatory when listeners are added + } + + // done + return writer; + } + + /** + * Performs a delete on the local store and if outbound replication is on, propogates + * the delete to the other stores too. + * + * @return Returns the value returned by the delete on the primary store. + */ + public boolean delete(String contentUrl) throws ContentIOException + { + // delete on the primary store + boolean deleted = primaryStore.delete(contentUrl); + + // propogate outbound deletions + if (outbound) + { + for (ContentStore store : secondaryStores) + { + store.delete(contentUrl); + } + // log + if (logger.isDebugEnabled()) + { + logger.debug("Propagated content delete to " + secondaryStores.size() + " stores:" + contentUrl); + } + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Deleted content for URL: " + contentUrl); + } + return deleted; + } + + /** + * @return Returns the results as given by the primary store, and if inbound + * replication is active, merges the URLs from the secondary stores. + */ + public Set getUrls(Date createdAfter, Date createdBefore) throws ContentIOException + { + Set urls = new HashSet(1024); + + // add in URLs from primary store + Set primaryUrls = primaryStore.getUrls(createdAfter, createdBefore); + urls.addAll(primaryUrls); + + // add in URLs from secondary stores (they are visible for reads) + for (ContentStore secondaryStore : secondaryStores) + { + Set secondaryUrls = secondaryStore.getUrls(createdAfter, createdBefore); + // merge them + urls.addAll(secondaryUrls); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Found " + urls.size() + " URLs, of which " + primaryUrls.size() + " are primary: \n" + + " created after: " + createdAfter + "\n" + + " created before: " + createdBefore); + } + return urls; + } + + /** + * Replicates the content upon stream closure. If the thread pool is available, + * then the process will be asynchronous. + *

+ * No transaction boundaries have been declared as the + * {@link ContentWriter#addListener(ContentStreamListener)} method indicates that + * all listeners will be called within a transaction. + * + * @author Derek Hulley + */ + public static class ReplicatingWriteListener implements ContentStreamListener + { + private List stores; + private ContentWriter writer; + private ThreadPoolExecutor threadPoolExecutor; + + public ReplicatingWriteListener( + List stores, + ContentWriter writer, + ThreadPoolExecutor threadPoolExecutor) + { + this.stores = stores; + this.writer = writer; + this.threadPoolExecutor = threadPoolExecutor; + } + + public void contentStreamClosed() throws ContentIOException + { + Runnable runnable = new ReplicateOnCloseRunnable(); + if (threadPoolExecutor == null) + { + // execute direct + runnable.run(); + } + else + { + threadPoolExecutor.execute(runnable); + } + } + + /** + * Performs the actual replication work. + * + * @author Derek Hulley + */ + private class ReplicateOnCloseRunnable implements Runnable + { + public void run() + { + for (ContentStore store : stores) + { + try + { + // replicate the content to the store - we know the URL that we want to write to + ContentReader reader = writer.getReader(); + String contentUrl = reader.getContentUrl(); + // in order to replicate, we have to specify the URL that we are going to write to + ContentWriter replicatedWriter = store.getWriter(null, contentUrl); + // write it + replicatedWriter.putContent(reader); + + if (logger.isDebugEnabled()) + { + logger.debug("Replicated content to store: \n" + + " url: " + contentUrl + "\n" + + " to store: " + store); + } + } + catch (Throwable e) + { + throw new ContentIOException("Content replication failed: \n" + + " url: " + writer.getContentUrl() + "\n" + + " to store: " + store); + } + } + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java new file mode 100644 index 0000000000..cdaf67c54f --- /dev/null +++ b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.content.replication; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.alfresco.repo.content.AbstractContentReadWriteTest; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.filestore.FileContentStore; +import org.alfresco.repo.transaction.DummyTransactionService; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.util.GUID; +import org.alfresco.util.TempFileProvider; + +/** + * Tests read and write functionality for the replicating store. + *

+ * By default, replication is off for both the inbound and outbound + * replication. Specific tests change this. + * + * @see org.alfresco.repo.content.replication.ReplicatingContentStore + * + * @author Derek Hulley + */ +public class ReplicatingContentStoreTest extends AbstractContentReadWriteTest +{ + private static final String SOME_CONTENT = "The No. 1 Ladies' Detective Agency"; + + private ReplicatingContentStore replicatingStore; + private ContentStore primaryStore; + private List secondaryStores; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + File tempDir = TempFileProvider.getTempDir(); + // create a primary file store + String storeDir = tempDir.getAbsolutePath() + File.separatorChar + GUID.generate(); + primaryStore = new FileContentStore(storeDir); + // create some secondary file stores + secondaryStores = new ArrayList(3); + for (int i = 0; i < 3; i++) + { + storeDir = tempDir.getAbsolutePath() + File.separatorChar + GUID.generate(); + ContentStore store = new FileContentStore(storeDir); + secondaryStores.add(store); + } + replicatingStore = new ReplicatingContentStore(); + replicatingStore.setTransactionService(new DummyTransactionService()); + replicatingStore.setPrimaryStore(primaryStore); + replicatingStore.setSecondaryStores(secondaryStores); + replicatingStore.setOutbound(false); + replicatingStore.setInbound(false); + } + + @Override + public ContentStore getStore() + { + return replicatingStore; + } + + /** + * Performs checks necessary to ensure the proper replication of content for the given + * URL + */ + private void checkForReplication(boolean inbound, boolean outbound, String contentUrl, String content) + { + if (inbound) + { + ContentReader reader = primaryStore.getReader(contentUrl); + assertTrue("Content was not replicated into the primary store", reader.exists()); + assertEquals("The replicated content was incorrect", content, reader.getContentString()); + } + if (outbound) + { + for (ContentStore store : secondaryStores) + { + ContentReader reader = store.getReader(contentUrl); + assertTrue("Content was not replicated out to the secondary stores within a second", reader.exists()); + assertEquals("The replicated content was incorrect", content, reader.getContentString()); + } + } + } + + /** + * Checks that the url is present in each of the stores + * + * @param contentUrl + * @param mustExist true if the content must exist, false if it must not exist + */ + private void checkForUrl(String contentUrl, boolean mustExist) + { + // check that the URL is present for each of the stores + for (ContentStore store : secondaryStores) + { + Set urls = store.getUrls(); + assertTrue("URL of new content not present in store", urls.contains(contentUrl) == mustExist); + } + } + + public void testNoReplication() throws Exception + { + ContentWriter writer = getWriter(); + writer.putContent(SOME_CONTENT); + + checkForReplication(false, false, writer.getContentUrl(), SOME_CONTENT); + } + + public void testOutboundReplication() throws Exception + { + replicatingStore.setOutbound(true); + + // write some content + ContentWriter writer = getWriter(); + writer.putContent(SOME_CONTENT); + String contentUrl = writer.getContentUrl(); + + checkForReplication(false, true, contentUrl, SOME_CONTENT); + + // check for outbound deletes + replicatingStore.delete(contentUrl); + checkForUrl(contentUrl, false); + } + + public void testAsyncOutboundReplication() throws Exception + { + ThreadPoolExecutor tpe = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new SynchronousQueue()); + + replicatingStore.setOutbound(true); + replicatingStore.setOutboundThreadPoolExecutor(tpe); + + // write some content + ContentWriter writer = getWriter(); + writer.putContent(SOME_CONTENT); + String contentUrl = writer.getContentUrl(); + + // wait for a second + synchronized(this) + { + this.wait(1000L); + } + + checkForReplication(false, true, contentUrl, SOME_CONTENT); + + // check for outbound deletes + replicatingStore.delete(contentUrl); + checkForUrl(contentUrl, false); + } + + public void testInboundReplication() throws Exception + { + replicatingStore.setInbound(false); + + // pick a secondary store and write some content to it + ContentStore secondaryStore = secondaryStores.get(2); + ContentWriter writer = secondaryStore.getWriter(null, null); + writer.putContent(SOME_CONTENT); + String contentUrl = writer.getContentUrl(); + + // get a reader from the replicating store + ContentReader reader = replicatingStore.getReader(contentUrl); + assertTrue("Reader must have been found in secondary store", reader.exists()); + + // set inbound replication on and repeat + replicatingStore.setInbound(true); + reader = replicatingStore.getReader(contentUrl); + + // this time, it must have been replicated to the primary store + checkForReplication(true, false, contentUrl, SOME_CONTENT); + } +} diff --git a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java new file mode 100644 index 0000000000..01dd7811a8 --- /dev/null +++ b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java @@ -0,0 +1,769 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.node.index; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.NodeStatus; +import org.alfresco.repo.search.Indexer; +import org.alfresco.repo.search.impl.lucene.LuceneIndexerImpl; +import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.CacheMode; +import org.hibernate.Query; +import org.hibernate.Session; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; + +/** + * Ensures that the FTS indexing picks up on any outstanding documents that + * require indexing. + *

+ * This component must be used as a singleton (one per VM) and may only be + * called to reindex once. It will start a thread that processes all available + * transactions and keeps checking to ensure that the index is up to date with + * the latest database changes. + *

+ * The following points are important: + *

    + *
  • + * By default, the Hibernate L2 cache is used during processing. + * This can be disabled by either disabling the L2 cache globally + * for the server (not recommended) or by setting the + * {@link #setL2CacheMode(String) l2CacheMode} property. If the + * database is static then the L2 cache usage can be set to use + * the NORMAL mode. REFRESH should be + * used where the server will still be accessed from some clients + * despite the database changing. + *
  • + *
  • + * This process should not run continuously on a live + * server as it would be performing unecessary work. + * If it was left running, however, it would not + * lead to data corruption or such-like. Use the + * {@link #setRunContinuously(boolean) runContinuously} property + * to change this behaviour. + *
  • + *
+ * + * @author Derek Hulley + */ +public class FullIndexRecoveryComponent extends HibernateDaoSupport implements IndexRecovery +{ + public static final String QUERY_GET_NEXT_CHANGE_TXN_IDS = "node.GetNextChangeTxnIds"; + public static final String QUERY_GET_CHANGED_NODE_STATUSES = "node.GetChangedNodeStatuses"; + public static final String QUERY_GET_CHANGED_NODE_STATUSES_COUNT = "node.GetChangedNodeStatusesCount"; + + private static final String START_TXN_ID = "000"; + + private static Log logger = LogFactory.getLog(FullIndexRecoveryComponent.class); + + /** ensures that this process is kicked off once per VM */ + private static boolean started = false; + /** The current transaction ID being processed */ + private static String currentTxnId = START_TXN_ID; + /** kept to notify the thread that it should quite */ + private boolean killThread = false; + + /** provides transactions to atomically index each missed transaction */ + private TransactionService transactionService; + /** the component to index the node hierarchy */ + private Indexer indexer; + /** the FTS indexer that we will prompt to pick up on any un-indexed text */ + private FullTextSearchIndexer ftsIndexer; + /** the component providing searches of the indexed nodes */ + private SearchService searcher; + /** the component giving direct access to node instances */ + private NodeService nodeService; + /** the stores to reindex */ + private List storeRefs; + /** set this to run the index recovery component */ + private boolean executeFullRecovery; + /** set this on to keep checking for new transactions and never stop */ + private boolean runContinuously; + /** set the time to wait between checking indexes */ + private long waitTime; + /** controls how the L2 cache is used */ + private CacheMode l2CacheMode; + + /** + * @return Returns the ID of the current (or last) transaction processed + */ + public static String getCurrentTransactionId() + { + return currentTxnId; + } + + public FullIndexRecoveryComponent() + { + this.storeRefs = new ArrayList(2); + + this.killThread = false; + this.executeFullRecovery = false; + this.runContinuously = false; + this.waitTime = 1000L; + this.l2CacheMode = CacheMode.REFRESH; + + // ensure that we kill the thread when the VM is shutting down + Runnable shutdownRunnable = new Runnable() + { + public void run() + { + killThread = true; + }; + }; + Thread shutdownThread = new Thread(shutdownRunnable); + Runtime.getRuntime().addShutdownHook(shutdownThread); + } + + /** + * @return Returns true if the component has already been started + */ + public static boolean isStarted() + { + return started; + } + + /** + * @param transactionService provide transactions to index each missed transaction + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * @param indexer the indexer that will be index + */ + public void setIndexer(Indexer indexer) + { + this.indexer = indexer; + } + + /** + * @param ftsIndexer the FTS background indexer + */ + public void setFtsIndexer(FullTextSearchIndexer ftsIndexer) + { + this.ftsIndexer = ftsIndexer; + } + + /** + * @param searcher component providing index searches + */ + public void setSearcher(SearchService searcher) + { + this.searcher = searcher; + } + + /** + * @param nodeService provides information about nodes for indexing + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the stores that need reindexing + * + * @param storeRefStrings a list of strings representing store references + */ + public void setStores(List storeRefStrings) + { + storeRefs.clear(); + for (String storeRefStr : storeRefStrings) + { + StoreRef storeRef = new StoreRef(storeRefStr); + storeRefs.add(storeRef); + } + } + + /** + * Set this to true to initiate the full index recovery. + *

+ * This used to default to true but is now false. Set this + * if the potentially long-running process of checking and fixing the + * indexes must be started. + * + * @param executeFullRecovery + */ + public void setExecuteFullRecovery(boolean executeFullRecovery) + { + this.executeFullRecovery = executeFullRecovery; + } + + /** + * Set this to ensure that the process continuously checks for new transactions. + * If not, it will permanently terminate once it catches up with the current + * transactions. + * + * @param runContinuously true to never cease looking for new transactions + */ + public void setRunContinuously(boolean runContinuously) + { + this.runContinuously = runContinuously; + } + + /** + * Set the time to wait between checking for new transaction changes in the database. + * + * @param waitTime the time to wait in milliseconds + */ + public void setWaitTime(long waitTime) + { + this.waitTime = waitTime; + } + + /** + * Set the hibernate cache mode by name + * + * @see org.hibernate.CacheMode + */ + public void setL2CacheMode(String l2CacheModeStr) + { + if (l2CacheModeStr.equals("GET")) + { + l2CacheMode = CacheMode.GET; + } + else if (l2CacheModeStr.equals("IGNORE")) + { + l2CacheMode = CacheMode.IGNORE; + } + else if (l2CacheModeStr.equals("NORMAL")) + { + l2CacheMode = CacheMode.NORMAL; + } + else if (l2CacheModeStr.equals("PUT")) + { + l2CacheMode = CacheMode.PUT; + } + else if (l2CacheModeStr.equals("REFRESH")) + { + l2CacheMode = CacheMode.REFRESH; + } + else + { + throw new IllegalArgumentException("Unrecognised Hibernate L2 cache mode: " + l2CacheModeStr); + } + } + + /** + * Ensure that the index is up to date with the current state of the persistence layer. + * The full list of unique transaction change IDs is retrieved and used to detect + * which are not present in the index. All the node changes and deletions for the + * remaining transactions are then indexed. + */ + public synchronized void reindex() + { + if (FullIndexRecoveryComponent.started) + { + throw new AlfrescoRuntimeException + ("Only one FullIndexRecoveryComponent may be used per VM and it may only be called once"); + } + + // ensure that we don't redo this work + FullIndexRecoveryComponent.started = true; + + // work to mark the stores for full text reindexing + TransactionWork ftsReindexWork = new TransactionWork() + { + public Object doWork() + { + // reindex each store + for (StoreRef storeRef : storeRefs) + { + // check if the store exists + if (!nodeService.exists(storeRef)) + { + // store does not exist + if (logger.isDebugEnabled()) + { + logger.debug("Skipping reindex of non-existent store: " + storeRef); + } + continue; + } + + // prompt FTS to reindex the store + ftsIndexer.requiresIndex(storeRef); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Prompted FTS index on stores: " + storeRefs); + } + return null; + } + }; + TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, ftsReindexWork); + + // start full index recovery, if necessary + if (!this.executeFullRecovery) + { + if (logger.isDebugEnabled()) + { + logger.debug("Full index recovery is off - quitting"); + } + } + else + { + // set the state of the reindex + FullIndexRecoveryComponent.currentTxnId = START_TXN_ID; + + // start a stateful thread that will begin processing the reindexing the transactions + Runnable runnable = new ReindexRunner(); + Thread reindexThread = new Thread(runnable); + // make it a daemon thread + reindexThread.setDaemon(true); + // it should not be a high priority + reindexThread.setPriority(Thread.MIN_PRIORITY); + // start it + reindexThread.start(); + + if (logger.isDebugEnabled()) + { + logger.debug("Full index recovery thread started: \n" + + " continuous: " + runContinuously + "\n" + + " stores: " + storeRefs); + } + } + } + + /** + * Stateful thread runnable that executes reindex calls. + * + * @see FullIndexRecoveryComponent#reindexNodes() + * + * @author Derek Hulley + */ + private class ReindexRunner implements Runnable + { + public void run() + { + // keep this thread going permanently + while (!killThread) + { + try + { + // reindex nodes + List txnsIndexed = FullIndexRecoveryComponent.this.reindexNodes(); + // reindex missing content + @SuppressWarnings("unused") + int missingContentCount = FullIndexRecoveryComponent.this.reindexMissingContent(); + // check if the process should terminate + if (txnsIndexed.size() == 0 && !runContinuously) + { + // the thread has caught up with all the available work and should not + // run continuously + if (logger.isDebugEnabled()) + { + logger.debug("Thread quitting - no more available indexing to do: \n" + + " last txn: " + FullIndexRecoveryComponent.getCurrentTransactionId()); + } + break; + } + // brief pause + synchronized(FullIndexRecoveryComponent.this) + { + FullIndexRecoveryComponent.this.wait(waitTime); + } + } + catch (InterruptedException e) + { + // ignore + } + catch (Throwable e) + { + if (killThread) + { + // the shutdown may have caused the exception - ignore it + } + else + { + // we are still a go; report it + logger.error("Reindex failure", e); + } + } + } + } + } + + /** + * @return Returns the number of documents reindexed + */ + private int reindexMissingContent() + { + int count = 0; + for (StoreRef storeRef : storeRefs) + { + count += reindexMissingContent(storeRef); + } + return count; + } + + /** + * @param storeRef the store to check for missing content + * @return Returns the number of documents reindexed + */ + private int reindexMissingContent(StoreRef storeRef) + { + SearchParameters sp = new SearchParameters(); + sp.addStore(storeRef); + + // search for it in the index + String query = "TEXT:" + LuceneIndexerImpl.NOT_INDEXED_CONTENT_MISSING; + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery(query); + ResultSet results = null; + try + { + results = searcher.query(sp); + + int count = 0; + // loop over the results and get the details of the nodes that have missing content + List assocRefs = results.getChildAssocRefs(); + for (ChildAssociationRef assocRef : assocRefs) + { + final NodeRef childNodeRef = assocRef.getChildRef(); + // prompt for a reindex - it might fail again, but we just keep plugging away + TransactionWork reindexWork = new TransactionWork() + { + public Object doWork() + { + indexer.updateNode(childNodeRef); + return null; + } + }; + TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, reindexWork); + count++; + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Reindexed missing content: \n" + + " store: " + storeRef + "\n" + + " node count: " + count); + } + return count; + } + finally + { + if (results != null) + { + results.close(); + } + } + } + + /** + * @return Returns the transaction ID just reindexed, i.e. where some work was performed + */ + private List reindexNodes() + { + // get a list of all transactions still requiring a check + List txnsToCheck = getNextChangeTxnIds(FullIndexRecoveryComponent.currentTxnId); + + // loop over each transaction + for (String changeTxnId : txnsToCheck) + { + reindexNodes(changeTxnId); + } + + // done + return txnsToCheck; + } + + /** + * Reindexes changes specific to the change transaction ID. + *

+ * All exceptions are absorbed. + */ + private void reindexNodes(final String changeTxnId) + { + /* + * This must execute each within its own transaction. + * The cache size is therefore not an issue. + */ + TransactionWork reindexWork = new TransactionWork() + { + public Object doWork() throws Exception + { + // perform the work in a Hibernate callback + HibernateCallback callback = new ReindexCallback(changeTxnId); + getHibernateTemplate().execute(callback); + // done + return null; + } + }; + try + { + TransactionUtil.executeInNonPropagatingUserTransaction(transactionService, reindexWork); + } + catch (Throwable e) + { + logger.error("Transaction reindex failed: \n" + + " txn: " + changeTxnId, + e); + } + finally + { + // Up the current transaction now, in case the process fails at this point. + // This will prevent the transaction from being processed again. + // This applies to failures as well, which should be dealt with externally + // and having the entire process start again, e.g. such as a system reboot + currentTxnId = changeTxnId; + } + } + + /** + * Stateful inner class that implements a single reindex call for a given store + * and transaction. + *

+ * It must be called within its own transaction. + * + * @author Derek Hulley + */ + private class ReindexCallback implements HibernateCallback + { + private final String changeTxnId; + + public ReindexCallback(String changeTxnId) + { + this.changeTxnId = changeTxnId; + } + + /** + * Changes the L2 cache usage before reindexing for each store + * + * @see #reindexNodes(StoreRef, String) + */ + public Object doInHibernate(Session session) + { + // set the way the L2 cache is used + getSession().setCacheMode(l2CacheMode); + + // reindex each store + for (StoreRef storeRef : storeRefs) + { + if (!nodeService.exists(storeRef)) + { + // the store is not present + continue; + } + // reindex for store + reindexNodes(storeRef, changeTxnId); + } + // done + return null; + } + + private void reindexNodes(StoreRef storeRef, String changeTxnId) + { + // check if we need to perform this operation + SearchParameters sp = new SearchParameters(); + sp.addStore(storeRef); + + // search for it in the index + String query = "TX:\"" + changeTxnId + "\""; + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery(query); + ResultSet results = null; + try + { + results = searcher.query(sp); + // did the index have any of these changes? + if (results.length() > 0) + { + // the transaction has an entry in the index - assume that it was + // atomically correct + if (logger.isDebugEnabled()) + { + logger.debug("Transaction present in index - no indexing required: \n" + + " store: " + storeRef + "\n" + + " txn: " + changeTxnId); + } + return; + } + } + finally + { + if (results != null) + { + results.close(); + } + } + // the index has no record of this + // were there any changes, or is it all just deletions? + int changedCount = getChangedNodeStatusesCount(storeRef, changeTxnId); + if (changedCount == 0) + { + // no nodes were changed in the transaction, i.e. they are only deletions + // the index is quite right not to have any entries for the transaction + if (logger.isDebugEnabled()) + { + logger.debug("Transaction only has deletions - no indexing required: \n" + + " store: " + storeRef + "\n" + + " txn: " + changeTxnId); + } + return; + } + + // process the deletions relevant to the txn and the store + List deletedNodeStatuses = getDeletedNodeStatuses(storeRef, changeTxnId); + for (NodeStatus status : deletedNodeStatuses) + { + NodeRef nodeRef = new NodeRef(storeRef, status.getKey().getGuid()); + // only the child node ref is relevant + ChildAssociationRef assocRef = new ChildAssociationRef( + ContentModel.ASSOC_CHILDREN, + null, + null, + nodeRef); + indexer.deleteNode(assocRef); + } + + // process additions + List changedNodeStatuses = getChangedNodeStatuses(storeRef, changeTxnId); + for (NodeStatus status : changedNodeStatuses) + { + NodeRef nodeRef = new NodeRef(storeRef, status.getKey().getGuid()); + // get the primary assoc for the node + ChildAssociationRef primaryAssocRef = nodeService.getPrimaryParent(nodeRef); + // reindex + indexer.createNode(primaryAssocRef); + } + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Transaction reindexed: \n" + + " store: " + storeRef + "\n" + + " txn: " + changeTxnId + "\n" + + " deletions: " + deletedNodeStatuses.size() + "\n" + + " modifications: " + changedNodeStatuses.size()); + } + } + }; + + /** + * Retrieve all transaction IDs that are greater than the given transaction ID. + * + * @param currentTxnId the transaction ID that must be less than all returned results + * @return Returns an ordered list of transaction IDs + */ + @SuppressWarnings("unchecked") + public List getNextChangeTxnIds(final String currentTxnId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_NEXT_CHANGE_TXN_IDS); + query.setString("currentTxnId", currentTxnId) + .setReadOnly(true); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + // done + return queryResults; + } + + @SuppressWarnings("unchecked") + public int getChangedNodeStatusesCount(final StoreRef storeRef, final String changeTxnId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_CHANGED_NODE_STATUSES_COUNT); + query.setBoolean("deleted", false) + .setString("storeProtocol", storeRef.getProtocol()) + .setString("storeIdentifier", storeRef.getIdentifier()) + .setString("changeTxnId", changeTxnId) + .setReadOnly(true); + return query.uniqueResult(); + } + }; + Integer changeCount = (Integer) getHibernateTemplate().execute(callback); + // done + return changeCount.intValue(); + } + + @SuppressWarnings("unchecked") + public List getChangedNodeStatuses(final StoreRef storeRef, final String changeTxnId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_CHANGED_NODE_STATUSES); + query.setBoolean("deleted", false) + .setString("storeProtocol", storeRef.getProtocol()) + .setString("storeIdentifier", storeRef.getIdentifier()) + .setString("changeTxnId", changeTxnId) + .setReadOnly(true); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + // done + return queryResults; + } + + @SuppressWarnings("unchecked") + public List getDeletedNodeStatuses(final StoreRef storeRef, final String changeTxnId) + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(QUERY_GET_CHANGED_NODE_STATUSES); + query.setBoolean("deleted", true) + .setString("storeProtocol", storeRef.getProtocol()) + .setString("storeIdentifier", storeRef.getIdentifier()) + .setString("changeTxnId", changeTxnId) + .setReadOnly(true); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + // done + return queryResults; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponentTest.java b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponentTest.java new file mode 100644 index 0000000000..b64c982918 --- /dev/null +++ b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponentTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.node.index; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.search.Indexer; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.InvalidStoreRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * Checks that full index recovery is possible + * + * @author Derek Hulley + */ +public class FullIndexRecoveryComponentTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private TransactionService transactionService; + private FullIndexRecoveryComponent indexRecoverer; + private NodeService nodeService; + private TransactionService txnService; + private Indexer indexer; + + private List storeRefs; + + public void setUp() throws Exception + { + transactionService = (TransactionService) ctx.getBean("transactionComponent"); + indexRecoverer = (FullIndexRecoveryComponent) ctx.getBean("indexRecoveryComponent"); + txnService = (TransactionService) ctx.getBean("transactionComponent"); + nodeService = (NodeService) ctx.getBean("nodeService"); + indexer = (Indexer) ctx.getBean("indexerComponent"); + + // create 2 stores + TransactionWork> createStoresWork = new TransactionWork>() + { + public List doWork() throws Exception + { + List storeRefs = new ArrayList(2); + storeRefs.add(nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.nanoTime())); + storeRefs.add(nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.nanoTime())); + return storeRefs; + } + }; + storeRefs = TransactionUtil.executeInUserTransaction(transactionService, createStoresWork); + } + + public void testNothing() throws Exception + { + + } + + public void xtestReindexing() throws Exception + { + // don't do anything if the component has already started + if (FullIndexRecoveryComponent.isStarted()) + { + return; + } + // deletes a content node from the index + final List storeRefStrings = new ArrayList(2); + TransactionWork dropNodeIndexWork = new TransactionWork() + { + public String doWork() + { + // create a node in each store and drop it from the index + for (StoreRef storeRef : storeRefs) + { + try + { + NodeRef rootNodeRef = nodeService.getRootNode(storeRef); + ChildAssociationRef assocRef = nodeService.createNode( + rootNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.ALFRESCO_URI, "unindexedChild" + System.currentTimeMillis()), + ContentModel.TYPE_BASE); + // this will have indexed it, so remove it from the index + indexer.deleteNode(assocRef); + // make the string version of the storeRef + storeRefStrings.add(storeRef.toString()); + } + catch (InvalidStoreRefException e) + { + // just ignore stores that are invalid + } + } + return AlfrescoTransactionSupport.getTransactionId(); + } + }; + + // create un-indexed nodes + String txnId = TransactionUtil.executeInNonPropagatingUserTransaction(txnService, dropNodeIndexWork); + + indexRecoverer.setExecuteFullRecovery(true); + indexRecoverer.setStores(storeRefStrings); + // reindex + indexRecoverer.reindex(); + + // check that reindexing fails + try + { + indexRecoverer.reindex(); + fail("Reindexer failed to prevent reindex from being called twice"); + } + catch (RuntimeException e) + { + // expected + } + + // loop for some time, giving it a chance to do its thing + String lastProcessedTxnId = null; + for (int i = 0; i < 60; i++) + { + lastProcessedTxnId = FullIndexRecoveryComponent.getCurrentTransactionId(); + if (lastProcessedTxnId.equals(txnId)) + { + break; + } + // wait for a second + synchronized(this) + { + this.wait(1000L); + } + } + // check that the index was recovered + assertEquals("Index transaction not up to date", txnId, lastProcessedTxnId); + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/jaas/JAASAuthenticationComponent.java b/source/java/org/alfresco/repo/security/authentication/jaas/JAASAuthenticationComponent.java new file mode 100644 index 0000000000..5c517a3cb6 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/jaas/JAASAuthenticationComponent.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.jaas; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.LanguageCallback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.RealmCallback; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationException; + +/** + * JAAS based authentication + * + * The user name and password are picked up from login. + * + * The other configurable parameters are: + * realm - the authentication realm if required, + * and the entry name to use from the login context. + * + * You will need to be familiar with the JAAS authentication process to set this up. + * + * In summary you will need to configure java.security (in the lib/security directory of the jre you are using) + * to find a jaas configuration. + * + * This entry could be used if you want to put the login configuration in the same place (in the lib/security directory of the jre you are using) + * + * + * login.config.url.1=file:${java.home}/lib/security/java.login.config + * + * + * Example configuration entries for Kerberos would be: + * + * + * Alfresco { + * com.sun.security.auth.module.Krb5LoginModule sufficient; + * }; + * + * com.sun.net.ssl.client { + * com.sun.security.auth.module.Krb5LoginModule sufficient; + * }; + * + * other { + * com.sun.security.auth.module.Krb5LoginModule sufficient; + * }; + * + * + * This sets up authentication using Kerberos for Alfresco and some defaults that would use the same mechanism if sasl failed for example. + * + * You could use kerberos and LDAP combined against an Active Directory server. + * + * @author Andy Hind + */ +public class JAASAuthenticationComponent extends AbstractAuthenticationComponent +{ + + /** + * A key into the login config that defines the authentication mechamisms required. + */ + private String jaasConfigEntryName = "Alfresco"; + + /** + * A default realm + */ + private String realm = null; + + public JAASAuthenticationComponent() + { + super(); + } + + // Springification + + public void setJaasConfigEntryName(String jaasConfigEntryName) + { + this.jaasConfigEntryName = jaasConfigEntryName; + } + + + public void setRealm(String realm) + { + this.realm = realm; + } + + /** + * Jaas does not support guest login + */ + @Override + protected boolean implementationAllowsGuestLogin() + { + return false; + } + + /** + * Implement Authentication + */ + public void authenticate(String userName, char[] password) throws AuthenticationException + { + + LoginContext lc; + try + { + lc = new LoginContext(jaasConfigEntryName, new SimpleCallback(userName, realm, password)); + } + catch (LoginException e) + { + throw new AuthenticationException("Login Failed", e); + } + try + { + lc.login(); + // Login has gone through OK, set up the acegi context + setCurrentUser(userName); + } + catch (LoginException e) + { + throw new AuthenticationException("Login Failed", e); + } + + } + + /** + * Simple call back class to support the common requirements. + * + * @author Andy Hind + */ + private static class SimpleCallback implements CallbackHandler + { + String userName; + + String realm; + + char[] password; + + SimpleCallback(String userName, String realm, char[] password) + { + this.userName = userName; + this.realm = realm; + this.password = password; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for (int i = 0; i < callbacks.length; i++) + { + if (callbacks[i] instanceof AuthorizeCallback) + { + AuthorizeCallback cb = (AuthorizeCallback) callbacks[i]; + cb.setAuthorized(false); + } + else if (callbacks[i] instanceof LanguageCallback) + { + LanguageCallback cb = (LanguageCallback) callbacks[i]; + cb.setLocale(I18NUtil.getLocale()); + } + else if (callbacks[i] instanceof NameCallback) + { + NameCallback cb = (NameCallback) callbacks[i]; + cb.setName(userName); + } + else if (callbacks[i] instanceof PasswordCallback) + { + PasswordCallback cb = (PasswordCallback) callbacks[i]; + cb.setPassword(password); + } + else if (callbacks[i] instanceof RealmCallback) + { + RealmCallback cb = (RealmCallback) callbacks[i]; + cb.setText(realm); + } + else + { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java new file mode 100644 index 0000000000..8f554bcb11 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ldap; + +import javax.naming.NamingException; +import javax.naming.directory.InitialDirContext; + +import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationException; + +/** + * Currently expects the cn name of the user which is in a fixed location. + * + * @author Andy Hind + */ +public class LDAPAuthenticationComponentImpl extends AbstractAuthenticationComponent +{ + + private String userNameFormat; + + private LDAPInitialDirContextFactory ldapInitialContextFactory; + + public LDAPAuthenticationComponentImpl() + { + super(); + } + + + public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) + { + this.ldapInitialContextFactory = ldapInitialDirContextFactory; + } + + + public void setUserNameFormat(String userNameFormat) + { + this.userNameFormat = userNameFormat; + } + + /** + * Implement the authentication method + */ + public void authenticate(String userName, char[] password) throws AuthenticationException + { + InitialDirContext ctx = null; + try + { + ctx = ldapInitialContextFactory.getInitialDirContext(String.format(userNameFormat, new Object[]{userName}), new String(password)); + + // Authentication has been successful. + // Set the current user, they are now authenticated. + setCurrentUser(userName); + + } + finally + { + if(ctx != null) + { + try + { + ctx.close(); + } + catch (NamingException e) + { + clearCurrentSecurityContext(); + throw new AuthenticationException("Failed to close connection", e); + } + } + } + } + + + @Override + protected boolean implementationAllowsGuestLogin() + { + InitialDirContext ctx = null; + try + { + ctx = ldapInitialContextFactory.getDefaultIntialDirContext(); + return true; + + } + catch(Exception e) + { + return false; + } + finally + { + if(ctx != null) + { + try + { + ctx.close(); + } + catch (NamingException e) + { + throw new AuthenticationException("Failed to close connection", e); + } + } + } + } + + +} diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java new file mode 100644 index 0000000000..ace7b85f2c --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java @@ -0,0 +1,695 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ldap; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.importer.ExportSource; +import org.alfresco.repo.importer.ExportSourceImporterException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +public class LDAPGroupExportSource implements ExportSource, InitializingBean +{ + private static Log s_logger = LogFactory.getLog(LDAPGroupExportSource.class); + + private String groupQuery = "(objectclass=groupOfNames)"; + + private String searchBase; + + private String groupIdAttributeName = "cn"; + + private String userIdAttributeName = "uid"; + + private String groupType = "groupOfNames"; + + private String personType = "inetOrgPerson"; + + private LDAPInitialDirContextFactory ldapInitialContextFactory; + + private NamespaceService namespaceService; + + private String memberAttribute = "member"; + + private boolean errorOnMissingMembers = false; + + private QName viewRef; + + private QName viewId; + + private QName viewAssociations; + + private QName childQName; + + private QName viewValueQName; + + private QName viewIdRef; + + public LDAPGroupExportSource() + { + super(); + } + + public void setGroupIdAttributeName(String groupIdAttributeName) + { + this.groupIdAttributeName = groupIdAttributeName; + } + + public void setGroupQuery(String groupQuery) + { + this.groupQuery = groupQuery; + } + + public void setGroupType(String groupType) + { + this.groupType = groupType; + } + + public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) + { + this.ldapInitialContextFactory = ldapInitialDirContextFactory; + } + + public void setMemberAttribute(String memberAttribute) + { + this.memberAttribute = memberAttribute; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setPersonType(String personType) + { + this.personType = personType; + } + + public void setSearchBase(String searchBase) + { + this.searchBase = searchBase; + } + + public void setUserIdAttributeName(String userIdAttributeName) + { + this.userIdAttributeName = userIdAttributeName; + } + + public void setErrorOnMissingMembers(boolean errorOnMissingMembers) + { + this.errorOnMissingMembers = errorOnMissingMembers; + } + + public void generateExport(XMLWriter writer) + { + HashSet rootGroups = new HashSet(); + HashMap lookup = new HashMap(); + HashSet secondaryLinks = new HashSet(); + + buildGroupsAndRoots(rootGroups, lookup, secondaryLinks); + + buildXML(rootGroups, lookup, secondaryLinks, writer); + + } + + private void buildXML(HashSet rootGroups, HashMap lookup, + HashSet secondaryLinks, XMLWriter writer) + { + + Collection prefixes = namespaceService.getPrefixes(); + QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); + + try + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName + .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); + + writer.startDocument(); + + for (String prefix : prefixes) + { + if (!prefix.equals("xml")) + { + String uri = namespaceService.getNamespaceURI(prefix); + writer.startPrefixMapping(prefix, uri); + } + } + + writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", + NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl()); + + // Create group structure + + for (Group group : rootGroups) + { + addRootGroup(lookup, group, writer); + } + + // Create secondary links. + + for (SecondaryLink sl : secondaryLinks) + { + addSecondarylink(lookup, sl, writer); + } + + for (String prefix : prefixes) + { + if (!prefix.equals("xml")) + { + writer.endPrefixMapping(prefix); + } + } + + writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX + + ":" + "view"); + + writer.endDocument(); + } + catch (SAXException e) + { + throw new ExportSourceImporterException("Failed to create file for import.", e); + } + + } + + private void addSecondarylink(HashMap lookup, SecondaryLink sl, XMLWriter writer) + throws SAXException + { + + String fromId = lookup.get(sl.from).guid; + String toId = lookup.get(sl.to).guid; + + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, fromId); + + writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), + viewRef.toPrefixString(namespaceService), attrs); + + writer.startElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations + .toPrefixString(namespaceService), new AttributesImpl()); + + writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), + ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), new AttributesImpl()); + + AttributesImpl attrsRef = new AttributesImpl(); + attrsRef.addAttribute(viewIdRef.getNamespaceURI(), viewIdRef.getLocalName(), viewIdRef.toPrefixString(), null, toId); + attrsRef.addAttribute(childQName.getNamespaceURI(), childQName.getLocalName(), childQName.toPrefixString(), + null, QName.createQName(ContentModel.USER_MODEL_URI, sl.to).toPrefixString(namespaceService)); + + writer.startElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), + viewRef.toPrefixString(namespaceService), attrsRef); + + writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService)); + + writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), + ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService)); + + writer.endElement(viewAssociations.getNamespaceURI(), viewAssociations.getLocalName(), viewAssociations + .toPrefixString(namespaceService)); + + writer.endElement(viewRef.getNamespaceURI(), viewRef.getLocalName(), viewRef.toPrefixString(namespaceService)); + + } + + private void addRootGroup(HashMap lookup, Group group, XMLWriter writer) throws SAXException + { + + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName + .toPrefixString(), null, QName.createQName(ContentModel.USER_MODEL_URI, group.gid).toPrefixString( + namespaceService)); + attrs.addAttribute(viewId.getNamespaceURI(), viewId.getLocalName(), viewId + .toPrefixString(), null, group.guid); + + writer.startElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), + ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER + .toPrefixString(namespaceService), attrs); + + writer.startElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME + .getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService), + new AttributesImpl()); + + writer.characters(group.gid.toCharArray(), 0, group.gid.length()); + + writer.endElement(ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI(), ContentModel.PROP_AUTHORITY_NAME + .getLocalName(), ContentModel.PROP_AUTHORITY_NAME.toPrefixString(namespaceService)); + + if (group.members.size() > 0) + { + writer.startElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), + ContentModel.PROP_MEMBERS.toPrefixString(namespaceService), new AttributesImpl()); + + for (String member : group.members) + { + writer.startElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName + .toPrefixString(namespaceService), new AttributesImpl()); + + writer.characters(member.toCharArray(), 0, member.length()); + + writer.endElement(viewValueQName.getNamespaceURI(), viewValueQName.getLocalName(), viewValueQName + .toPrefixString(namespaceService)); + } + + writer.endElement(ContentModel.PROP_MEMBERS.getNamespaceURI(), ContentModel.PROP_MEMBERS.getLocalName(), + ContentModel.PROP_MEMBERS.toPrefixString(namespaceService)); + } + + for (Group child : group.children) + { + addgroup(lookup, child, writer); + } + + writer.endElement(ContentModel.TYPE_AUTHORITY_CONTAINER.getNamespaceURI(), + ContentModel.TYPE_AUTHORITY_CONTAINER.getLocalName(), ContentModel.TYPE_AUTHORITY_CONTAINER + .toPrefixString(namespaceService)); + + } + + private void addgroup(HashMap lookup, Group group, XMLWriter writer) throws SAXException + { + AttributesImpl attrs = new AttributesImpl(); + + writer.startElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), + ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService), attrs); + + addRootGroup(lookup, group, writer); + + writer.endElement(ContentModel.ASSOC_MEMBER.getNamespaceURI(), ContentModel.ASSOC_MEMBER.getLocalName(), + ContentModel.ASSOC_MEMBER.toPrefixString(namespaceService)); + } + + private void buildGroupsAndRoots(HashSet rootGroups, HashMap lookup, + HashSet secondaryLinks) + { + InitialDirContext ctx = null; + try + { + ctx = ldapInitialContextFactory.getDefaultIntialDirContext(); + + SearchControls userSearchCtls = new SearchControls(); + userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + NamingEnumeration searchResults = ctx.search(searchBase, groupQuery, userSearchCtls); + while (searchResults.hasMoreElements()) + { + SearchResult result = (SearchResult) searchResults.next(); + Attributes attributes = result.getAttributes(); + Attribute gidAttribute = attributes.get(groupIdAttributeName); + if(gidAttribute == null) + { + throw new ExportSourceImporterException("Group returned by group search does not have mandatory group id attribute "+attributes); + } + String gid = (String) gidAttribute.get(0); + + Group group = lookup.get(gid); + if (group == null) + { + group = new Group(gid); + lookup.put(group.gid, group); + rootGroups.add(group); + } + Attribute memAttribute = attributes.get(memberAttribute); + // check for null + if (memAttribute != null) + { + for (int i = 0; i < memAttribute.size(); i++) + { + String attribute = (String) memAttribute.get(i); + if (attribute != null) + { + group.distinguishedNames.add(attribute); + } + } + } + } + + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Found " + lookup.size()); + } + + for (Group group : lookup.values()) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Linking " + group.gid); + } + for (String dn : group.distinguishedNames) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... " + dn); + } + String id; + Boolean isGroup = null; + + SearchControls memberSearchCtls = new SearchControls(); + memberSearchCtls.setSearchScope(SearchControls.OBJECT_SCOPE); + NamingEnumeration memberSearchResults; + try + { + memberSearchResults = ctx.search(dn, "(objectClass=*)", memberSearchCtls); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve distinguished name: " + dn); + continue; + } + while (memberSearchResults.hasMoreElements()) + { + id = null; + + SearchResult result; + try + { + result = (SearchResult) memberSearchResults.next(); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve distinguished name: " + dn); + continue; + } + Attributes attributes = result.getAttributes(); + Attribute objectclass = attributes.get("objectclass"); + if(objectclass == null) + { + throw new ExportSourceImporterException("Failed to find attribute objectclass for DN "+dn); + } + for (int i = 0; i < objectclass.size(); i++) + { + String testType; + try + { + testType = (String) objectclass.get(i); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve object class attribute for distinguished name: " + dn); + continue; + } + if (testType.equals(groupType)) + { + isGroup = true; + try + { + Attribute groupIdAttribute = attributes.get(groupIdAttributeName); + if(groupIdAttribute == null) + { + throw new ExportSourceImporterException("Group missing group id attribute DN ="+dn + " att = "+groupIdAttributeName); + } + id = (String) groupIdAttribute.get(0); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve group identifier " + + groupIdAttributeName + " for distinguished name: " + dn); + id = "Unknown sub group"; + } + break; + } + else if (testType.equals(personType)) + { + isGroup = false; + try + { + Attribute userIdAttribute = attributes.get(userIdAttributeName); + if(userIdAttribute == null) + { + throw new ExportSourceImporterException("User missing user id attribute DN ="+dn + " att = "+userIdAttributeName); + } + id = (String) userIdAttribute.get(0); + } + catch (NamingException e) + { + if (errorOnMissingMembers) + { + throw e; + } + s_logger.warn("Failed to resolve group identifier " + + userIdAttributeName + " for distinguished name: " + dn); + id = "Unknown member"; + } + break; + } + } + + if (id != null) + { + if (isGroup == null) + { + throw new ExportSourceImporterException("Type not recognised for DN"+dn); + } + else if (isGroup) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... is sub group"); + } + Group child = lookup.get("GROUP_" + id); + if (child == null) + { + throw new ExportSourceImporterException("Failed to find child group " + id); + } + if (rootGroups.contains(child)) + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... Primary created from " + + group.gid + " to " + child.gid); + } + group.children.add(child); + rootGroups.remove(child); + } + else + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... Secondary created from " + + group.gid + " to " + child.gid); + } + secondaryLinks.add(new SecondaryLink(group.gid, child.gid)); + } + } + else + { + if (s_logger.isDebugEnabled()) + { + s_logger.debug("... is member"); + } + group.members.add(id); + } + } + } + } + } + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Top " + rootGroups.size()); + s_logger.debug("Secondary " + secondaryLinks.size()); + } + } + catch (NamingException e) + { + throw new ExportSourceImporterException("Failed to import people.", e); + } + finally + { + if (ctx != null) + { + try + { + ctx.close(); + } + catch (NamingException e) + { + throw new ExportSourceImporterException("Failed to import people.", e); + } + } + } + } + + private static class Group + { + String gid; + + String guid = GUID.generate(); + + HashSet children = new HashSet(); + + HashSet members = new HashSet(); + + HashSet distinguishedNames = new HashSet(); + + private Group(String gid) + { + this.gid = "GROUP_" + gid; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (!(o instanceof Group)) + { + return false; + } + Group g = (Group) o; + return this.gid.equals(g.gid); + } + + @Override + public int hashCode() + { + return gid.hashCode(); + } + } + + private static class SecondaryLink + { + String from; + + String to; + + private SecondaryLink(String from, String to) + { + this.from = from; + this.to = to; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (!(o instanceof Group)) + { + return false; + } + SecondaryLink l = (SecondaryLink) o; + return EqualsHelper.nullSafeEquals(this.from, l.from) && EqualsHelper.nullSafeEquals(this.to, l.to); + } + + @Override + public int hashCode() + { + int hashCode = 0; + if (from != null) + { + hashCode = hashCode * 37 + from.hashCode(); + } + if (to != null) + { + hashCode = hashCode * 37 + to.hashCode(); + } + return hashCode; + } + } + + public static void main(String[] args) throws IOException + { + ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + ExportSource source = (ExportSource) ctx.getBean("ldapGroupExportSource"); + + File file = new File(args[0]); + Writer writer = new BufferedWriter(new FileWriter(file)); + XMLWriter xmlWriter = createXMLExporter(writer); + source.generateExport(xmlWriter); + xmlWriter.close(); + + } + + private static XMLWriter createXMLExporter(Writer writer) + { + // Define output format + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(3); + format.setEncoding("UTF-8"); + + // Construct an XML Exporter + + XMLWriter xmlWriter = new XMLWriter(writer, format); + return xmlWriter; + } + + public void afterPropertiesSet() throws Exception + { + viewRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "reference", namespaceService); + viewId = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "id", namespaceService); + viewIdRef = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "idref", namespaceService); + viewAssociations = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "associations", namespaceService); + childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); + viewValueQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "value", namespaceService); + + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactory.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactory.java new file mode 100644 index 0000000000..2373485558 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ldap; + +import java.util.Map; + +import javax.naming.directory.InitialDirContext; + +import org.alfresco.repo.security.authentication.AuthenticationException; + +/** + * Interface that defines a factory for obtaining ldap directory contexts. + * + * @author Andy Hind + */ +public interface LDAPInitialDirContextFactory +{ + /** + * Set the LDAP environment Hashtable properties used ot initialise the LDAP connection. + * + * @param environment + */ + public void setInitialDirContextEnvironment(Map environment); + + /** + * Use the environment properties and connect to the LDAP server. + * Used to obtain read only access to the LDAP server. + * + * @return + * @throws AuthenticationException + */ + public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException; + + /** + * Augment the connection environment with the identity and credentials and bind to the ldap server. + * Mainly used to validate a user's credentials during authentication. + * + * @param principal + * @param credentials + * @return + * @throws AuthenticationException + */ + public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException; +} diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactoryImpl.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactoryImpl.java new file mode 100644 index 0000000000..cd3bad6a75 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactoryImpl.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ldap; + +import java.util.Collections; +import java.util.Hashtable; +import java.util.Map; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.InitialDirContext; + +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFactory +{ + private Map initialDirContextEnvironment = Collections. emptyMap(); + + static + { + System.setProperty("javax.security.auth.useSubjectCredentialsOnly", "false"); + } + + public LDAPInitialDirContextFactoryImpl() + { + super(); + } + + public void setInitialDirContextEnvironment(Map initialDirContextEnvironment) + + { + this.initialDirContextEnvironment = initialDirContextEnvironment; + } + + public Map getInitialDirContextEnvironment() + { + return initialDirContextEnvironment; + } + + public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException + { + Hashtable env = new Hashtable(initialDirContextEnvironment.size()); + env.putAll(initialDirContextEnvironment); + env.put("javax.security.auth.useSubjectCredsOnly", "false"); + return buildInitialDirContext(env); + } + + private InitialDirContext buildInitialDirContext(Hashtable env) throws AuthenticationException + { + try + { + return new InitialDirContext(env); + } + catch (javax.naming.AuthenticationException ax) + { + throw new AuthenticationException("LDAP authentication failed.", ax); + } + catch (NamingException nx) + { + throw new AuthenticationException("Unable to connect to LDAP Server; check LDAP configuration", nx); + } + } + + public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException + { + if (principal == null) + { + throw new AuthenticationException("Null user name provided."); + } + + if (credentials == null) + { + throw new AuthenticationException("No credentials provided."); + } + Hashtable env = new Hashtable(initialDirContextEnvironment.size()); + env.putAll(initialDirContextEnvironment); + env.put(Context.SECURITY_PRINCIPAL, principal); + env.put(Context.SECURITY_CREDENTIALS, credentials); + + return buildInitialDirContext(env); + } + + public static void main(String[] args) + { + // ....build a pyramid selling scheme ..... + + // A group has three user members and 2 group members .... and off we go .... + // We make the people and groups to represent this and stick them into LDAP ...used to populate a test data base for user and groups + + int userMembers = Integer.parseInt(args[3]); + + ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); + LDAPInitialDirContextFactory factory = (LDAPInitialDirContextFactory) applicationContext + .getBean("ldapInitialDirContextFactory"); + + InitialDirContext ctx = null; + try + { + ctx = factory.getInitialDirContext("cn=" + args[0] + "," + args[2], args[1]); + + /* Values we'll use in creating the entry */ + Attribute objClasses = new BasicAttribute("objectclass"); + objClasses.add("top"); + objClasses.add("person"); + objClasses.add("organizationalPerson"); + objClasses.add("inetOrgPerson"); + + for (int i = 0; i < userMembers; i++) + { + + Attribute cn = new BasicAttribute("cn", "User" + i + " TestUser"); + Attribute sn = new BasicAttribute("sn", "TestUser"); + Attribute givenNames = new BasicAttribute("givenName", "User" + i); + Attribute telephoneNumber = new BasicAttribute("telephoneNumber", "123"); + Attribute uid = new BasicAttribute("uid", "User" + i); + Attribute mail = new BasicAttribute("mail", "woof@woof"); + Attribute o = new BasicAttribute("o", "Alfresco"); + Attribute userPassword = new BasicAttribute("userPassword", "bobbins"); + /* Specify the DN we're adding */ + String dn = "cn=User" + i + " TestUser," + args[2]; + + Attributes orig = new BasicAttributes(); + orig.put(objClasses); + orig.put(cn); + orig.put(sn); + orig.put(givenNames); + orig.put(telephoneNumber); + orig.put(uid); + orig.put(mail); + orig.put(o); + orig.put(userPassword); + + try + { + ctx.destroySubcontext(dn); + } + catch (NamingException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + ctx.createSubcontext(dn, orig); + } + + } + catch (NamingException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + finally + { + if (ctx != null) + { + try + { + ctx.close(); + } + catch (NamingException e) + { + + e.printStackTrace(); + } + } + } + + } + +} diff --git a/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java new file mode 100644 index 0000000000..797d2640b7 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ldap; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Collection; +import java.util.Map; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.importer.ExportSource; +import org.alfresco.repo.importer.ExportSourceImporterException; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.springframework.context.ApplicationContext; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +public class LDAPPersonExportSource implements ExportSource +{ + private static Log s_logger = LogFactory.getLog(LDAPPersonExportSource.class); + + private String personQuery = "(objectclass=inetOrgPerson)"; + + private String searchBase; + + private String userIdAttributeName; + + private LDAPInitialDirContextFactory ldapInitialContextFactory; + + private PersonService personService; + + private Map attributeMapping; + + private NamespaceService namespaceService; + + private String defaultHomeFolder; + + public LDAPPersonExportSource() + { + super(); + } + + public void setPersonQuery(String personQuery) + { + this.personQuery = personQuery; + } + + public void setSearchBase(String searchBase) + { + this.searchBase = searchBase; + } + + public void setUserIdAttributeName(String userIdAttributeName) + { + this.userIdAttributeName = userIdAttributeName; + } + + public void setLDAPInitialDirContextFactory(LDAPInitialDirContextFactory ldapInitialDirContextFactory) + { + this.ldapInitialContextFactory = ldapInitialDirContextFactory; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setDefaultHomeFolder(String defaultHomeFolder) + { + this.defaultHomeFolder = defaultHomeFolder; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setAttributeMapping(Map attributeMapping) + { + this.attributeMapping = attributeMapping; + } + + public void generateExport(XMLWriter writer) + { + QName nodeUUID = QName.createQName("sys:node-uuid", namespaceService); + + Collection prefixes = namespaceService.getPrefixes(); + QName childQName = QName.createQName(NamespaceService.REPOSITORY_VIEW_PREFIX, "childName", namespaceService); + + try + { + AttributesImpl attrs = new AttributesImpl(); + attrs.addAttribute(NamespaceService.REPOSITORY_VIEW_1_0_URI, childQName.getLocalName(), childQName + .toPrefixString(), null, ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); + + writer.startDocument(); + + for (String prefix : prefixes) + { + if (!prefix.equals("xml")) + { + String uri = namespaceService.getNamespaceURI(prefix); + writer.startPrefixMapping(prefix, uri); + } + } + + writer.startElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", + NamespaceService.REPOSITORY_VIEW_PREFIX + ":" + "view", new AttributesImpl()); + + InitialDirContext ctx = null; + try + { + ctx = ldapInitialContextFactory.getDefaultIntialDirContext(); + + // Authentication has been successful. + // Set the current user, they are now authenticated. + + SearchControls userSearchCtls = new SearchControls(); + userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); + System.out.println("COUNT "+userSearchCtls.getCountLimit()); + System.out.println("TIME "+userSearchCtls.getTimeLimit()); + userSearchCtls.setCountLimit(Integer.MAX_VALUE); + + NamingEnumeration searchResults = ctx.search(searchBase, personQuery, userSearchCtls); + while (searchResults.hasMoreElements()) + { + SearchResult result = (SearchResult) searchResults.next(); + Attributes attributes = result.getAttributes(); + Attribute uidAttribute = attributes.get(userIdAttributeName); + String uid = (String) uidAttribute.get(0); + + if(s_logger.isDebugEnabled()) + { + s_logger.debug("Adding user for "+uid); + } + System.out.println("User "+uid); + + writer.startElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON + .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService), attrs); + + // permissions + + // owner + + writer.startElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE + .getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService), + new AttributesImpl()); + + writer.endElement(ContentModel.ASPECT_OWNABLE.getNamespaceURI(), ContentModel.ASPECT_OWNABLE + .getLocalName(), ContentModel.ASPECT_OWNABLE.toPrefixString(namespaceService)); + + writer.startElement(ContentModel.PROP_OWNER.getNamespaceURI(), ContentModel.PROP_OWNER + .getLocalName(), ContentModel.PROP_OWNER.toPrefixString(namespaceService), + new AttributesImpl()); + + writer.characters(uid.toCharArray(), 0, uid.length()); + + writer.endElement(ContentModel.PROP_OWNER.getNamespaceURI(), + ContentModel.PROP_OWNER.getLocalName(), ContentModel.PROP_OWNER + .toPrefixString(namespaceService)); + + for (String key : attributeMapping.keySet()) + { + QName keyQName = QName.createQName(key, namespaceService); + + writer.startElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName + .toPrefixString(namespaceService), new AttributesImpl()); + + // cater for null + String attribute = attributeMapping.get(key); + if (attribute != null) + { + String value = (String) attributes.get(attribute).get(0); + if (value != null) + { + writer.characters(value.toCharArray(), 0, value.length()); + } + } + + writer.endElement(keyQName.getNamespaceURI(), keyQName.getLocalName(), keyQName + .toPrefixString(namespaceService)); + } + + // Default home folder + + if (!(attributeMapping.keySet().contains(ContentModel.PROP_HOMEFOLDER.toString()) || attributeMapping + .keySet().contains(ContentModel.PROP_HOMEFOLDER.toPrefixString(namespaceService)))) + { + // Only if we are creating the person for the first time + if (!personService.personExists(uid)) + { + writer.startElement(ContentModel.PROP_HOMEFOLDER.getNamespaceURI(), + ContentModel.PROP_HOMEFOLDER.getLocalName(), ContentModel.PROP_HOMEFOLDER + .toPrefixString(namespaceService), new AttributesImpl()); + + if (defaultHomeFolder != null) + { + writer.characters(defaultHomeFolder.toCharArray(), 0, defaultHomeFolder.length()); + } + + writer.endElement(ContentModel.PROP_HOMEFOLDER.getNamespaceURI(), + ContentModel.PROP_HOMEFOLDER.getLocalName(), ContentModel.PROP_HOMEFOLDER + .toPrefixString(namespaceService)); + } + } + + if (personService.personExists(uid)) + { + String uguid = personService.getPerson(uid).getId(); + + writer.startElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID + .toPrefixString(namespaceService), new AttributesImpl()); + + writer.characters(uguid.toCharArray(), 0, uguid.length()); + + writer.endElement(nodeUUID.getNamespaceURI(), nodeUUID.getLocalName(), nodeUUID + .toPrefixString(namespaceService)); + } + writer.endElement(ContentModel.TYPE_PERSON.getNamespaceURI(), ContentModel.TYPE_PERSON + .getLocalName(), ContentModel.TYPE_PERSON.toPrefixString(namespaceService)); + + } + + } + catch (NamingException e) + { + throw new ExportSourceImporterException("Failed to import people.", e); + } + finally + { + if (ctx != null) + { + try + { + ctx.close(); + } + catch (NamingException e) + { + throw new ExportSourceImporterException("Failed to import people.", e); + } + } + } + + for (String prefix : prefixes) + { + if (!prefix.equals("xml")) + { + writer.endPrefixMapping(prefix); + } + } + + writer.endElement(NamespaceService.REPOSITORY_VIEW_PREFIX, "view", NamespaceService.REPOSITORY_VIEW_PREFIX + + ":" + "view"); + + writer.endDocument(); + } + catch (SAXException e) + { + throw new ExportSourceImporterException("Failed to create file for import.", e); + } + } + + public static void main(String[] args) throws IOException + { + ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + ExportSource source = (ExportSource) ctx.getBean("ldapPeopleExportSource"); + + File file = new File(args[0]); + Writer writer = new BufferedWriter(new FileWriter(file)); + XMLWriter xmlWriter = createXMLExporter(writer); + source.generateExport(xmlWriter); + xmlWriter.close(); + + } + + private static XMLWriter createXMLExporter(Writer writer) + { + // Define output format + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(3); + format.setEncoding("UTF-8"); + + // Construct an XML Exporter + + XMLWriter xmlWriter = new XMLWriter(writer, format); + return xmlWriter; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java new file mode 100644 index 0000000000..e8e8b7f9b0 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java @@ -0,0 +1,932 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ntlm; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.Enumeration; +import java.util.Hashtable; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.AuthenticationServiceException; +import net.sf.acegisecurity.BadCredentialsException; +import net.sf.acegisecurity.CredentialsExpiredException; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.server.auth.PasswordEncryptor; +import org.alfresco.filesys.server.auth.passthru.AuthenticateSession; +import org.alfresco.filesys.server.auth.passthru.PassthruServers; +import org.alfresco.filesys.smb.SMBException; +import org.alfresco.filesys.smb.SMBStatus; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.NTLMMode; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PersonService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * NTLM Authentication Component Class + * + *

Provides authentication using passthru to a Windows server(s)/domain controller(s) using the accounts + * defined on the passthru server to validate users. + * + * @author GKSpencer + */ +public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationComponent +{ + // Logging + + private static final Log logger = LogFactory.getLog("org.alfresco.passthru.auth"); + + // Constants + // + // Standard authorities + + public static final String NTLMAuthorityGuest = "Guest"; + public static final String NTLMAuthorityAdministrator = "Administrator"; + + // Active session timeout + + private static final long DefaultSessionTimeout = 60000L; // 1 minute + private static final long MinimumSessionTimeout = 5000L; // 5 seconds + + // Passthru authentication servers + + private PassthruServers m_passthruServers; + + // Password encryptor for generating password hash for local authentication + + private PasswordEncryptor m_encryptor; + + // Allow guest access + + private boolean m_allowGuest; + + // Table of currently active passthru authentications and the associated authentication session + // + // If the two authentication stages are not completed within a reasonable time the authentication + // session will be closed by the reaper thread. + + private Hashtable m_passthruSessions; + + // Active authentication session timeout, in milliseconds + + private long m_passthruSessTmo = DefaultSessionTimeout; + + // Authentication session reaper thread + + private PassthruReaperThread m_reaperThread; + + // Person service, used to map passthru usernames to Alfresco person names + + private PersonService m_personService; + private NodeService m_nodeService; + + /** + * Passthru Session Reaper Thread + */ + class PassthruReaperThread extends Thread + { + // Thread shutdown request flag + + private boolean m_ishutdown; + + // Reaper wakeup interval, in milliseconds + + private long m_wakeupInterval = m_passthruSessTmo / 2; + + /** + * Default constructor + */ + PassthruReaperThread() + { + setDaemon(true); + setName("PassthruReaper"); + start(); + } + + /** + * Set the wakeup interval + * + * @param wakeup long + */ + public final void setWakeup(long wakeup) + { + m_wakeupInterval = wakeup; + } + + /** + * Main thread code + */ + public void run() + { + // Loop until shutdown + + m_ishutdown = false; + + while ( m_ishutdown == false) + { + // Sleep for a while + + try + { + sleep( m_wakeupInterval); + } + catch ( InterruptedException ex) + { + } + + // Check if there are any active sessions to check + + if ( m_passthruSessions.size() > 0) + { + // Enumerate the active sessions + + Enumeration tokenEnum = m_passthruSessions.keys(); + long timeNow = System.currentTimeMillis(); + + while (tokenEnum.hasMoreElements()) + { + // Get the current NTLM token and check if it has expired + + NTLMPassthruToken ntlmToken = tokenEnum.nextElement(); + + if ( ntlmToken != null && ntlmToken.getAuthenticationExpireTime() < timeNow) + { + // Authentication token has expired, close the associated authentication session + + AuthenticateSession authSess = m_passthruSessions.get(ntlmToken); + if ( authSess != null) + { + try + { + // Close the authentication session + + authSess.CloseSession(); + } + catch ( Exception ex) + { + // Debug + + if(logger.isDebugEnabled()) + logger.debug("Error closing expired authentication session", ex); + } + } + + // Remove the expired token from the active list + + m_passthruSessions.remove(ntlmToken); + + // Debug + + if(logger.isDebugEnabled()) + logger.debug("Removed expired NTLM token " + ntlmToken); + } + } + } + } + + // Debug + + if(logger.isDebugEnabled()) + logger.debug("Passthru reaper thread shutdown"); + } + + /** + * Shutdown the reaper thread + */ + public final void shutdownRequest() + { + m_ishutdown = true; + this.interrupt(); + } + } + + /** + * Class constructor + */ + public NTLMAuthenticationComponentImpl() { + + // Create the passthru authentication server list + + m_passthruServers = new PassthruServers(); + + // Create the password encryptor for local password hashing + + m_encryptor = new PasswordEncryptor(); + + // Create the active session list and reaper thread + + m_passthruSessions = new Hashtable(); + m_reaperThread = new PassthruReaperThread(); + } + + /** + * Determine if guest logons are allowed + * + * @return boolean + */ + public final boolean allowsGuest() + { + return m_allowGuest; + } + + /** + * Set the domain to authenticate against + * + * @param domain String + */ + public void setDomain(String domain) { + + // Check if the passthru server list is already configured + + if ( m_passthruServers.getTotalServerCount() > 0) + throw new AlfrescoRuntimeException("Passthru server list already configured"); + + // Configure the passthru authentication server list using the domain controllers + + m_passthruServers.setDomain(domain); + } + + /** + * Set the server(s) to authenticate against + * + * @param servers String + */ + public void setServers(String servers) { + + // Check if the passthru server list is already configured + + if ( m_passthruServers.getTotalServerCount() > 0) + throw new AlfrescoRuntimeException("Passthru server list already configured"); + + // Configure the passthru authenticaiton list using a list of server names/addresses + + m_passthruServers.setServerList(servers); + } + + /** + * Use the local server as the authentication server + * + * @param useLocal String + */ + public void setUseLocalServer(String useLocal) + { + // Check if the local server should be used for authentication + + if ( Boolean.parseBoolean(useLocal) == true) + { + // Check if the passthru server list is already configured + + if ( m_passthruServers.getTotalServerCount() > 0) + throw new AlfrescoRuntimeException("Passthru server list already configured"); + + try + { + // Get the list of local network addresses + + InetAddress[] localAddrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); + + // Build the list of local addresses + + if ( localAddrs != null && localAddrs.length > 0) + { + StringBuilder addrStr = new StringBuilder(); + + for ( InetAddress curAddr : localAddrs) + { + if ( curAddr.isLoopbackAddress() == false) + { + addrStr.append(curAddr.getHostAddress()); + addrStr.append(","); + } + } + + if ( addrStr.length() > 0) + addrStr.setLength(addrStr.length() - 1); + + // Set the server list using the local address list + + m_passthruServers.setServerList(addrStr.toString()); + } + else + throw new AlfrescoRuntimeException("No local server address(es)"); + } + catch ( UnknownHostException ex) + { + throw new AlfrescoRuntimeException("Failed to get local address list"); + } + } + } + + /** + * Allow guest access + * + * @param guest String + */ + public void setGuestAccess(String guest) + { + m_allowGuest = Boolean.parseBoolean(guest); + } + + /** + * Set the JCE provider + * + * @param providerClass String + */ + public void setJCEProvider(String providerClass) + { + // Set the JCE provider, required to provide various encryption/hashing algorithms not available + // in the standard Sun JDK/JRE + + try + { + + // Load the JCE provider class and validate + + Object jceObj = Class.forName(providerClass).newInstance(); + if (jceObj instanceof java.security.Provider) + { + + // Inform listeners, validate the configuration change + + Provider jceProvider = (Provider) jceObj; + + // Add the JCE provider + + Security.addProvider(jceProvider); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Using JCE provider " + providerClass); + } + else + { + throw new AlfrescoRuntimeException("JCE provider class is not a valid Provider class"); + } + } + catch (ClassNotFoundException ex) + { + throw new AlfrescoRuntimeException("JCE provider class " + providerClass + " not found"); + } + catch (Exception ex) + { + throw new AlfrescoRuntimeException("JCE provider class error", ex); + } + } + + /** + * Set the authentication session timeout, in seconds + * + * @param sessTmo String + */ + public void setSessionTimeout(String sessTmo) + { + // Convert to an integer value and range check the timeout value + + try + { + // Convert to an integer value + + long sessTmoMilli = Long.parseLong(sessTmo) * 1000L; + + if ( sessTmoMilli < MinimumSessionTimeout) + throw new AlfrescoRuntimeException("Authentication session timeout too low, " + sessTmo); + + // Set the authentication session timeout value + + m_passthruSessTmo = sessTmoMilli; + + // Set the reaper thread wakeup interval + + m_reaperThread.setWakeup( sessTmoMilli / 2); + } + catch(NumberFormatException ex) + { + throw new AlfrescoRuntimeException("Invalid authenication session timeout value"); + } + } + + /** + * Set the person service + * + * @param personService PersonService + */ + public final void setPersonService(PersonService personService) + { + m_personService = personService; + } + + /** + * Set the node service + * + * @param nodeService NodeService + */ + public final void setNodeService(NodeService nodeService) + { + m_nodeService = nodeService; + } + + /** + * Return the authentication session timeout, in milliseconds + * + * @return long + */ + private final long getSessionTimeout() + { + return m_passthruSessTmo; + } + + /** + * Authenticate + * + * @param userName String + * @param password char[] + * @throws AuthenticationException + */ + public void authenticate(String userName, char[] password) throws AuthenticationException + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Authenticate user=" + userName + " via local credentials"); + + // Create a local authentication token + + NTLMLocalToken authToken = new NTLMLocalToken(userName, new String(password)); + + // Authenticate using the token + + authenticate( authToken); + setCurrentUser( userName.toLowerCase()); + } + + /** + * Authenticate using a token + * + * @param token Authentication + * @return Authentication + * @throws AuthenticationException + */ + public Authentication authenticate(Authentication auth) throws AuthenticationException + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Authenticate " + auth + " via token"); + + // Check if the token is for passthru authentication + + if( auth instanceof NTLMPassthruToken) + { + // Access the NTLM passthru token + + NTLMPassthruToken ntlmToken = (NTLMPassthruToken) auth; + + // Authenticate using passthru + + authenticatePassthru(ntlmToken); + } + + // Check for a local authentication token + + else if( auth instanceof NTLMLocalToken) + { + AuthenticateSession authSess = null; + + try + { + + // Access the NTLM token + + NTLMLocalToken ntlmToken = (NTLMLocalToken) auth; + + // Open a session to an authentication server + + authSess = m_passthruServers.openSession(); + + // Authenticate using the credentials supplied + + authenticateLocal(ntlmToken, authSess); + } + finally + { + // Make sure the authentication session is closed + + if ( authSess != null) + { + try + { + authSess.CloseSession(); + } + catch ( Exception ex) + { + } + } + } + } + else + { + // Unsupported authentication token + + throw new AuthenticationException("Unsupported authentication token type"); + } + + // Return the updated authentication token + + return getCurrentAuthentication(); + } + + /** + * Get the enum that describes NTLM integration + * + * @return NTLMMode + */ + public NTLMMode getNTLMMode() + { + return NTLMMode.PASS_THROUGH; + } + + /** + * Get the MD4 password hash, as required by NTLM based authentication methods. + * + * @param userName String + * @return String + */ + public String getMD4HashedPassword(String userName) + { + // Do not support MD4 hashed password + + throw new AlfrescoRuntimeException("MD4 passwords not supported"); + } + + /** + * Authenticate a user using local credentials + * + * @param ntlmToken NTLMLocalToken + * @param authSess AuthenticateSession + */ + private void authenticateLocal(NTLMLocalToken ntlmToken, AuthenticateSession authSess) + { + try + { + // Get the plaintext password and generate an NTLM1 password hash + + String username = (String) ntlmToken.getPrincipal(); + String plainPwd = (String) ntlmToken.getCredentials(); + byte[] ntlm1Pwd = m_encryptor.generateEncryptedPassword( plainPwd, authSess.getEncryptionKey(), PasswordEncryptor.NTLM1); + + // Send the logon request to the authentication server + // + // Note: Only use the stronger NTLM hash, we do not send the LM hash + + authSess.doSessionSetup(username, null, ntlm1Pwd); + + // Check if the session has logged on as a guest + + if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST")) + { + // If guest access is enabled add a guest authority to the token + + if ( allowsGuest()) + { + // Set the guest authority + + GrantedAuthority[] authorities = new GrantedAuthority[2]; + authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest); + authorities[1] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED"); + + ntlmToken.setAuthorities(authorities); + } + else + { + // Guest access not allowed + + throw new AuthenticationException("Guest logons disabled"); + } + } + else + { + // Set authorities + + GrantedAuthority[] authorities = new GrantedAuthority[1]; + authorities[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED"); + + ntlmToken.setAuthorities(authorities); + } + + // Indicate that the token is authenticated + + ntlmToken.setAuthenticated(true); + + // Map the passthru username to an Alfresco person + + NodeRef userNode = m_personService.getPerson(username); + if ( userNode != null) + { + // Get the person name and use that as the current user to line up with permission checks + + String personName = (String) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME); + setCurrentUser(personName); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Setting current user using person " + personName + " (username " + username + ")"); + } + else + { + // Set using the user name, lowercase the name if hte person service is case insensitive + + if ( m_personService.getUserNamesAreCaseSensitive() == false) + username = username.toLowerCase(); + setCurrentUser( username); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Setting current user using username " + username); + } + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Authenticated token=" + ntlmToken); + } + catch (NoSuchAlgorithmException ex) + { + // JCE provider does not have the required encryption/hashing algorithms + + throw new AuthenticationServiceException("JCE provider error", ex); + } + catch (IOException ex) + { + // Error connecting to the authentication server + + throw new AuthenticationServiceException("I/O error", ex); + } + catch (SMBException ex) + { + // Check the returned status code to determine why the logon failed and throw an appropriate exception + + if ( ex.getErrorClass() == SMBStatus.NTErr) + { + AuthenticationException authEx = null; + + switch( ex.getErrorCode()) + { + case SMBStatus.NTLogonFailure: + authEx = new AuthenticationException("Logon failure"); + break; + case SMBStatus.NTAccountDisabled: + authEx = new AuthenticationException("Account disabled"); + break; + default: + authEx = new AuthenticationException("Logon failure"); + break; + } + + throw authEx; + } + else + throw new AuthenticationException("Logon failure"); + } + } + + /** + * Authenticate using passthru authentication with a client + * + * @param ntlmToken NTLMPassthruToken + */ + private void authenticatePassthru(NTLMPassthruToken ntlmToken) + { + // Check if the token has an authentication session, if not then it is either a new token + // or the session has been timed out + + AuthenticateSession authSess = m_passthruSessions.get(ntlmToken); + + if ( authSess == null) + { + // Check if the token has a challenge, if it does then the associated session has been + // timed out + + if ( ntlmToken.getChallenge() != null) + throw new CredentialsExpiredException("Authentication session expired"); + + // Open an authentication session for the new token and add to the active session list + + authSess = m_passthruServers.openSession(); + + ntlmToken.setAuthenticationExpireTime(System.currentTimeMillis() + getSessionTimeout()); + + // Get the challenge from the initial session negotiate stage + + ntlmToken.setChallenge(new NTLMChallenge(authSess.getEncryptionKey())); + + StringBuilder details = new StringBuilder(); + + // Build a details string with the authentication session details + + details.append(authSess.getDomain()); + details.append("\\"); + details.append(authSess.getPCShare().getNodeName()); + details.append(","); + details.append(authSess.getSession().getProtocolName()); + + ntlmToken.setDetails(details.toString()); + + // Put the token/session into the active session list + + m_passthruSessions.put(ntlmToken, authSess); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Passthru stage 1 token " + ntlmToken); + } + else + { + try + { + // Stage two of the authentication, send the hashed password to the authentication server + + byte[] lmPwd = null; + byte[] ntlmPwd = null; + + if ( ntlmToken.getPasswordType() == PasswordEncryptor.LANMAN) + lmPwd = ntlmToken.getHashedPassword(); + else if ( ntlmToken.getPasswordType() == PasswordEncryptor.NTLM1) + ntlmPwd = ntlmToken.getHashedPassword(); + + String username = (String) ntlmToken.getPrincipal(); + + authSess.doSessionSetup(username, lmPwd, ntlmPwd); + + // Check if the session has logged on as a guest + + if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST")) + { + // If guest access is enabled add a guest authority to the token + + if ( allowsGuest()) + { + // Set the guest authority + + GrantedAuthority[] authorities = new GrantedAuthority[1]; + authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest); + + ntlmToken.setAuthorities(authorities); + } + else + { + // Guest access not allowed + + throw new BadCredentialsException("Guest logons disabled"); + } + } + + // Indicate that the token is authenticated + + ntlmToken.setAuthenticated(true); + + // Map the passthru username to an Alfresco person + + NodeRef userNode = m_personService.getPerson(username); + if ( userNode != null) + { + // Get the person name and use that as the current user to line up with permission checks + + String personName = (String) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME); + setCurrentUser(personName); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Setting current user using person " + personName + " (username " + username + ")"); + } + else + { + // Set using the user name, lowercase the name if the person service is case insensitive + + if ( m_personService.getUserNamesAreCaseSensitive() == false) + username = username.toLowerCase(); + setCurrentUser( username); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Setting current user using username " + username); + } + } + catch (IOException ex) + { + // Error connecting to the authentication server + + throw new AuthenticationServiceException("I/O error", ex); + } + catch (SMBException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Passthru exception, " + ex); + + // Check the returned status code to determine why the logon failed and throw an appropriate exception + + if ( ex.getErrorClass() == SMBStatus.NTErr) + { + AuthenticationException authEx = null; + + switch( ex.getErrorCode()) + { + case SMBStatus.NTLogonFailure: + authEx = new AuthenticationException("Logon failure"); + break; + case SMBStatus.NTAccountDisabled: + authEx = new AuthenticationException("Account disabled"); + break; + default: + authEx = new AuthenticationException("Logon failure"); + break; + } + + throw authEx; + } + else + throw new BadCredentialsException("Logon failure"); + } + finally + { + // Make sure the authentication session is closed + + if ( authSess != null) + { + try + { + // Remove the session from the active list + + m_passthruSessions.remove(ntlmToken); + + // Close the session to the authentication server + + authSess.CloseSession(); + } + catch (Exception ex) + { + } + } + } + } + } + + /** + * Check if the user exists + * + * @param userName String + * @return boolean + */ + public boolean exists(String userName) + { + throw new UnsupportedOperationException(); + } + + @Override + protected boolean implementationAllowsGuestLogin() + { + return allowsGuest(); + } + + +} diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationProvider.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationProvider.java new file mode 100644 index 0000000000..becdc3d281 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationProvider.java @@ -0,0 +1,755 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ntlm; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.server.auth.PasswordEncryptor; +import org.alfresco.filesys.server.auth.passthru.AuthenticateSession; +import org.alfresco.filesys.server.auth.passthru.PassthruServers; +import org.alfresco.filesys.smb.SMBException; +import org.alfresco.filesys.smb.SMBStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import net.sf.acegisecurity.*; +import net.sf.acegisecurity.providers.*; + +/** + * NTLM Authentication Provider + * + * @author GKSpencer + */ +public class NTLMAuthenticationProvider implements AuthenticationProvider +{ + private static final Log logger = LogFactory.getLog("org.alfresco.acegi"); + + // Constants + // + // Standard authorities + + public static final String NTLMAuthorityGuest = "Guest"; + public static final String NTLMAuthorityAdministrator = "Administrator"; + + // Active session timeout + + private static final long DefaultSessionTimeout = 60000L; // 1 minute + private static final long MinimumSessionTimeout = 5000L; // 5 seconds + + // Passthru authentication servers + + private PassthruServers m_passthruServers; + + // Password encryptor for generating password hash for local authentication + + private PasswordEncryptor m_encryptor; + + // Allow guest access + + private boolean m_allowGuest; + + // Table of currently active passthru authentications and the associated authentication session + // + // If the two authentication stages are not completed within a reasonable time the authentication + // session will be closed by the reaper thread. + + private Hashtable m_passthruSessions; + + // Active authentication session timeout, in milliseconds + + private long m_passthruSessTmo = DefaultSessionTimeout; + + // Authentication session reaper thread + + private PassthruReaperThread m_reaperThread; + + /** + * Passthru Session Repear Thread + */ + class PassthruReaperThread extends Thread + { + // Thread shutdown request flag + + private boolean m_ishutdown; + + // Reaper wakeup interval, in milliseconds + + private long m_wakeupInterval = m_passthruSessTmo / 2; + + /** + * Default constructor + */ + PassthruReaperThread() + { + setDaemon(true); + setName("PassthruReaper"); + start(); + } + + /** + * Set the wakeup interval + * + * @param wakeup long + */ + public final void setWakeup(long wakeup) + { + m_wakeupInterval = wakeup; + } + + /** + * Main thread code + */ + public void run() + { + // Loop until shutdown + + m_ishutdown = false; + + while ( m_ishutdown == false) + { + // Sleep for a while + + try + { + sleep( m_wakeupInterval); + } + catch ( InterruptedException ex) + { + } + + // Check if there are any active sessions to check + + if ( m_passthruSessions.size() > 0) + { + // Enumerate the active sessions + + Enumeration tokenEnum = m_passthruSessions.keys(); + long timeNow = System.currentTimeMillis(); + + while (tokenEnum.hasMoreElements()) + { + // Get the current NTLM token and check if it has expired + + NTLMPassthruToken ntlmToken = tokenEnum.nextElement(); + + if ( ntlmToken != null && ntlmToken.getAuthenticationExpireTime() < timeNow) + { + // Authentication token has expired, close the associated authentication session + + AuthenticateSession authSess = m_passthruSessions.get(ntlmToken); + if ( authSess != null) + { + try + { + // Close the authentication session + + authSess.CloseSession(); + } + catch ( Exception ex) + { + // Debug + + if(logger.isDebugEnabled()) + logger.debug("Error closing expired authentication session", ex); + } + } + + // Remove the expired token from the active list + + m_passthruSessions.remove(ntlmToken); + + // Debug + + if(logger.isDebugEnabled()) + logger.debug("Removed expired NTLM token " + ntlmToken); + } + } + } + } + + // Debug + + if(logger.isDebugEnabled()) + logger.debug("Passthru reaper thread shutdown"); + } + + /** + * Shutdown the reaper thread + */ + public final void shutdownRequest() + { + m_ishutdown = true; + this.interrupt(); + } + } + + /** + * Class constructor + */ + public NTLMAuthenticationProvider() { + + // Create the passthru authentication server list + + m_passthruServers = new PassthruServers(); + + // Create the password encryptor for local password hashing + + m_encryptor = new PasswordEncryptor(); + + // Create the active session list and reaper thread + + m_passthruSessions = new Hashtable(); + m_reaperThread = new PassthruReaperThread(); + } + + /** + * Authenticate a user + * + * @param auth Authentication + * @return Authentication + * @exception AuthenticationException + */ + public Authentication authenticate(Authentication auth) throws AuthenticationException + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Authenticate " + auth); + + // Check if the token is for passthru authentication + + if( auth instanceof NTLMPassthruToken) + { + // Access the NTLM passthru token + + NTLMPassthruToken ntlmToken = (NTLMPassthruToken) auth; + + // Authenticate using passthru + + authenticatePassthru(ntlmToken); + } + + // Check for a local authentication token + + else if( auth instanceof NTLMLocalToken) + { + AuthenticateSession authSess = null; + + try + { + + // Access the NTLM token + + NTLMLocalToken ntlmToken = (NTLMLocalToken) auth; + + // Open a session to an authentication server + + authSess = m_passthruServers.openSession(); + + // Authenticate using the credentials supplied + + authenticateLocal(ntlmToken, authSess); + } + finally + { + // Make sure the authentication session is closed + + if ( authSess != null) + { + try + { + authSess.CloseSession(); + } + catch ( Exception ex) + { + } + } + } + } + + // Return the updated authentication token + + return auth; + } + + /** + * Determine if this provider supports the specified authentication token + * + * @param authentication Class + */ + public boolean supports(Class authentication) + { + // Check if the authentication is an NTLM authentication token + + if ( NTLMPassthruToken.class.isAssignableFrom(authentication)) + return true; + return NTLMLocalToken.class.isAssignableFrom(authentication); + } + + /** + * Determine if guest logons are allowed + * + * @return boolean + */ + public final boolean allowsGuest() + { + return m_allowGuest; + } + + /** + * Set the domain to authenticate against + * + * @param domain String + */ + public final void setDomain(String domain) { + + // Check if the passthru server list is already configured + + if ( m_passthruServers.getTotalServerCount() > 0) + throw new AlfrescoRuntimeException("Passthru server list already configured"); + + // Configure the passthru authentication server list using the domain controllers + + m_passthruServers.setDomain(domain); + } + + /** + * Set the server(s) to authenticate against + * + * @param servers String + */ + public final void setServers(String servers) { + + // Check if the passthru server list is already configured + + if ( m_passthruServers.getTotalServerCount() > 0) + throw new AlfrescoRuntimeException("Passthru server list already configured"); + + // Configure the passthru authenticaiton list using a list of server names/addresses + + m_passthruServers.setServerList(servers); + } + + /** + * Use the local server as the authentication server + * + * @param useLocal String + */ + public final void setUseLocalServer(String useLocal) + { + // Check if the local server should be used for authentication + + if ( Boolean.parseBoolean(useLocal) == true) + { + // Check if the passthru server list is already configured + + if ( m_passthruServers.getTotalServerCount() > 0) + throw new AlfrescoRuntimeException("Passthru server list already configured"); + + try + { + // Get the list of local network addresses + + InetAddress[] localAddrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); + + // Build the list of local addresses + + if ( localAddrs != null && localAddrs.length > 0) + { + StringBuilder addrStr = new StringBuilder(); + + for ( InetAddress curAddr : localAddrs) + { + if ( curAddr.isLoopbackAddress() == false) + { + addrStr.append(curAddr.getHostAddress()); + addrStr.append(","); + } + } + + if ( addrStr.length() > 0) + addrStr.setLength(addrStr.length() - 1); + + // Set the server list using the local address list + + m_passthruServers.setServerList(addrStr.toString()); + } + else + throw new AlfrescoRuntimeException("No local server address(es)"); + } + catch ( UnknownHostException ex) + { + throw new AlfrescoRuntimeException("Failed to get local address list"); + } + } + } + + /** + * Allow guest access + * + * @param guest String + */ + public final void setGuestAccess(String guest) + { + m_allowGuest = Boolean.parseBoolean(guest); + } + + /** + * Set the JCE provider + * + * @param providerClass String + */ + public final void setJCEProvider(String providerClass) + { + // Set the JCE provider, required to provide various encryption/hashing algorithms not available + // in the standard Sun JDK/JRE + + try + { + + // Load the JCE provider class and validate + + Object jceObj = Class.forName(providerClass).newInstance(); + if (jceObj instanceof java.security.Provider) + { + + // Inform listeners, validate the configuration change + + Provider jceProvider = (Provider) jceObj; + + // Add the JCE provider + + Security.addProvider(jceProvider); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Using JCE provider " + providerClass); + } + else + { + throw new AlfrescoRuntimeException("JCE provider class is not a valid Provider class"); + } + } + catch (ClassNotFoundException ex) + { + throw new AlfrescoRuntimeException("JCE provider class " + providerClass + " not found"); + } + catch (Exception ex) + { + throw new AlfrescoRuntimeException("JCE provider class error", ex); + } + } + + /** + * Set the authentication session timeout, in seconds + * + * @param sessTmo String + */ + public final void setSessionTimeout(String sessTmo) + { + // Convert to an integer value and range check the timeout value + + try + { + // Convert to an integer value + + long sessTmoMilli = Long.parseLong(sessTmo) * 1000L; + + if ( sessTmoMilli < MinimumSessionTimeout) + throw new AlfrescoRuntimeException("Authentication session timeout too low, " + sessTmo); + + // Set the authentication session timeout value + + m_passthruSessTmo = sessTmoMilli; + + // Set the reaper thread wakeup interval + + m_reaperThread.setWakeup( sessTmoMilli / 2); + } + catch(NumberFormatException ex) + { + throw new AlfrescoRuntimeException("Invalid authenication session timeout value"); + } + } + + /** + * Return the authentication session timeout, in milliseconds + * + * @return long + */ + private final long getSessionTimeout() + { + return m_passthruSessTmo; + } + + /** + * Authenticate a user using local credentials + * + * @param ntlmToken NTLMLocalToken + * @param authSess AuthenticateSession + */ + private void authenticateLocal(NTLMLocalToken ntlmToken, AuthenticateSession authSess) + { + try + { + // Get the plaintext password and generate an NTLM1 password hash + + String username = (String) ntlmToken.getPrincipal(); + String plainPwd = (String) ntlmToken.getCredentials(); + byte[] ntlm1Pwd = m_encryptor.generateEncryptedPassword( plainPwd, authSess.getEncryptionKey(), PasswordEncryptor.NTLM1); + + // Send the logon request to the authentication server + // + // Note: Only use the stronger NTLM hash, we do not send the LM hash + + authSess.doSessionSetup(username, null, ntlm1Pwd); + + // Check if the session has logged on as a guest + + if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST")) + { + // If guest access is enabled add a guest authority to the token + + if ( allowsGuest()) + { + // Set the guest authority + + GrantedAuthority[] authorities = new GrantedAuthority[1]; + authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest); + + ntlmToken.setAuthorities(authorities); + } + else + { + // Guest access not allowed + + throw new BadCredentialsException("Guest logons disabled"); + } + } + + // Indicate that the token is authenticated + + ntlmToken.setAuthenticated(true); + } + catch (NoSuchAlgorithmException ex) + { + // JCE provider does not have the required encryption/hashing algorithms + + throw new AuthenticationServiceException("JCE provider error", ex); + } + catch (IOException ex) + { + // Error connecting to the authentication server + + throw new AuthenticationServiceException("I/O error", ex); + } + catch (SMBException ex) + { + // Check the returned status code to determine why the logon failed and throw an appropriate exception + + if ( ex.getErrorClass() == SMBStatus.NTErr) + { + AuthenticationException authEx = null; + + switch( ex.getErrorCode()) + { + case SMBStatus.NTLogonFailure: + authEx = new BadCredentialsException("Logon failure"); + break; + case SMBStatus.NTAccountDisabled: + authEx = new DisabledException("Account disabled"); + break; + default: + authEx = new BadCredentialsException("Logon failure"); + break; + } + + throw authEx; + } + else + throw new BadCredentialsException("Logon failure"); + } + } + + /** + * Authenticate using passthru authentication with a client + * + * @param ntlmToken NTLMPassthruToken + */ + private void authenticatePassthru(NTLMPassthruToken ntlmToken) + { + // Check if the token has an authentication session, if not then it is either a new token + // or the session has been timed out + + AuthenticateSession authSess = m_passthruSessions.get(ntlmToken); + + if ( authSess == null) + { + // Check if the token has a challenge, if it does then the associated session has been + // timed out + + if ( ntlmToken.getChallenge() != null) + throw new CredentialsExpiredException("Authentication session expired"); + + // Open an authentication session for the new token and add to the active session list + + authSess = m_passthruServers.openSession(); + + ntlmToken.setAuthenticationExpireTime(System.currentTimeMillis() + getSessionTimeout()); + + // Get the challenge from the initial session negotiate stage + + ntlmToken.setChallenge(new NTLMChallenge(authSess.getEncryptionKey())); + + StringBuilder details = new StringBuilder(); + + // Build a details string with the authentication session details + + details.append(authSess.getDomain()); + details.append("\\"); + details.append(authSess.getPCShare().getNodeName()); + details.append(","); + details.append(authSess.getSession().getProtocolName()); + + ntlmToken.setDetails(details.toString()); + + // Put the token/session into the active session list + + m_passthruSessions.put(ntlmToken, authSess); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Passthru stage 1 token " + ntlmToken); + } + else + { + try + { + // Stage two of the authentication, send the hashed password to the authentication server + + byte[] lmPwd = null; + byte[] ntlmPwd = null; + + if ( ntlmToken.getPasswordType() == PasswordEncryptor.LANMAN) + lmPwd = ntlmToken.getHashedPassword(); + else if ( ntlmToken.getPasswordType() == PasswordEncryptor.NTLM1) + ntlmPwd = ntlmToken.getHashedPassword(); + + String username = (String) ntlmToken.getPrincipal(); + + authSess.doSessionSetup(username, lmPwd, ntlmPwd); + + // Check if the session has logged on as a guest + + if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST")) + { + // If guest access is enabled add a guest authority to the token + + if ( allowsGuest()) + { + // Set the guest authority + + GrantedAuthority[] authorities = new GrantedAuthority[1]; + authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest); + + ntlmToken.setAuthorities(authorities); + } + else + { + // Guest access not allowed + + throw new BadCredentialsException("Guest logons disabled"); + } + } + + // Indicate that the token is authenticated + + ntlmToken.setAuthenticated(true); + } + catch (IOException ex) + { + // Error connecting to the authentication server + + throw new AuthenticationServiceException("I/O error", ex); + } + catch (SMBException ex) + { + // Check the returned status code to determine why the logon failed and throw an appropriate exception + + if ( ex.getErrorClass() == SMBStatus.NTErr) + { + AuthenticationException authEx = null; + + switch( ex.getErrorCode()) + { + case SMBStatus.NTLogonFailure: + authEx = new BadCredentialsException("Logon failure"); + break; + case SMBStatus.NTAccountDisabled: + authEx = new DisabledException("Account disabled"); + break; + default: + authEx = new BadCredentialsException("Logon failure"); + break; + } + + throw authEx; + } + else + throw new BadCredentialsException("Logon failure"); + } + finally + { + // Make sure the authentication session is closed + + if ( authSess != null) + { + try + { + // Remove the session from the active list + + m_passthruSessions.remove(ntlmToken); + + // Close the session to the authentication server + + authSess.CloseSession(); + } + catch (Exception ex) + { + } + } + } + } + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMChallenge.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMChallenge.java new file mode 100644 index 0000000000..753bcdcd94 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMChallenge.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ntlm; + +import org.alfresco.filesys.util.HexDump; + +/** + * Contains the NTLM challenge bytes. + * + * @author GKSpencer + */ +public class NTLMChallenge +{ + // Challenge bytes + + private byte[] m_challenge; + + /** + * Class constructor + * + * @param chbyts byte[] + */ + protected NTLMChallenge(byte[] chbyts) + { + m_challenge = chbyts; + } + + /** + * Return the challenge bytes + * + * @return byte[] + */ + public final byte[] getBytes() + { + return m_challenge; + } + + /** + * Check for object equality + * + * @param obj Object + * @return boolean + */ + public boolean equals(Object obj) + { + if ( obj instanceof NTLMChallenge) + { + NTLMChallenge ntlmCh = (NTLMChallenge) obj; + + // Check if both challenges are null + + if ( getBytes() == null && ntlmCh.getBytes() == null) + return true; + + // Check if both challenges are the same length + + if ( getBytes() != null && ntlmCh.getBytes() != null && + getBytes().length == ntlmCh.getBytes().length) + { + // Check if challenages are the same value + + byte[] ntlmBytes = ntlmCh.getBytes(); + + for ( int i = 0; i < m_challenge.length; i++) + if ( m_challenge[i] != ntlmBytes[i]) + return false; + } + else + return false; + } + + // Not the same type + + return false; + } + + /** + * Return the challenge as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append(HexDump.hexString(getBytes(), " ")); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMLocalToken.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMLocalToken.java new file mode 100644 index 0000000000..8af4c58193 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMLocalToken.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ntlm; + +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.providers.*; + +/** + *

Used to provide authentication with a remote Windows server when the username and password are + * provided locally. + * + * @author GKSpencer + */ +public class NTLMLocalToken extends UsernamePasswordAuthenticationToken +{ + private static final long serialVersionUID = -7946514578455279387L; + + /** + * Class constructor + */ + protected NTLMLocalToken() + { + super(null, null); + } + + /** + * Class constructor + * + * @param username String + * @param plainPwd String + */ + public NTLMLocalToken(String username, String plainPwd) { + super(username.toLowerCase(), plainPwd); + } + + /** + * Check if the user logged on as a guest + * + * @return boolean + */ + public final boolean isGuestLogon() + { + return hasAuthority(NTLMAuthenticationProvider.NTLMAuthorityGuest); + } + + /** + * Check if the user is an administrator + * + * @return boolean + */ + public final boolean isAdministrator() + { + return hasAuthority(NTLMAuthenticationProvider.NTLMAuthorityAdministrator); + } + + /** + * Search for the specified authority + * + * @param authority String + * @return boolean + */ + public final boolean hasAuthority(String authority) + { + boolean found = false; + GrantedAuthority[] authorities = getAuthorities(); + + if ( authorities != null && authorities.length > 0) + { + // Search for the specified authority + + int i = 0; + + while ( found == false && i < authorities.length) + { + if ( authorities[i++].getAuthority().equals(authority)) + found = true; + } + } + + // Return the status + + return found; + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMPassthruToken.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMPassthruToken.java new file mode 100644 index 0000000000..19c808ab3e --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NTLMPassthruToken.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ntlm; + +/** + *

Used to provide passthru authentication to a remote Windows server using multiple stages that + * allows authentication details to be passed between a client and the remote authenticating server without + * the password being known by the authentication provider. + * + * @author GKSpencer + */ +public class NTLMPassthruToken extends NTLMLocalToken +{ + private static final long serialVersionUID = -4635444888514735368L; + + // Challenge for this session + + private NTLMChallenge m_challenge; + + // User name, hashed password and algorithm type + + private String m_username; + private byte[] m_hashedPassword; + private int m_hashType; + + // Time that the authentication session will expire + + private long m_authExpiresAt; + + /** + * Class constructor + */ + public NTLMPassthruToken() + { + // We do not know the username yet, and will not know the password + + super("", ""); + } + + /** + * Return the challenge + * + * @return NTLMChallenge + */ + public final NTLMChallenge getChallenge() + { + return m_challenge; + } + + /** + * Return the user account + * + * @return Object + */ + public final Object getPrincipal() + { + return m_username; + } + + /** + * Return the hashed password + * + * @return byte[] + */ + public final byte[] getHashedPassword() + { + return m_hashedPassword; + } + + /** + * Return the hashed password type + * + * @return int + */ + public final int getPasswordType() + { + return m_hashType; + } + + /** + * Return the authentication expiry time, this will be zero if the authentication session has not yet + * been opened to the server + * + * @return long + */ + public final long getAuthenticationExpireTime() + { + return m_authExpiresAt; + } + + /** + * Set the hashed password and type + * + * @param hashedPassword byte[] + * @param hashType int + */ + public final void setUserAndPassword(String username, byte[] hashedPassword, int hashType) + { + m_username = username.toLowerCase(); + m_hashedPassword = hashedPassword; + m_hashType = hashType; + } + + /** + * Set the challenge for this token + * + * @param challenge NTLMChallenge + */ + protected final void setChallenge(NTLMChallenge challenge) + { + m_challenge = challenge; + } + + /** + * Set the authentication expire time, this indicates that an authentication session is associated with this + * token and the session will be closed if the authentication is not completed by this time. + * + * @param startTime long + */ + protected final void setAuthenticationExpireTime(long expireTime) + { + m_authExpiresAt = expireTime; + } + + /** + * Check for object equality + * + * @param obj Object + * @return boolean + */ + public boolean equals(Object obj) + { + // Only match on the same object + + return this == obj; + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java new file mode 100644 index 0000000000..f8b290fa74 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authentication.ntlm; + +import java.util.Date; + +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.providers.dao.UsernameNotFoundException; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.service.cmr.repository.NodeService; +import org.springframework.dao.DataAccessException; + +/** + * Null Mutable Authentication Dao Class + * + *

Mutable authentication implementation that does nothing. + * + * @author GKSpencer + */ +public class NullMutableAuthenticationDao implements MutableAuthenticationDao +{ + /** + * Method kept just for backward compatibility with older configurations that + * might have been passing in a value. + * + * @param nodeService ignored + */ + public void setNodeService(NodeService nodeService) + { + // do nothing + } + + /** + * Create a user with the given userName and password + * + * @param userName + * @param rawPassword + * @throws AuthenticationException + */ + public void createUser(String userName, char[] rawPassword) throws AuthenticationException + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Update a user's password. + * + * @param userName + * @param rawPassword + * @throws AuthenticationException + */ + public void updateUser(String userName, char[] rawPassword) throws AuthenticationException + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Delete a user. + * + * @param userName + * @throws AuthenticationException + */ + public void deleteUser(String userName) throws AuthenticationException + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Check is a user exists. + * + * @param userName + * @return + */ + public boolean userExists(String userName) + { + return true; + } + + /** + * Enable/disable a user. + * + * @param userName + * @param enabled + */ + public void setEnabled(String userName, boolean enabled) + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Getter for user enabled + * + * @param userName + * @return + */ + public boolean getEnabled(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return true; + } + + /** + * Set if the account should expire + * + * @param userName + * @param expires + */ + public void setAccountExpires(String userName, boolean expires) + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Does the account expire? + * + * @param userName + * @return + */ + + public boolean getAccountExpires(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return false; + } + + /** + * Has the account expired? + * + * @param userName + * @return + */ + public boolean getAccountHasExpired(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return false; + } + + /** + * Set if the password expires. + * + * @param userName + * @param expires + */ + public void setCredentialsExpire(String userName, boolean expires) + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Do the credentials for the user expire? + * + * @param userName + * @return + */ + public boolean getCredentialsExpire(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return false; + } + + /** + * Have the credentials for the user expired? + * + * @param userName + * @return + */ + public boolean getCredentialsHaveExpired(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return false; + } + + /** + * Set if the account is locked. + * + * @param userName + * @param locked + */ + public void setLocked(String userName, boolean locked) + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Is the account locked? + * + * @param userName + * @return + */ + public boolean getAccountlocked(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return false; + } + + /** + * Set the date on which the account expires + * + * @param userName + * @param exipryDate + */ + public void setAccountExpiryDate(String userName, Date exipryDate) + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Get the date when this account expires. + * + * @param userName + * @return + */ + public Date getAccountExpiryDate(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return null; + } + + /** + * Set the date when credentials expire. + * + * @param userName + * @param exipryDate + */ + public void setCredentialsExpiryDate(String userName, Date exipryDate) + { + throw new AlfrescoRuntimeException("Not implemented"); + + // Nothing to do + } + + /** + * Get the date when the credentials/password expire. + * + * @param userName + * @return + */ + public Date getCredentialsExpiryDate(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return null; + } + + /** + * Get the MD4 password hash + * + * @param userName + * @return + */ + public String getMD4HashedPassword(String userName) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return null; + } + + /** + * Are user names case sensitive? + * + * @return + */ + public boolean getUserNamesAreCaseSensitive() + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return false; + } + + /** + * Return the user details for the specified user + * + * @param user String + * @return UserDetails + * @exception UsernameNotFoundException + * @exception DataAccessException + */ + public UserDetails loadUserByUsername(String arg0) throws UsernameNotFoundException, DataAccessException + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return null; + } + + /** + * Return salt for user + * + * @param user UserDetails + * @return Object + */ + public Object getSalt(UserDetails user) + { + throw new AlfrescoRuntimeException("Not implemented"); + +// return null; + } +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java new file mode 100644 index 0000000000..be18598dd0 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authority; + +import java.util.Set; + +import org.alfresco.service.cmr.security.AuthorityType; + +public interface AuthorityDAO +{ + /** + * Add an authority to another. + * + * @param parentName + * @param childName + */ + void addAuthority(String parentName, String childName); + + /** + * Create an authority. + * + * @param parentName + * @param name + */ + void createAuthority(String parentName, String name); + + /** + * Delete an authority. + * + * @param name + */ + void deleteAuthority(String name); + + /** + * Get all root authorities. + * + * @param type + * @return + */ + Set getAllRootAuthorities(AuthorityType type); + + /** + * Get contained authorities. + * + * @param type + * @param name + * @param immediate + * @return + */ + Set getContainedAuthorities(AuthorityType type, String name, boolean immediate); + + /** + * Remove an authority. + * + * @param parentName + * @param childName + */ + void removeAuthority(String parentName, String childName); + + /** + * Get the authorities that contain the one given. + * + * @param type + * @param name + * @param immediate + * @return + */ + Set getContainingAuthorities(AuthorityType type, String name, boolean immediate); + + /** + * Get all authorities by type + * + * @param type + * @return + */ + Set getAllAuthorities(AuthorityType type); +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java new file mode 100644 index 0000000000..ae00c57878 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authority; + +import java.io.Serializable; +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.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.search.impl.lucene.QueryParser; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ISO9075; + +public class AuthorityDAOImpl implements AuthorityDAO +{ + private static final StoreRef STOREREF_USERS = new StoreRef("user", "alfrescoUserStore"); + + private NodeService nodeService; + private NamespacePrefixResolver namespacePrefixResolver; + private QName qnameAssocSystem; + private QName qnameAssocAuthorities; + private SearchService searchService; + private DictionaryService dictionaryService; + private SimpleCache> userToAuthorityCache; + + public AuthorityDAOImpl() + { + super(); + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver) + { + this.namespacePrefixResolver = namespacePrefixResolver; + qnameAssocSystem = QName.createQName("sys", "system", namespacePrefixResolver); + qnameAssocAuthorities = QName.createQName("sys", "authorities", namespacePrefixResolver); + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setUserToAuthorityCache(SimpleCache> userToAuthorityCache) + { + this.userToAuthorityCache = userToAuthorityCache; + } + + public void addAuthority(String parentName, String childName) + { + NodeRef parentRef = getAuthorityOrNull(parentName); + if (parentRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + parentName); + } + if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER)) + { + Collection memberCollection = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService + .getProperty(parentRef, ContentModel.PROP_MEMBERS)); + HashSet members = new HashSet(); + members.addAll(memberCollection); + members.add(childName); + nodeService.setProperty(parentRef, ContentModel.PROP_MEMBERS, members); + userToAuthorityCache.remove(childName); + } + else + { + NodeRef childRef = getAuthorityOrNull(childName); + if (childRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + childName); + } + nodeService.addChild( + parentRef, + childRef, + ContentModel.ASSOC_MEMBER, + QName.createQName("usr", childName, namespacePrefixResolver)); + } + + } + + public void createAuthority(String parentName, String name) + { + HashMap props = new HashMap(); + props.put(ContentModel.PROP_AUTHORITY_NAME, name); + if (parentName != null) + { + NodeRef parentRef = getAuthorityOrNull(parentName); + if (parentRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + parentName); + } + nodeService.createNode( + parentRef, + ContentModel.ASSOC_MEMBER, + QName.createQName("usr", name, namespacePrefixResolver), + ContentModel.TYPE_AUTHORITY_CONTAINER, + props); + } + else + { + NodeRef authorityContainerRef = getAuthorityContainer(); + nodeService.createNode( + authorityContainerRef, + ContentModel.ASSOC_MEMBER, + QName.createQName("usr", name, namespacePrefixResolver), + ContentModel.TYPE_AUTHORITY_CONTAINER, + props); + } + } + + public void deleteAuthority(String name) + { + NodeRef nodeRef = getAuthorityOrNull(name); + if (nodeRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + name); + } + nodeService.deleteNode(nodeRef); + + } + + public Set getAllRootAuthorities(AuthorityType type) + { + HashSet authorities = new HashSet(); + NodeRef container = getAuthorityContainer(); + if (container != null) + { + findAuthorities(type, container, authorities, false, false, false); + } + return authorities; + } + + public Set getAllAuthorities(AuthorityType type) + { + HashSet authorities = new HashSet(); + NodeRef container = getAuthorityContainer(); + if (container != null) + { + findAuthorities(type, container, authorities, false, true, false); + } + return authorities; + } + + public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate) + { + if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER)) + { + return Collections. emptySet(); + } + else + { + NodeRef nodeRef = getAuthorityOrNull(name); + if (nodeRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + name); + } + HashSet authorities = new HashSet(); + findAuthorities(type, nodeRef, authorities, false, !immediate, false); + return authorities; + } + } + + public void removeAuthority(String parentName, String childName) + { + NodeRef parentRef = getAuthorityOrNull(parentName); + if (parentRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + parentName); + } + if (AuthorityType.getAuthorityType(childName).equals(AuthorityType.USER)) + { + Collection memberCollection = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService + .getProperty(parentRef, ContentModel.PROP_MEMBERS)); + HashSet members = new HashSet(); + members.addAll(memberCollection); + members.remove(childName); + nodeService.setProperty(parentRef, ContentModel.PROP_MEMBERS, members); + userToAuthorityCache.remove(childName); + } + else + { + NodeRef childRef = getAuthorityOrNull(childName); + if (childRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + childName); + } + nodeService.removeChild(parentRef, childRef); + } + + } + + public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) + { + HashSet authorities = new HashSet(); + findAuthorities(type, name, authorities, true, !immediate); + return authorities; + } + + private void findAuthorities(AuthorityType type, String name, Set authorities, boolean parents, + boolean recursive) + { + if (AuthorityType.getAuthorityType(name).equals(AuthorityType.GUEST)) + { + // Nothing to do + } + else if (AuthorityType.getAuthorityType(name).equals(AuthorityType.USER)) + { + for (NodeRef ref : getUserContainers(name)) + { + findAuthorities(type, ref, authorities, parents, recursive, true); + } + + } + else + { + NodeRef ref = getAuthorityOrNull(name); + + if (ref == null) + { + throw new UnknownAuthorityException("An authority was not found for " + name); + } + + findAuthorities(type, ref, authorities, parents, recursive, false); + + } + } + + private ArrayList getUserContainers(String name) + { + ArrayList containers = userToAuthorityCache.get(name); + if (containers == null) + { + containers = findUserContainers(name); + userToAuthorityCache.put(name, containers); + } + return containers; + } + + private ArrayList findUserContainers(String name) + { + SearchParameters sp = new SearchParameters(); + sp.addStore(STOREREF_USERS); + sp.setLanguage("lucene"); + sp.setQuery("+TYPE:\"" + + ContentModel.TYPE_AUTHORITY_CONTAINER + + "\"" + + " +@" + + QueryParser.escape("{" + + ContentModel.PROP_MEMBERS.getNamespaceURI() + "}" + + ISO9075.encode(ContentModel.PROP_MEMBERS.getLocalName())) + ":\"" + name + "\""); + ResultSet rs = null; + try + { + rs = searchService.query(sp); + ArrayList answer = new ArrayList(rs.length()); + for (ResultSetRow row : rs) + { + answer.add(row.getNodeRef()); + } + return answer; + } + finally + { + if (rs != null) + { + rs.close(); + } + } + + } + + private void findAuthorities(AuthorityType type, NodeRef nodeRef, Set authorities, boolean parents, + boolean recursive, boolean includeNode) + { + List cars = parents ? nodeService.getParentAssocs(nodeRef) : nodeService + .getChildAssocs(nodeRef); + + if (includeNode) + { + String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, + ContentModel.PROP_AUTHORITY_NAME)); + if (type == null) + { + authorities.add(authorityName); + } + else + { + AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName); + if (authorityType.equals(type)) + { + authorities.add(authorityName); + } + } + } + + // Loop over children + for (ChildAssociationRef car : cars) + { + NodeRef current = parents ? car.getParentRef() : car.getChildRef(); + QName currentType = nodeService.getType(current); + if (dictionaryService.isSubClass(currentType, ContentModel.TYPE_AUTHORITY)) + { + + String authorityName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty( + current, ContentModel.PROP_AUTHORITY_NAME)); + + if (type == null) + { + authorities.add(authorityName); + if (recursive) + { + findAuthorities(type, current, authorities, parents, recursive, false); + } + } + else + { + AuthorityType authorityType = AuthorityType.getAuthorityType(authorityName); + if (authorityType.equals(type)) + { + authorities.add(authorityName); + } + if (recursive) + { + findAuthorities(type, current, authorities, parents, recursive, false); + } + } + } + } + // loop over properties + if (!parents) + { + Collection members = DefaultTypeConverter.INSTANCE.getCollection(String.class, nodeService + .getProperty(nodeRef, ContentModel.PROP_MEMBERS)); + if (members != null) + { + for (String user : members) + { + if (user != null) + { + if (type == null) + { + authorities.add(user); + } + else + { + AuthorityType authorityType = AuthorityType.getAuthorityType(user); + if (authorityType.equals(type)) + { + authorities.add(user); + } + } + } + } + } + } + } + + private NodeRef getAuthorityOrNull(String name) + { + SearchParameters sp = new SearchParameters(); + sp.addStore(STOREREF_USERS); + sp.setLanguage("lucene"); + sp.setQuery("+TYPE:\"" + + ContentModel.TYPE_AUTHORITY_CONTAINER + + "\"" + + " +@" + + QueryParser.escape("{" + + ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}" + + ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName())) + ":\"" + name + "\""); + ResultSet rs = null; + try + { + rs = searchService.query(sp); + if (rs.length() == 0) + { + return null; + } + else + { + for (ResultSetRow row : rs) + { + String test = DefaultTypeConverter.INSTANCE.convert( + String.class, + nodeService.getProperty(row.getNodeRef(), ContentModel.PROP_AUTHORITY_NAME)); + if (test.equals(name)) + { + return row.getNodeRef(); + } + } + } + return null; + } + finally + { + if (rs != null) + { + rs.close(); + } + } + + } + + /** + * @return Returns the authority container, which must exist + */ + private NodeRef getAuthorityContainer() + { + NodeRef rootNodeRef = nodeService.getRootNode(STOREREF_USERS); + List results = nodeService.getChildAssocs( + rootNodeRef, + RegexQNamePattern.MATCH_ALL, + qnameAssocSystem); + NodeRef sysNodeRef = null; + if (results.size() == 0) + { + throw new AlfrescoRuntimeException("Required authority system path not found: " + qnameAssocSystem); + } + else + { + sysNodeRef = results.get(0).getChildRef(); + } + results = nodeService.getChildAssocs( + sysNodeRef, + RegexQNamePattern.MATCH_ALL, + qnameAssocAuthorities); + NodeRef authNodeRef = null; + if (results.size() == 0) + { + throw new AlfrescoRuntimeException("Required authority path not found: " + qnameAssocAuthorities); + } + else + { + authNodeRef = results.get(0).getChildRef(); + } + return authNodeRef; + } +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityException.java b/source/java/org/alfresco/repo/security/authority/AuthorityException.java new file mode 100644 index 0000000000..614a2e1674 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityException.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authority; + +import org.alfresco.error.AlfrescoRuntimeException; + +public class AuthorityException extends AlfrescoRuntimeException +{ + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = -5367993045129604445L; + + public AuthorityException(String msgId) + { + super(msgId); + } + + public AuthorityException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + public AuthorityException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public AuthorityException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java new file mode 100644 index 0000000000..3186d263e9 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authority; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; + +/** + * The default implementation of the authority service. + * + * @author Andy Hind + */ +public class AuthorityServiceImpl implements AuthorityService +{ + private PersonService personService; + + private NodeService nodeService; + + private AuthorityDAO authorityDAO; + + private PermissionServiceSPI permissionServiceSPI; + + private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY); + + private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY); + + private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES); + + private Set adminUsers; + + private AuthenticationComponent authenticationComponent; + + public AuthorityServiceImpl() + { + super(); + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setAuthorityDAO(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) + { + this.permissionServiceSPI = permissionServiceSPI; + } + + /** + * Currently the admin authority is granted only to the ALFRESCO_ADMIN_USER + * user. + */ + public boolean hasAdminAuthority() + { + String currentUserName = authenticationComponent.getCurrentUserName(); + return ((currentUserName != null) && adminUsers.contains(currentUserName)); + } + + // IOC + + public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + public void setAdminUsers(Set adminUsers) + { + this.adminUsers = adminUsers; + } + + public Set getAuthorities() + { + Set authorities = new HashSet(); + String currentUserName = authenticationComponent.getCurrentUserName(); + if (adminUsers.contains(currentUserName)) + { + authorities.addAll(adminSet); + } + if(AuthorityType.getAuthorityType(currentUserName) != AuthorityType.GUEST) + { + authorities.addAll(allSet); + } + authorities.addAll(getContainingAuthorities(null, currentUserName, false)); + return authorities; + } + + public Set getAllAuthorities(AuthorityType type) + { + Set authorities = new HashSet(); + switch (type) + { + case ADMIN: + authorities.addAll(adminSet); + break; + case EVERYONE: + authorities.addAll(allSet); + break; + case GUEST: + authorities.addAll(guestSet); + break; + case GROUP: + authorities.addAll(authorityDAO.getAllAuthorities(type)); + break; + case OWNER: + break; + case ROLE: + authorities.addAll(authorityDAO.getAllAuthorities(type)); + break; + case USER: + for (NodeRef personRef : personService.getAllPeople()) + { + authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef, + ContentModel.PROP_USERNAME))); + } + break; + default: + break; + } + return authorities; + } + + public void addAuthority(String parentName, String childName) + { + authorityDAO.addAuthority(parentName, childName); + } + + private void checkTypeIsMutable(AuthorityType type) + { + if((type == AuthorityType.GROUP) || (type == AuthorityType.ROLE)) + { + return; + } + else + { + throw new AuthorityException("Trying to modify a fixed authority"); + } + } + + public String createAuthority(AuthorityType type, String parentName, String shortName) + { + checkTypeIsMutable(type); + String name = getName(type, shortName); + authorityDAO.createAuthority(parentName, name); + return name; + } + + public void deleteAuthority(String name) + { + AuthorityType type = AuthorityType.getAuthorityType(name); + checkTypeIsMutable(type); + authorityDAO.deleteAuthority(name); + permissionServiceSPI.deletePermissions(name); + } + + public Set getAllRootAuthorities(AuthorityType type) + { + return authorityDAO.getAllRootAuthorities(type); + } + + public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate) + { + return authorityDAO.getContainedAuthorities(type, name, immediate); + } + + public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) + { + return authorityDAO.getContainingAuthorities(type, name, immediate); + } + + public String getName(AuthorityType type, String shortName) + { + if (type.isFixedString()) + { + return type.getFixedString(); + } + else if (type.isPrefixed()) + { + return type.getPrefixString() + shortName; + } + else + { + return shortName; + } + } + + public String getShortName(String name) + { + AuthorityType type = AuthorityType.getAuthorityType(name); + if (type.isFixedString()) + { + return ""; + } + else if (type.isPrefixed()) + { + return name.substring(type.getPrefixString().length()); + } + else + { + return name; + } + + } + + public void removeAuthority(String parentName, String childName) + { + authorityDAO.removeAuthority(parentName, childName); + } + +} diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java new file mode 100644 index 0000000000..6516f4c190 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authority; + +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +public class AuthorityServiceTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private AuthenticationComponent authenticationComponent; + + private AuthenticationComponent authenticationComponentImpl; + + private AuthenticationService authenticationService; + + private MutableAuthenticationDao authenticationDAO; + + private AuthorityService authorityService; + + private AuthorityService pubAuthorityService; + + private PersonService personService; + + private UserTransaction tx; + + public AuthorityServiceTest() + { + super(); + + } + + public void setUp() throws Exception + { + authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); + authenticationComponentImpl = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl"); + authenticationService = (AuthenticationService) ctx.getBean("authenticationService"); + authorityService = (AuthorityService) ctx.getBean("authorityService"); + pubAuthorityService = (AuthorityService) ctx.getBean("AuthorityService"); + personService = (PersonService) ctx.getBean("personService"); + authenticationDAO = (MutableAuthenticationDao) ctx.getBean("authenticationDao"); + + authenticationComponentImpl.setSystemUserAsCurrentUser(); + + TransactionService transactionService = (TransactionService) ctx.getBean(ServiceRegistry.TRANSACTION_SERVICE + .getLocalName()); + tx = transactionService.getUserTransaction(); + tx.begin(); + + if (!authenticationDAO.userExists("andy")) + { + authenticationService.createAuthentication("andy", "andy".toCharArray()); + } + + if (!authenticationDAO.userExists("admin")) + { + authenticationService.createAuthentication("admin", "admin".toCharArray()); + } + + if (!authenticationDAO.userExists("administrator")) + { + authenticationService.createAuthentication("administrator", "administrator".toCharArray()); + } + + } + + @Override + protected void tearDown() throws Exception + { + authenticationComponentImpl.clearCurrentSecurityContext(); + tx.rollback(); + super.tearDown(); + } + + public void testNonAdminUser() + { + authenticationComponent.setCurrentUser("andy"); + assertFalse(authorityService.hasAdminAuthority()); + assertFalse(pubAuthorityService.hasAdminAuthority()); + assertEquals(1, authorityService.getAuthorities().size()); + } + + public void testAdminUser() + { + authenticationComponent.setCurrentUser("admin"); + assertTrue(authorityService.hasAdminAuthority()); + assertTrue(pubAuthorityService.hasAdminAuthority()); + assertEquals(2, authorityService.getAuthorities().size()); + + authenticationComponent.setCurrentUser("administrator"); + assertTrue(authorityService.hasAdminAuthority()); + assertTrue(pubAuthorityService.hasAdminAuthority()); + assertEquals(2, authorityService.getAuthorities().size()); + } + + public void testAuthorities() + { + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ADMIN).size()); + assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.ADMIN).contains( + PermissionService.ADMINISTRATOR_AUTHORITY)); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.EVERYONE).size()); + assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.EVERYONE).contains( + PermissionService.ALL_AUTHORITIES)); + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertFalse(pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).contains( + PermissionService.ALL_AUTHORITIES)); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GUEST).size()); + assertTrue(pubAuthorityService.getAllAuthorities(AuthorityType.GUEST).contains(PermissionService.GUEST_AUTHORITY)); + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.OWNER).size()); + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(personService.getAllPeople().size(), pubAuthorityService.getAllAuthorities(AuthorityType.USER) + .size()); + + } + + public void testCreateAdminAuth() + { + try + { + pubAuthorityService.createAuthority(AuthorityType.ADMIN, null, "woof"); + fail("Should not be able to create an admin authority"); + } + catch (AuthorityException ae) + { + + } + } + + public void testCreateEveryoneAuth() + { + try + { + pubAuthorityService.createAuthority(AuthorityType.EVERYONE, null, "woof"); + fail("Should not be able to create an everyone authority"); + } + catch (AuthorityException ae) + { + + } + } + + public void testCreateGuestAuth() + { + try + { + pubAuthorityService.createAuthority(AuthorityType.GUEST, null, "woof"); + fail("Should not be able to create an guest authority"); + } + catch (AuthorityException ae) + { + + } + } + + public void testCreateOwnerAuth() + { + try + { + pubAuthorityService.createAuthority(AuthorityType.OWNER, null, "woof"); + fail("Should not be able to create an owner authority"); + } + catch (AuthorityException ae) + { + + } + } + + public void testCreateUserAuth() + { + try + { + pubAuthorityService.createAuthority(AuthorityType.USER, null, "woof"); + fail("Should not be able to create an user authority"); + } + catch (AuthorityException ae) + { + + } + } + + public void testCreateRootAuth() + { + String auth; + + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "woof"); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + pubAuthorityService.deleteAuthority(auth); + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + auth = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "woof"); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + pubAuthorityService.deleteAuthority(auth); + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + } + + public void testCreateAuth() + { + String auth1; + String auth2; + String auth3; + String auth4; + String auth5; + + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one"); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two"); + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three"); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four"); + assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five"); + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + + pubAuthorityService.deleteAuthority(auth5); + assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + pubAuthorityService.deleteAuthority(auth4); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + pubAuthorityService.deleteAuthority(auth3); + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + pubAuthorityService.deleteAuthority(auth2); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + pubAuthorityService.deleteAuthority(auth1); + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + auth1 = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "one"); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + auth2 = pubAuthorityService.createAuthority(AuthorityType.ROLE, null, "two"); + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + auth3 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth1, "three"); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + auth4 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth1, "four"); + assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + auth5 = pubAuthorityService.createAuthority(AuthorityType.ROLE, auth2, "five"); + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + + pubAuthorityService.deleteAuthority(auth5); + assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + pubAuthorityService.deleteAuthority(auth4); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + pubAuthorityService.deleteAuthority(auth3); + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + pubAuthorityService.deleteAuthority(auth2); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + pubAuthorityService.deleteAuthority(auth1); + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.ROLE).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.ROLE).size()); + } + + public void testCreateAuthTree() + { + String auth1; + String auth2; + String auth3; + String auth4; + String auth5; + + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one"); + assertEquals("GROUP_one", auth1); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two"); + assertEquals("GROUP_two", auth2); + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three"); + assertEquals("GROUP_three", auth3); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four"); + assertEquals("GROUP_four", auth4); + assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five"); + assertEquals("GROUP_five", auth5); + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + pubAuthorityService.addAuthority(auth5, "andy"); + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + // The next call looks for people not users :-) + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(2, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); + assertEquals(1, pubAuthorityService.getContainingAuthorities(null, auth5, false).size()); + assertTrue(pubAuthorityService.getContainingAuthorities(null, auth5, false).contains(auth2)); + + assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy")); + + assertEquals(1, pubAuthorityService.getContainedAuthorities(null, auth5, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth5, false).contains("andy")); + + pubAuthorityService.removeAuthority(auth5, "andy"); + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + // The next call looks for people not users :-) + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(0, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); + assertEquals(1, pubAuthorityService.getContainingAuthorities(null, auth5, false).size()); + assertTrue(pubAuthorityService.getContainingAuthorities(null, auth5, false).contains(auth2)); + + assertEquals(1, pubAuthorityService.getContainedAuthorities(null, auth2, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5)); + + assertEquals(0, pubAuthorityService.getContainedAuthorities(null, auth5, false).size()); + } + + public void testCreateAuthNet() + { + String auth1; + String auth2; + String auth3; + String auth4; + String auth5; + + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one"); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two"); + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three"); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four"); + assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five"); + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + pubAuthorityService.addAuthority(auth5, "andy"); + pubAuthorityService.addAuthority(auth1, "andy"); + + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + // The next call looks for people not users :-) + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth1)); + + assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy")); + assertEquals(3, pubAuthorityService.getContainedAuthorities(null, auth1, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth3)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth4)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains("andy")); + + pubAuthorityService.removeAuthority(auth1, "andy"); + + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + // The next call looks for people not users :-) + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(2, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); + + assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy")); + assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth1, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth3)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth4)); + } + + public void testCreateAuthNet2() + { + String auth1; + String auth2; + String auth3; + String auth4; + String auth5; + + assertEquals(0, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(0, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth1 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "one"); + assertEquals(1, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(1, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth2 = pubAuthorityService.createAuthority(AuthorityType.GROUP, null, "two"); + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth3 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "three"); + assertEquals(3, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth4 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth1, "four"); + assertEquals(4, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + auth5 = pubAuthorityService.createAuthority(AuthorityType.GROUP, auth2, "five"); + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + pubAuthorityService.addAuthority(auth5, "andy"); + pubAuthorityService.addAuthority(auth1, "andy"); + + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + // The next call looks for people not users :-) + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(3, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth1)); + + assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy")); + assertEquals(3, pubAuthorityService.getContainedAuthorities(null, auth1, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth3)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth4)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains("andy")); + + + pubAuthorityService.addAuthority(auth3, auth2); + + assertEquals(5, pubAuthorityService.getAllAuthorities(AuthorityType.GROUP).size()); + assertEquals(2, pubAuthorityService.getAllRootAuthorities(AuthorityType.GROUP).size()); + // The next call looks for people not users :-) + assertEquals(2, pubAuthorityService.getAllAuthorities(AuthorityType.USER).size()); + assertEquals(4, pubAuthorityService.getContainingAuthorities(null, "andy", false).size()); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth5)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth2)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth1)); + assertTrue(pubAuthorityService.getContainingAuthorities(null, "andy", false).contains(auth3)); + + assertEquals(2, pubAuthorityService.getContainedAuthorities(null, auth2, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains(auth5)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth2, false).contains("andy")); + assertEquals(5, pubAuthorityService.getContainedAuthorities(null, auth1, false).size()); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth3)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth4)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth2)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains(auth5)); + assertTrue(pubAuthorityService.getContainedAuthorities(null, auth1, false).contains("andy")); + + } +} diff --git a/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java b/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java new file mode 100644 index 0000000000..fde16b88a4 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authority; + +import org.alfresco.repo.security.permissions.impl.AbstractPermissionTest; +import org.alfresco.repo.security.permissions.impl.SimplePermissionEntry; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; + +public class ExtendedPermissionServiceTest extends AbstractPermissionTest +{ + public void testGroupPermission() + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "GROUP_test", AccessStatus.ALLOWED)); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + authorityService.createAuthority(AuthorityType.GROUP, null, "test"); + authorityService.addAuthority("GROUP_test", "andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + authorityService.removeAuthority("GROUP_test", "andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + permissionService.clearPermission(rootNodeRef, "andy"); + } + + public void testDeletePermissionByRecipient() + { + runAs("andy"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + permissionService.setPermission(new SimplePermissionEntry(rootNodeRef, getPermission(PermissionService.READ), + "GROUP_test", AccessStatus.ALLOWED)); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + authorityService.createAuthority(AuthorityType.GROUP, null, "test"); + authorityService.addAuthority("GROUP_test", "andy"); + assertTrue(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + permissionService.deletePermissions("GROUP_test"); + assertFalse(permissionService.hasPermission(rootNodeRef, getPermission(PermissionService.READ)) == AccessStatus.ALLOWED); + } +} diff --git a/source/java/org/alfresco/repo/security/authority/UnknownAuthorityException.java b/source/java/org/alfresco/repo/security/authority/UnknownAuthorityException.java new file mode 100644 index 0000000000..523a77e929 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authority/UnknownAuthorityException.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.security.authority; + +public class UnknownAuthorityException extends AuthorityException +{ + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 4639634037108317201L; + + public UnknownAuthorityException(String msgId) + { + super(msgId); + } + + public UnknownAuthorityException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + public UnknownAuthorityException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public UnknownAuthorityException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + +} diff --git a/source/java/org/alfresco/repo/transaction/TransactionManagerJndiLookup.java b/source/java/org/alfresco/repo/transaction/TransactionManagerJndiLookup.java new file mode 100644 index 0000000000..3d3ca0c5e0 --- /dev/null +++ b/source/java/org/alfresco/repo/transaction/TransactionManagerJndiLookup.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.repo.transaction; + +import java.util.Properties; + +import javax.transaction.TransactionManager; + +import org.jboss.cache.TransactionManagerLookup; +import org.springframework.jndi.JndiObjectFactoryBean; +import org.springframework.jndi.JndiTemplate; + +/** + * Helper lookup class to supply JBoss components with a TransactionManager. + *

+ * The JBossTransactionManagerLookup will work when Alfresco is running in JBoss, + * but the TreeCache can be used within other containers; there might not be any + * container and the TransactionManager may held in a local JNDI tree. + *

+ * For compatibility with other app servers, the JBoss GenericTransactionManagerLookup + * could also be used. + *

+ * The default constructor configures the object to look in java:/TransactionManager + * for a TransactionManager. The only customisation that should be required is + * to change the {@link #setJndiName(String) jndiName} property. If more JNDI details need + * changing, then the actual {@link #setJndiLookup(JndiObjectFactoryBean) jndiLookup object} can + * be substituted with a customized version. + * + * @author Derek Hulley + */ +public class TransactionManagerJndiLookup implements TransactionManagerLookup +{ + public static final String DEFAULT_JNDI_NAME = "java:/TransactionManager"; + + private JndiObjectFactoryBean jndiLookup; + + public TransactionManagerJndiLookup() + { + jndiLookup = new JndiObjectFactoryBean(); + jndiLookup.setJndiName(DEFAULT_JNDI_NAME); + jndiLookup.setProxyInterface(TransactionManager.class); + } + + /** + * @see org.springframework.jndi.JndiAccessor#setJndiTemplate(org.springframework.jndi.JndiTemplate) + */ + public void setJndiTemplate(JndiTemplate jndiTemplate) + { + this.jndiLookup.setJndiTemplate(jndiTemplate); + } + + /** + * @see org.springframework.jndi.JndiAccessor#setJndiEnvironment(java.util.Properties) + */ + public void setJndiEnvironment(Properties jndiEnvironment) + { + this.jndiLookup.setJndiEnvironment(jndiEnvironment); + } + + /** + * Set the JNDI location where the TransactionManager can be found. + * + * @param jndiName + */ + public void setJndiName(String jndiName) + { + jndiLookup.setJndiName(jndiName); + } + + /** + * @return Returns a TransactionManager looked up at the JNDI location + */ + public TransactionManager getTransactionManager() throws Exception + { + return (TransactionManager) jndiLookup.getObject(); + } +}