From c37ff8805c16f3137392ab20b2f1da6944a62cba Mon Sep 17 00:00:00 2001 From: Paul Holmes-Higgin Date: Wed, 3 May 2006 18:34:13 +0000 Subject: [PATCH] Merged enterprise features git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2746 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/authority-services-context.xml | 38 +- config/alfresco/desktop/Alfresco.exe | Bin 0 -> 81920 bytes ...chaining-authentication-context.xml.sample | 93 ++ .../jaas-authentication-context.xml.sample | 38 + .../ldap-authentication-context.xml.sample | 442 +++++++++ .../ntlm-authentication-context.xml.sample | 35 + ...cating-content-services-context.xml.sample | 71 ++ config/alfresco/index-recovery-context.xml | 34 +- config/alfresco/version.properties | 2 +- source/cpp/CAlfrescoApp/CAlfrescoApp.aps | Bin 0 -> 38300 bytes source/cpp/CAlfrescoApp/CAlfrescoApp.cpp | 513 ++++++++++ source/cpp/CAlfrescoApp/CAlfrescoApp.h | 65 ++ source/cpp/CAlfrescoApp/CAlfrescoApp.htm | 19 + source/cpp/CAlfrescoApp/CAlfrescoApp.ncb | Bin 0 -> 232448 bytes source/cpp/CAlfrescoApp/CAlfrescoApp.rc | 256 +++++ source/cpp/CAlfrescoApp/CAlfrescoApp.sln | 21 + source/cpp/CAlfrescoApp/CAlfrescoApp.suo | Bin 0 -> 13312 bytes source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj | 294 ++++++ source/cpp/CAlfrescoApp/CAlfrescoAppDlg.cpp | 164 +++ source/cpp/CAlfrescoApp/CAlfrescoAppDlg.h | 51 + source/cpp/CAlfrescoApp/FileStatusDialog.cpp | 118 +++ source/cpp/CAlfrescoApp/FileStatusDialog.h | 57 ++ source/cpp/CAlfrescoApp/FileStatusView.cpp | 40 + source/cpp/CAlfrescoApp/FileStatusView.h | 24 + source/cpp/CAlfrescoApp/ReadMe.txt | 90 ++ source/cpp/CAlfrescoApp/alfresco.ico | Bin 0 -> 894 bytes .../includes/alfresco/Alfresco.hpp | 302 ++++++ .../CAlfrescoApp/includes/util/ByteArray.h | 109 ++ .../CAlfrescoApp/includes/util/DataBuffer.h | 157 +++ .../CAlfrescoApp/includes/util/DataPacker.h | 93 ++ .../CAlfrescoApp/includes/util/Exception.h | 130 +++ .../cpp/CAlfrescoApp/includes/util/FileName.h | 100 ++ .../cpp/CAlfrescoApp/includes/util/Integer.h | 67 ++ .../CAlfrescoApp/includes/util/JavaTypes.h | 32 + source/cpp/CAlfrescoApp/includes/util/Long.h | 84 ++ .../cpp/CAlfrescoApp/includes/util/String.h | 268 +++++ .../cpp/CAlfrescoApp/includes/util/System.h | 55 ++ source/cpp/CAlfrescoApp/includes/util/Types.h | 57 ++ source/cpp/CAlfrescoApp/res/CAlfrescoApp.ico | Bin 0 -> 21630 bytes .../CAlfrescoApp/res/CAlfrescoApp.manifest | 22 + source/cpp/CAlfrescoApp/res/CAlfrescoApp.rc2 | 13 + source/cpp/CAlfrescoApp/resource.h | 25 + .../cpp/CAlfrescoApp/source/AlfrescoApp.cpp | 420 ++++++++ .../CAlfrescoApp/source/alfresco/Alfresco.cpp | 448 +++++++++ .../CAlfrescoApp/source/util/ByteArray.cpp | 232 +++++ .../CAlfrescoApp/source/util/DataBuffer.cpp | 732 ++++++++++++++ .../CAlfrescoApp/source/util/DataPacker.cpp | 436 ++++++++ .../CAlfrescoApp/source/util/Exception.cpp | 137 +++ .../cpp/CAlfrescoApp/source/util/FileName.cpp | 390 ++++++++ .../cpp/CAlfrescoApp/source/util/Integer.cpp | 76 ++ source/cpp/CAlfrescoApp/source/util/Long.cpp | 96 ++ .../cpp/CAlfrescoApp/source/util/String.cpp | 889 +++++++++++++++++ .../cpp/CAlfrescoApp/source/util/System.cpp | 47 + source/cpp/CAlfrescoApp/stdafx.cpp | 7 + source/cpp/CAlfrescoApp/stdafx.h | 44 + .../alfresco/repo/cache/TreeCacheAdapter.java | 140 +++ .../repo/cache/TreeCacheAdapterTest.java | 75 ++ .../replication/ContentStoreReplicator.java | 277 ++++++ .../ContentStoreReplicatorTest.java | 160 +++ .../replication/ReplicatingContentStore.java | 447 +++++++++ .../ReplicatingContentStoreTest.java | 201 ++++ .../index/FullIndexRecoveryComponent.java | 769 +++++++++++++++ .../index/FullIndexRecoveryComponentTest.java | 166 ++++ .../jaas/JAASAuthenticationComponent.java | 209 ++++ .../ldap/LDAPAuthenticationComponentImpl.java | 125 +++ .../ldap/LDAPGroupExportSource.java | 695 +++++++++++++ .../ldap/LDAPInitialDirContextFactory.java | 65 ++ .../LDAPInitialDirContextFactoryImpl.java | 197 ++++ .../ldap/LDAPPersonExportSource.java | 328 ++++++ .../ntlm/NTLMAuthenticationComponentImpl.java | 932 ++++++++++++++++++ .../ntlm/NTLMAuthenticationProvider.java | 755 ++++++++++++++ .../authentication/ntlm/NTLMChallenge.java | 113 +++ .../authentication/ntlm/NTLMLocalToken.java | 105 ++ .../ntlm/NTLMPassthruToken.java | 158 +++ .../ntlm/NullMutableAuthenticationDao.java | 344 +++++++ .../repo/security/authority/AuthorityDAO.java | 98 ++ .../security/authority/AuthorityDAOImpl.java | 488 +++++++++ .../authority/AuthorityException.java | 56 ++ .../authority/AuthorityServiceImpl.java | 251 +++++ .../authority/AuthorityServiceTest.java | 517 ++++++++++ .../ExtendedPermissionServiceTest.java | 62 ++ .../authority/UnknownAuthorityException.java | 54 + .../TransactionManagerJndiLookup.java | 98 ++ 83 files changed, 15809 insertions(+), 9 deletions(-) create mode 100644 config/alfresco/desktop/Alfresco.exe create mode 100644 config/alfresco/extension/chaining-authentication-context.xml.sample create mode 100644 config/alfresco/extension/jaas-authentication-context.xml.sample create mode 100644 config/alfresco/extension/ldap-authentication-context.xml.sample create mode 100644 config/alfresco/extension/ntlm-authentication-context.xml.sample create mode 100644 config/alfresco/extension/replicating-content-services-context.xml.sample create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.aps create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.cpp create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.h create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.htm create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.ncb create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.rc create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.sln create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.suo create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoApp.vcproj create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoAppDlg.cpp create mode 100644 source/cpp/CAlfrescoApp/CAlfrescoAppDlg.h create mode 100644 source/cpp/CAlfrescoApp/FileStatusDialog.cpp create mode 100644 source/cpp/CAlfrescoApp/FileStatusDialog.h create mode 100644 source/cpp/CAlfrescoApp/FileStatusView.cpp create mode 100644 source/cpp/CAlfrescoApp/FileStatusView.h create mode 100644 source/cpp/CAlfrescoApp/ReadMe.txt create mode 100644 source/cpp/CAlfrescoApp/alfresco.ico create mode 100644 source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp create mode 100644 source/cpp/CAlfrescoApp/includes/util/ByteArray.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/DataBuffer.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/DataPacker.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/Exception.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/FileName.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/Integer.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/JavaTypes.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/Long.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/String.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/System.h create mode 100644 source/cpp/CAlfrescoApp/includes/util/Types.h create mode 100644 source/cpp/CAlfrescoApp/res/CAlfrescoApp.ico create mode 100644 source/cpp/CAlfrescoApp/res/CAlfrescoApp.manifest create mode 100644 source/cpp/CAlfrescoApp/res/CAlfrescoApp.rc2 create mode 100644 source/cpp/CAlfrescoApp/resource.h create mode 100644 source/cpp/CAlfrescoApp/source/AlfrescoApp.cpp create mode 100644 source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/ByteArray.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/DataBuffer.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/DataPacker.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/Exception.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/FileName.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/Integer.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/Long.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/String.cpp create mode 100644 source/cpp/CAlfrescoApp/source/util/System.cpp create mode 100644 source/cpp/CAlfrescoApp/stdafx.cpp create mode 100644 source/cpp/CAlfrescoApp/stdafx.h create mode 100644 source/java/org/alfresco/repo/cache/TreeCacheAdapter.java create mode 100644 source/java/org/alfresco/repo/cache/TreeCacheAdapterTest.java create mode 100644 source/java/org/alfresco/repo/content/replication/ContentStoreReplicator.java create mode 100644 source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java create mode 100644 source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java create mode 100644 source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java create mode 100644 source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java create mode 100644 source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponentTest.java create mode 100644 source/java/org/alfresco/repo/security/authentication/jaas/JAASAuthenticationComponent.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ldap/LDAPAuthenticationComponentImpl.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ldap/LDAPGroupExportSource.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactory.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ldap/LDAPInitialDirContextFactoryImpl.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ldap/LDAPPersonExportSource.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationComponentImpl.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ntlm/NTLMAuthenticationProvider.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ntlm/NTLMChallenge.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ntlm/NTLMLocalToken.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ntlm/NTLMPassthruToken.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ntlm/NullMutableAuthenticationDao.java create mode 100644 source/java/org/alfresco/repo/security/authority/AuthorityDAO.java create mode 100644 source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java create mode 100644 source/java/org/alfresco/repo/security/authority/AuthorityException.java create mode 100644 source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java create mode 100644 source/java/org/alfresco/repo/security/authority/AuthorityServiceTest.java create mode 100644 source/java/org/alfresco/repo/security/authority/ExtendedPermissionServiceTest.java create mode 100644 source/java/org/alfresco/repo/security/authority/UnknownAuthorityException.java create mode 100644 source/java/org/alfresco/repo/transaction/TransactionManagerJndiLookup.java 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 0000000000000000000000000000000000000000..1afb08137368cd3f8a27624da06de060863765e5 GIT binary patch literal 81920 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 G9{3;EhhG~2 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8e5c213a85ced0ee71f1f9b9e833810cd6c4d8d2 GIT binary patch literal 38300 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..39edd3a847cd9104943de69d202a9bdda40a67c7 GIT binary patch literal 232448 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%0AJ7Ty3 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

+dhUVRD^rsZLn~gz7*4=FMI~s!p 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..75201f26f0b9e81d18215b965e3e23aa19aada11 GIT binary patch literal 13312 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..9307688a6c246411cb22644e2c23ffea243d16c3 GIT binary patch literal 894 zcmbtSZAg<*6n-oyYLF6C_AmFV$f78PAS;Jd%n#D`LkSk9;UI`UD2R}-B1)of?)-;cT!RJJ^w|-@KnZ4V6F7M~_ z6P65EWN;}n{7;)Fsy0}6Vyxhr*SJh5(T&bx*tQn?4N0x5&;U5II|F?Zc`x`ed|9vY z)-0ts!d|%2;5=Rwd~?Q<13l6QPigFDQ45%{#dlLN{a};(@{)d9)Z7Y~fG&A|*oU{+ z+jXd*FCUH22m7nXbt-GtJ@>@ z2?u=@Qi?;%a`5ZLO8@J0hkBnaOEp{~+*6DwdXw=EYdU~WGI&m6IK?wROM59_fJU$} zo+c&rJ7~Ku^nDTLOiezTi|B$hhWcgSk77V>2~9ENV-0v4ECE&E%fqyl@k1_q3gr&M zF51Ce9-4oTtewb~>Jjj_^GOQt# +#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 0000000000000000000000000000000000000000..b9e83ab0008d2a686dd608008c089ee4799e81a9 GIT binary patch literal 21630 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 literal 0 HcmV?d00001 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(); + } +}